chore(build): execute `pub get` only if a pubspec.yaml changed and run `dart analyzer` on all dart files

`pub get` is now only executed when the `pubspec.yaml` in the `modules`
folder is different than the `pubspec.yaml` in the `build/dart` folder.

Generates the file `build/dart/_analyzer.dart` that imports all modules
to run `dart analyzer` against all of them. The build will fail whenever
there are errors, warnings or hints in `dart analyzer`.

Changes the sources so that `dart analyzer` does not report any
error, warning or hint.

Closes #40
This commit is contained in:
Tobias Bosch 2014-10-02 12:27:01 -07:00
parent 64fe73e20d
commit 33af1d0b39
22 changed files with 186 additions and 88 deletions

View File

@ -10,7 +10,10 @@ var ejs = require('gulp-ejs');
var path = require('path'); var path = require('path');
var through2 = require('through2'); var through2 = require('through2');
var file2moduleName = require('./file2modulename'); var file2moduleName = require('./file2modulename');
var exec = require('child_process').exec; var spawn = require('child_process').spawn;
var fs = require('fs');
var path = require('path');
var readline = require('readline');
var Q = require('q'); var Q = require('q');
var js2es5Options = { var js2es5Options = {
@ -31,14 +34,6 @@ var js2dartOptions = {
var gulpTraceur = require('./tools/transpiler/gulp-traceur'); var gulpTraceur = require('./tools/transpiler/gulp-traceur');
function execWithLog(command, options, done) {
exec(command, options, function (err, stdout, stderr) {
stdout && console.log(stdout);
stderr && console.log(stderr);
done(err);
});
}
// --------- // ---------
// traceur runtime // traceur runtime
@ -55,20 +50,10 @@ var sourceTypeConfigs = {
compilerOptions: js2dartOptions, compilerOptions: js2dartOptions,
transpileSrc: ['modules/**/*.js'], transpileSrc: ['modules/**/*.js'],
htmlSrc: ['modules/*/src/**/*.html'], htmlSrc: ['modules/*/src/**/*.html'],
copySrc: ['modules/**/*.dart', 'modules/**/*.yaml'], copySrc: ['modules/**/*.dart'],
outputDir: 'build/dart', outputDir: 'build/dart',
outputExt: 'dart', outputExt: 'dart',
mimeType: 'application/dart', mimeType: 'application/dart'
postProcess: function(file, done) {
if (file.path.match(/pubspec\.yaml/)) {
console.log('pub get ' + file.path);
execWithLog('pub get', {
cwd: path.dirname(file.path)
}, done);
} else {
done();
}
}
}, },
js: { js: {
compilerOptions: js2es5Options, compilerOptions: js2es5Options,
@ -76,8 +61,7 @@ var sourceTypeConfigs = {
htmlSrc: ['modules/*/src/**/*.html'], htmlSrc: ['modules/*/src/**/*.html'],
copySrc: ['modules/**/*.es5'], copySrc: ['modules/**/*.es5'],
outputDir: 'build/js', outputDir: 'build/js',
outputExt: 'js', outputExt: 'js'
postProcess: null
} }
}; };
@ -91,24 +75,35 @@ gulp.task('modules/build.dart/src', function() {
return createModuleTask(sourceTypeConfigs.dart); return createModuleTask(sourceTypeConfigs.dart);
}); });
gulp.task('modules/build.dart/analyzer', function() { gulp.task('modules/build.dart/pubspec', function(done) {
var baseDir = sourceTypeConfigs.dart.outputDir; var outputDir = sourceTypeConfigs.dart.outputDir;
var files = [].slice.call(glob.sync('*/lib/*.dart', { return gulp.src('modules/*/pubspec.yaml')
cwd: baseDir .pipe(through2.obj(function(file, enc, done) {
})); var targetFile = path.join(outputDir, file.relative);
files = files.filter(function(fileName) { if (fs.existsSync(targetFile)) {
return fileName.match(/(\w+)\/lib\/\1/); file.previousContents = fs.readFileSync(targetFile);
}); } else {
return Q.all(files.map(function(fileName) { file.previousContents = '';
var deferred = Q.defer(); }
execWithLog('dartanalyzer '+baseDir+'/'+fileName, {}, deferred.makeNodeResolver()); this.push(file);
return deferred.promise; done();
})); }))
.pipe(gulp.dest(outputDir))
.pipe(through2.obj(function(file, enc, done) {
if (file.previousContents.toString() !== file.contents.toString()) {
console.log(file.path + ' changed, calling pub get');
var stream = spawn('pub', ['get'], {
stdio: [process.stdin, process.stdout, process.stderr],
cwd: path.dirname(file.path)
});
stream.on('close', done);
} else {
done();
}
}));
}); });
gulp.task('modules/build.dart', function(done) { gulp.task('modules/build.dart', ['modules/build.dart/src', 'modules/build.dart/pubspec']);
runSequence('modules/build.dart/src', 'modules/build.dart/analyzer', done);
});
gulp.task('modules/build.js', function() { gulp.task('modules/build.js', function() {
return createModuleTask(sourceTypeConfigs.js); return createModuleTask(sourceTypeConfigs.js);
@ -136,15 +131,77 @@ function createModuleTask(sourceTypeConfig) {
})) }))
.pipe(gulp.dest(sourceTypeConfig.outputDir)); .pipe(gulp.dest(sourceTypeConfig.outputDir));
var s = mergeStreams(transpile, copy, html); return mergeStreams(transpile, copy, html);
if (!sourceTypeConfig.postProcess) {
return s;
}
return s.pipe(through2.obj(function(file, enc, done) {
sourceTypeConfig.postProcess(file, done);
}));
} }
// ------------------
// ANALYZE
gulp.task('analyze/dartanalyzer', function(done) {
var pubSpecs = [].slice.call(glob.sync('build/dart/*/pubspec.yaml', {
cwd: __dirname
}));
var tempFile = '_analyzer.dart';
// analyze in parallel!
return Q.all(pubSpecs.map(function(pubSpecFile) {
var dir = path.dirname(pubSpecFile);
var srcFiles = [].slice.call(glob.sync('lib/**/*.dart', {
cwd: dir
}));
var testFiles = [].slice.call(glob.sync('test/**/*_spec.dart', {
cwd: dir
}));
var analyzeFile = ['library _analyzer;'];
srcFiles.concat(testFiles).forEach(function(fileName, index) {
if (fileName !== tempFile) {
analyzeFile.push('import "./'+fileName+'" as mod'+index+';');
}
});
fs.writeFileSync(path.join(dir, tempFile), analyzeFile.join('\n'));
var defer = Q.defer();
analyze(dir, defer.makeNodeResolver());
return defer.promise;
}));
function analyze(dirName, done) {
var stream = spawn('dartanalyzer', ['--fatal-warnings', tempFile], {
// inherit stdin and stderr, but filter stdout
stdio: [process.stdin, 'pipe', process.stderr],
cwd: dirName
});
// Filter out unused imports from our generated file.
// We don't reexports from the generated file
// as this could lead to name clashes when two files
// export the same thing.
var rl = require('readline').createInterface({
input: stream.stdout,
output: process.stdout,
terminal: false
});
var hintCount = 0;
rl.on('line', function(line) {
if (line.match(/Unused import .*_analyzer\.dart/)) {
return;
}
if (line.match(/\[hint\]/)) {
hintCount++;
}
console.log(dirName + ':' + line);
});
stream.on('close', function(code) {
var error;
if (code !== 0) {
error = new Error('Dartanalyzer failed with exit code ' + code);
}
if (hintCount > 0) {
error = new Error('Dartanalyzer showed hints');
}
done(error);
});
}
});
// ------------------ // ------------------
// WEB SERVER // WEB SERVER
gulp.task('serve', connect.server({ gulp.task('serve', connect.server({
@ -167,4 +224,15 @@ gulp.task('serve', connect.server({
gulp.task('clean', ['modules/clean']); gulp.task('clean', ['modules/clean']);
gulp.task('build', ['jsRuntime/build', 'modules/build.dart', 'modules/build.js']); gulp.task('build', function(done) {
runSequence(
// parallel
['jsRuntime/build', 'modules/build.dart', 'modules/build.js'],
// sequential
'analyze/dartanalyzer'
);
});
gulp.task('analyze', function(done) {
runSequence('analyze/dartanalyzer');
});

View File

@ -2,8 +2,9 @@ name: change_detection
environment: environment:
sdk: '>=1.4.0' sdk: '>=1.4.0'
dependencies: dependencies:
facade:
path: ../facade
dev_dependencies: dev_dependencies:
test_lib: test_lib:
path: ../test_lib path: ../test_lib
facade: guinness: ">=0.1.5 <0.2.0"
path: ../facade

View File

@ -368,7 +368,7 @@ export class Scanner {
} }
error(message:string) { error(message:string) {
var position:int = this.index + this.offset; var position:int = this.index;
throw `Lexer Error: ${message} at column ${position} in expression [${input}]`; throw `Lexer Error: ${message} at column ${position} in expression [${input}]`;
} }
} }

View File

@ -25,12 +25,14 @@ export class ProtoRecord {
this.prev = null; this.prev = null;
this.changeNotifier = null; this.changeNotifier = null;
this._clone = null; this._clone = null;
} this.changeContext = null;
this.dispatcherContext = null;
}
instantiate(watchGroup/*:wg.WatchGroup*/):Record { instantiate(watchGroup/*:wg.WatchGroup*/):Record {
var record = this._clone = new Record(watchGroup, this); var record = this._clone = new Record(watchGroup, this);
record.prev = this.prev._clone; record.prev = this.prev._clone;
record._checkPrev = this._prev._clone; record._checkPrev = this.prev._clone;
return _clone; return _clone;
} }

View File

@ -1,13 +1,11 @@
import {describe, it, expect} from 'test_lib/test_lib'; import {describe, it, xit, expect} from 'test_lib/test_lib';
import {ProtoWatchGroup, WatchGroup, WatchGroupDispatcher} from 'change_detection/change_detection'; import {ProtoWatchGroup, WatchGroup, WatchGroupDispatcher, ChangeDetection} from 'change_detection/change_detection';
import {DOM} from 'facade/dom';
export function main() { export function main() {
describe('change_detection', function() { describe('change_detection', function() {
describe('ChangeDetection', function() { describe('ChangeDetection', function() {
it('should do simple watching', function() { xit('should do simple watching', function() {
return; // remove me once xit or CD works.
var person = new Person('misko', 38); var person = new Person('misko', 38);
var pwg = new ProtoWatchGroup(); var pwg = new ProtoWatchGroup();
pwg.watch('name', 'nameToken'); pwg.watch('name', 'nameToken');
@ -32,11 +30,16 @@ export function main() {
class Person { class Person {
constructor(name:string, age:number) { constructor(name:string, age:number) {
this.name = null; this.name = name;
this.a this.age = age;
} }
} }
class Dispatcher extends WatchGroupDispatcher { class LoggingDispatcher extends WatchGroupDispatcher {
constructor() {
this.log = null;
}
clear() {
}
} }

View File

@ -1,6 +1,5 @@
import {describe, it, expect} from 'test_lib/test_lib'; import {describe, it, expect} from 'test_lib/test_lib';
import {Scanner, Token} from 'change_detection/parser/scanner'; import {Scanner, Token} from 'change_detection/parser/scanner';
import {DOM} from 'facade/dom';
import {List, ListWrapper} from "facade/collection"; import {List, ListWrapper} from "facade/collection";
import {StringWrapper} from "facade/lang"; import {StringWrapper} from "facade/lang";

View File

@ -11,3 +11,4 @@ dependencies:
dev_dependencies: dev_dependencies:
test_lib: test_lib:
path: ../test_lib path: ../test_lib
guinness: ">=0.1.5 <0.2.0"

View File

@ -1,19 +1,19 @@
import {Type} from 'facade/lang'; // import {Type} from 'facade/lang';
import {ElementServicesFunction} from './facade'; // import {ElementServicesFunction} from './facade';
import {ABSTRACT} from 'facade/lang'; import {ABSTRACT} from 'facade/lang';
@ABSTRACT @ABSTRACT()
export class Directive { export class Directive {
constructor({ constructor({
selector, selector,
lightDomServices, lightDomServices,
implementsTypes implementsTypes
}:{ }/*:{
selector:String, selector:String,
lightDomServices:ElementServicesFunction, lightDomServices:ElementServicesFunction,
implementsTypes:Array<Type> implementsTypes:Array<Type>
}) }*/)
{ {
this.lightDomServices = lightDomServices; this.lightDomServices = lightDomServices;
this.selector = selector; this.selector = selector;

View File

@ -1,3 +1,5 @@
library core.annotations.facade;
import 'package:di/di.dart' show Module; import 'package:di/di.dart' show Module;
import '../compiler/element_module.dart' show ElementModule; import '../compiler/element_module.dart' show ElementModule;

View File

@ -1,4 +1,4 @@
import {Type, List} from 'facade/lang'; // import {Type, List} from 'facade/lang';
export class TemplateConfig { export class TemplateConfig {
constructor({ constructor({
@ -6,11 +6,11 @@ export class TemplateConfig {
directives, directives,
formatters, formatters,
source source
}: { }/*: {
url: String, url: String,
directives: List<Type>, directives: List<Type>,
formatters: List<Type>, formatters: List<Type>,
source: List<TemplateConfig> source: List<TemplateConfig>
}) }*/)
{} {}
} }

View File

@ -1,6 +1,6 @@
import {Future, Type} from 'facade/lang'; import {Future, Type} from 'facade/lang';
import {Element} from 'facade/dom'; import {Element} from 'facade/dom';
import {ProtoView} from './view'; //import {ProtoView} from './view';
import {TemplateLoader} from './template_loader'; import {TemplateLoader} from './template_loader';
import {FIELD} from 'facade/lang'; import {FIELD} from 'facade/lang';
@ -19,7 +19,7 @@ export class Compiler {
* - don't know about injector in deserialization * - don't know about injector in deserialization
* - compile does not need the injector, only the ViewFactory does * - compile does not need the injector, only the ViewFactory does
*/ */
compile(component:Type, element:Element/* = null*/):Future<ProtoView> { compile(component:Type, element:Element/* = null*/):Future/*<ProtoView>*/ {
return null; return null;
} }

View File

@ -1,11 +1,11 @@
import {Future} from 'facade/lang'; import {Future} from 'facade/lang';
import {Document} from 'facade/dom'; //import {Document} from 'facade/dom';
export class TemplateLoader { export class TemplateLoader {
constructor() {} constructor() {}
load(url:String):Future<Document> { load(url:String):Future/*<Document>*/ {
return null; return null;
} }
} }

View File

@ -4,7 +4,10 @@ export class LifeCycle {
@FIELD('final _changeDetection:ChangeDetection') @FIELD('final _changeDetection:ChangeDetection')
@FIELD('final _onChangeDispatcher:OnChangeDispatcher') @FIELD('final _onChangeDispatcher:OnChangeDispatcher')
constructor() {} constructor() {
this._changeDetection = null;
this._onChangeDispatcher = null;
}
digest() { digest() {
_changeDetection.detectChanges(); _changeDetection.detectChanges();

View File

@ -1,5 +1,5 @@
import {describe, it, expect} from 'test_lib/test_lib'; import {describe, it} from 'test_lib/test_lib';
import {Compiler} from 'core/compiler/compiler'; //import {Compiler} from 'core/compiler/compiler';
export function main() { export function main() {
describe('compiler', function() { describe('compiler', function() {

View File

@ -7,3 +7,4 @@ dependencies:
dev_dependencies: dev_dependencies:
test_lib: test_lib:
path: ../test_lib path: ../test_lib
guinness: ">=0.1.5 <0.2.0"

View File

@ -2,6 +2,9 @@ name: examples
environment: environment:
sdk: '>=1.4.0' sdk: '>=1.4.0'
dependencies: dependencies:
facade:
path: ../facade
dev_dependencies: dev_dependencies:
test_lib: test_lib:
path: ../test_lib path: ../test_lib
guinness: ">=0.1.5 <0.2.0"

View File

@ -5,3 +5,4 @@ dependencies:
dev_dependencies: dev_dependencies:
test_lib: test_lib:
path: ../test_lib path: ../test_lib
guinness: ">=0.1.5 <0.2.0"

View File

@ -6,7 +6,7 @@ export 'dart:html' show DocumentFragment, Node, Element, TemplateElement, Text;
class DOM { class DOM {
static query(selector) { static query(selector) {
return document.query(selector); return document.querySelector(selector);
} }
static on(element, event, callback) { static on(element, event, callback) {
element.addEventListener(event, callback); element.addEventListener(event, callback);

View File

@ -4,12 +4,20 @@ export 'dart:async' show Future;
export 'dart:core' show Type, int; export 'dart:core' show Type, int;
class FIELD { class FIELD {
const constructor(this.definition); final String definition;
const FIELD(this.definition);
} }
class CONST {} class CONST {
class ABSTRACT {} const CONST();
class IMPLEMENTS {} }
class ABSTRACT {
const ABSTRACT();
}
class IMPLEMENTS {
final interfaceClass;
const IMPLEMENTS(this.interfaceClass);
}
class StringWrapper { class StringWrapper {

View File

@ -1,3 +1,4 @@
library test_lib.test_lib;
export 'package:guinness/guinness.dart' show export 'package:guinness/guinness.dart' show
describe, ddescribe, xdescribe, describe, ddescribe, xdescribe,
it, xit, iit, it, xit, iit,

View File

@ -2,6 +2,8 @@ name: angular
environment: environment:
sdk: '>=1.4.0' sdk: '>=1.4.0'
dependencies: dependencies:
examples:
path: build/dart/examples
test_lib: test_lib:
path: build/dart/test_lib path: build/dart/test_lib
facade: facade:

View File

@ -255,13 +255,16 @@ export class DartTreeWriter extends JavaScriptParseTreeWriter {
// ANNOTATIONS // ANNOTATIONS
// TODO(vojta): this is just fixing a bug in Traceur, send a PR. // TODO(vojta): this is just fixing a bug in Traceur, send a PR.
visitAnnotation(tree) { visitAnnotation(tree) {
if (tree.name.identifierToken) { // TODO(tbosch): Disabled the removal of control annotations (annotations in uppercase),
var nameValue = tree.name.identifierToken.value; // as they should be handeled by a transformer and right now lead
if (nameValue === nameValue.toUpperCase()) { // to errors (unused import) in dartanalyzer.
// control annotations for transpiler // if (tree.name.identifierToken) {
return; // var nameValue = tree.name.identifierToken.value;
} // if (nameValue === nameValue.toUpperCase()) {
} // // control annotations for transpiler
// return;
// }
// }
this.write_(AT); this.write_(AT);
this.visitAny(tree.name); this.visitAny(tree.name);