diff --git a/gulpfile.js b/gulpfile.js index e0873d581c..14282984d7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -178,6 +178,12 @@ gulp.task('!build/tree.dart', function() { // Run a top-level `pub get` for this project. gulp.task('pubget.dart', pubget.dir(gulp, gulpPlugins, { dir: '.', command: DART_SDK.PUB })); +// Run `pub get` only on the angular2 dir of CONFIG.dest.dart +gulp.task('!build/pubget.angular2.dart', pubget.dir(gulp, gulpPlugins, { + dir: path.join(CONFIG.dest.dart, 'angular2'), + command: DART_SDK.PUB +})); + // Run `pub get` over CONFIG.dest.dart gulp.task('build/pubspec.dart', pubget.subDir(gulp, gulpPlugins, { dir: CONFIG.dest.dart, @@ -588,6 +594,8 @@ gulp.task('build/packages.dart', function(done) { runSequence( 'build/tree.dart', // Run after 'build/tree.dart' because broccoli clears the dist/dart folder + '!build/pubget.angular2.dart', + '!build/change_detect.dart', 'build/pure-packages.dart', 'build/format.dart', done); @@ -811,6 +819,34 @@ gulp.task('clean', ['build/clean.tools', 'build/clean.js', 'build/clean.dart', ' gulp.task('build', ['build.js', 'build.dart']); +// ------------ +// change detection codegen + + +gulp.task('build.change_detect.dart', function(done) { + return runSequence('build/packages.dart', '!build/pubget.angular2.dart', + '!build/change_detect.dart', done); +}); + +gulp.task('!build/change_detect.dart', function(done) { + var fs = require('fs'); + var changeDetectDir = path.join(CONFIG.dest.dart, 'angular2/test/change_detection/'); + var srcDir = path.join(changeDetectDir, 'generator'); + var destDir = path.join(changeDetectDir, 'generated'); + + var dartStream = fs.createWriteStream(path.join(destDir, 'simple_watch_classes.dart')); + var genMain = path.join(srcDir, 'gen_change_detectors.dart'); + var proc = spawn(DART_SDK.VM, [genMain], { stdio:['ignore', 'pipe', 'inherit'] }); + proc.on('error', function(code) { + done(new Error('Failed while generating change detector classes. Please run manually: ' + + DART_SDK.VM + ' ' + dartArgs.join(' '))); + }); + proc.on('close', function() { + dartStream.close(); + done(); + }); + proc.stdout.pipe(dartStream); +}); // ------------ // angular material testing rules diff --git a/modules/angular2/src/change_detection/change_detection_jit_generator.ts b/modules/angular2/src/change_detection/change_detection_jit_generator.ts index bb933d658e..06742bc74a 100644 --- a/modules/angular2/src/change_detection/change_detection_jit_generator.ts +++ b/modules/angular2/src/change_detection/change_detection_jit_generator.ts @@ -27,6 +27,10 @@ import { * The code generator takes a list of proto records and creates a function/class * that "emulates" what the developer would write by hand to implement the same * kind of behaviour. + * + * This code should be kept in sync with the Dart transformer's + * `angular2.transform.template_compiler.change_detector_codegen` library. If you make updates + * here, please make equivalent changes there. */ var ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector"; var UTIL = "ChangeDetectionUtil"; diff --git a/modules/angular2/src/change_detection/pregen_proto_change_detector.dart b/modules/angular2/src/change_detection/pregen_proto_change_detector.dart new file mode 100644 index 0000000000..87df3f33b7 --- /dev/null +++ b/modules/angular2/src/change_detection/pregen_proto_change_detector.dart @@ -0,0 +1,65 @@ +library angular2.src.change_detection.pregen_proto_change_detector; + +import 'package:angular2/src/change_detection/coalesce.dart'; +import 'package:angular2/src/change_detection/directive_record.dart'; +import 'package:angular2/src/change_detection/interfaces.dart'; +import 'package:angular2/src/change_detection/pipes/pipe_registry.dart'; +import 'package:angular2/src/change_detection/proto_change_detector.dart'; +import 'package:angular2/src/change_detection/proto_record.dart'; + +export 'dart:core' show List; +export 'package:angular2/src/change_detection/abstract_change_detector.dart' + show AbstractChangeDetector; +export 'package:angular2/src/change_detection/directive_record.dart' + show DirectiveIndex, DirectiveRecord; +export 'package:angular2/src/change_detection/interfaces.dart' + show ChangeDetector, ChangeDetectorDefinition, ProtoChangeDetector; +export 'package:angular2/src/change_detection/pipes/pipe_registry.dart' + show PipeRegistry; +export 'package:angular2/src/change_detection/proto_record.dart' + show ProtoRecord; +export 'package:angular2/src/change_detection/change_detection_util.dart' + show ChangeDetectionUtil; +export 'package:angular2/src/facade/lang.dart' show looseIdentical; + +typedef ChangeDetector InstantiateMethod(dynamic dispatcher, + PipeRegistry registry, List protoRecords, + List directiveRecords); + +/// Implementation of [ProtoChangeDetector] for use by pre-generated change +/// detectors in Angular 2 Dart. +/// Classes generated by the `TemplateCompiler` use this. The `export`s above +/// allow the generated code to `import` a single library and get all +/// dependencies. +class PregenProtoChangeDetector extends ProtoChangeDetector { + /// The [ChangeDetectorDefinition#id]. Strictly informational. + final String id; + + /// Closure used to generate an actual [ChangeDetector]. + final InstantiateMethod _instantiateMethod; + + // [ChangeDetector] dependencies. + final PipeRegistry _pipeRegistry; + final List _protoRecords; + final List _directiveRecords; + + /// Internal ctor. + PregenProtoChangeDetector._(this.id, this._instantiateMethod, + this._pipeRegistry, this._protoRecords, this._directiveRecords); + + factory PregenProtoChangeDetector(InstantiateMethod instantiateMethod, + PipeRegistry registry, ChangeDetectorDefinition def) { + // TODO(kegluneq): Pre-generate these (#2067). + var recordBuilder = new ProtoRecordBuilder(); + def.bindingRecords.forEach((b) { + recordBuilder.add(b, def.variableNames); + }); + var protoRecords = coalesce(recordBuilder.records); + return new PregenProtoChangeDetector._(def.id, instantiateMethod, registry, + protoRecords, def.directiveRecords); + } + + @override + instantiate(dynamic dispatcher) => _instantiateMethod( + dispatcher, _pipeRegistry, _protoRecords, _directiveRecords); +} diff --git a/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart b/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart new file mode 100644 index 0000000000..2ad4a9e409 --- /dev/null +++ b/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart @@ -0,0 +1,440 @@ +library angular2.transform.template_compiler.change_detector_codegen; + +import 'dart:convert' show JSON; +import 'package:angular2/src/change_detection/change_detection_util.dart'; +import 'package:angular2/src/change_detection/coalesce.dart'; +import 'package:angular2/src/change_detection/directive_record.dart'; +import 'package:angular2/src/change_detection/interfaces.dart'; +import 'package:angular2/src/change_detection/proto_change_detector.dart'; +import 'package:angular2/src/change_detection/proto_record.dart'; + +/// Responsible for generating change detector classes for Angular 2. +/// +/// This code should be kept in sync with the `ChangeDetectorJITGenerator` +/// class. If you make updates here, please make equivalent changes there. +class Codegen { + final StringBuffer _buf = new StringBuffer(); + + void generate(String name, ChangeDetectorDefinition def) { + new _CodegenState(name, def)._writeToBuf(_buf); + } + + String get imports { + return _buf.isEmpty + ? '' + : '''import '$_PREGEN_PROTO_CHANGE_DETECTOR_IMPORT' as $_GEN_PREFIX;'''; + } + + bool get isEmpty => _buf.isEmpty; + + @override + String toString() => '$_buf'; +} + +/// The state needed to generate a change detector for a single `Component`. +class _CodegenState { + final String _typeName; + final String _changeDetectionMode; + final List _records; + final List _directiveRecords; + final List _localNames; + final List _changeNames; + final List _fieldNames; + final List _pipeNames; + + _CodegenState._(this._typeName, String changeDetectionStrategy, this._records, + this._directiveRecords, List localNames) + : this._localNames = localNames, + _changeNames = _getChangeNames(localNames), + _fieldNames = _getFieldNames(localNames), + _pipeNames = _getPipeNames(localNames), + _changeDetectionMode = ChangeDetectionUtil + .changeDetectionMode(changeDetectionStrategy); + + factory _CodegenState(String typeName, ChangeDetectorDefinition def) { + var protoRecords = new ProtoRecordBuilder(); + def.bindingRecords + .forEach((rec) => protoRecords.add(rec, def.variableNames)); + var records = coalesce(protoRecords.records); + return new _CodegenState._(typeName, def.strategy, records, + def.directiveRecords, _getLocalNames(records)); + } + + /// Generates sanitized names for use as local variables. + static List _getLocalNames(List records) { + var localNames = new List(records.length + 1); + localNames[0] = 'context'; + for (var i = 0; i < records.length; ++i) { + var sanitizedName = records[i].name.replaceAll(new RegExp(r'\W'), ''); + localNames[i + 1] = '$sanitizedName$i'; + } + return localNames; + } + + /// Generates names for use as local change variables. + static List _getChangeNames(List localNames) => + localNames.map((name) => 'change_$name').toList(); + + /// Generates names for use as private fields. + static List _getFieldNames(List localNames) => + localNames.map((name) => '_$name').toList(); + + /// Generates names for use as private pipe variables. + static List _getPipeNames(List localNames) => + localNames.map((name) => '_${name}_pipe').toList(); + + void _writeToBuf(StringBuffer buf) { + buf.write(''' + class $_typeName extends $_BASE_CLASS { + final dynamic $_DISPATCHER_ACCESSOR; + final $_GEN_PREFIX.PipeRegistry $_PIPE_REGISTRY_ACCESSOR; + final $_GEN_PREFIX.List<$_GEN_PREFIX.ProtoRecord> $_PROTOS_ACCESSOR; + final $_GEN_PREFIX.List<$_GEN_PREFIX.DirectiveRecord> + $_DIRECTIVES_ACCESSOR; + dynamic $_LOCALS_ACCESSOR = null; + ${_allFields().map( + (f) => 'dynamic $f = $_UTIL.uninitialized();').join('')} + + $_typeName( + this.$_DISPATCHER_ACCESSOR, + this.$_PIPE_REGISTRY_ACCESSOR, + this.$_PROTOS_ACCESSOR, + this.$_DIRECTIVES_ACCESSOR) : super(); + + void detectChangesInRecords(throwOnChange) { + ${_genLocalDefinitions()} + ${_genChangeDefinitons()} + var $_IS_CHANGED_LOCAL = false; + var $_CURRENT_PROTO; + var $_CHANGES_LOCAL = null; + + context = $_CONTEXT_ACCESSOR; + ${_records.map(_genRecord).join('')} + } + + void callOnAllChangesDone() { + ${_getCallOnAllChangesDoneBody()} + } + + void hydrate(context, locals, directives) { + $_MODE_ACCESSOR = '$_changeDetectionMode'; + $_CONTEXT_ACCESSOR = context; + $_LOCALS_ACCESSOR = locals; + ${_genHydrateDirectives()} + ${_genHydrateDetectors()} + } + + void dehydrate() { + ${_genPipeOnDestroy()} + ${_allFields().map((f) => '$f = $_UTIL.uninitialized();').join('')} + $_LOCALS_ACCESSOR = null; + } + + hydrated() => !$_IDENTICAL_CHECK_FN( + $_CONTEXT_ACCESSOR, $_UTIL.uninitialized()); + + static $_GEN_PREFIX.ProtoChangeDetector + $PROTO_CHANGE_DETECTOR_FACTORY_METHOD( + $_GEN_PREFIX.PipeRegistry registry, + $_GEN_PREFIX.ChangeDetectorDefinition def) { + return new $_GEN_PREFIX.PregenProtoChangeDetector( + (a, b, c, d) => new $_typeName(a, b, c, d), + registry, def); + } + } + '''); + } + + List _genGetDirectiveFieldNames() { + return _directiveRecords + .map((d) => _genGetDirective(d.directiveIndex)) + .toList(); + } + + List _genGetDetectorFieldNames() { + return _directiveRecords + .where((d) => d.isOnPushChangeDetection()) + .map((d) => _genGetDetector(d.directiveIndex)) + .toList(); + } + + String _genGetDirective(DirectiveIndex d) => '_directive_${d.name}'; + String _genGetDetector(DirectiveIndex d) => '_detector_${d.name}'; + + List _getNonNullPipeNames() { + return _records + .where((r) => + r.mode == RECORD_TYPE_PIPE || r.mode == RECORD_TYPE_BINDING_PIPE) + .map((r) => _pipeNames[r.selfIndex]) + .toList(); + } + + List _allFields() { + return new List.from(_fieldNames) + ..addAll(_getNonNullPipeNames()) + ..addAll(_genGetDirectiveFieldNames()) + ..addAll(_genGetDetectorFieldNames()); + } + + String _genHydrateDirectives() { + var buf = new StringBuffer(); + var directiveFieldNames = _genGetDirectiveFieldNames(); + for (var i = 0; i < directiveFieldNames.length; ++i) { + buf.writeln('${directiveFieldNames[i]} = directives.getDirectiveFor(' + '$_DIRECTIVES_ACCESSOR[$i].directiveIndex);'); + } + return '$buf'; + } + + String _genHydrateDetectors() { + var buf = new StringBuffer(); + var detectorFieldNames = _genGetDetectorFieldNames(); + for (var i = 0; i < detectorFieldNames.length; ++i) { + buf.writeln('${detectorFieldNames[i]} = directives.getDetectorFor(' + '$_DIRECTIVES_ACCESSOR[$i].directiveIndex)'); + } + return '$buf'; + } + + String _genPipeOnDestroy() => + _getNonNullPipeNames().map((p) => '$p.onDestroy();').join(''); + + /// Generates calls to `onAllChangesDone` for all `Directive`s that request + /// them. + String _getCallOnAllChangesDoneBody() { + // NOTE(kegluneq): Order is important! + return _directiveRecords.reversed + .where((rec) => rec.callOnAllChangesDone) + .map((rec) => + '${_genGetDirective(rec.directiveIndex)}.onAllChangesDone();') + .join(''); + } + + String _genLocalDefinitions() => + _localNames.map((name) => 'var $name = null;').join(''); + + String _genChangeDefinitons() => + _changeNames.map((name) => 'var $name = false;').join(''); + + String _genRecord(ProtoRecord r) { + if (r.mode == RECORD_TYPE_PIPE || r.mode == RECORD_TYPE_BINDING_PIPE) { + return _genPipeCheck(r); + } else { + return _genReferenceCheck(r); + } + } + + String _genPipeCheck(ProtoRecord r) { + var context = _localNames[r.contextIndex]; + var oldValue = _fieldNames[r.selfIndex]; + var newValue = _localNames[r.selfIndex]; + var change = _changeNames[r.selfIndex]; + + var pipe = _pipeNames[r.selfIndex]; + var cdRef = r.mode == RECORD_TYPE_BINDING_PIPE ? 'this.ref' : 'null'; + + var protoIndex = r.selfIndex - 1; + var pipeType = r.name; + return ''' + $_CURRENT_PROTO = $_PROTOS_ACCESSOR[$protoIndex]; + if ($_IDENTICAL_CHECK_FN($pipe, $_UTIL.uninitialized())) { + $pipe = $_PIPE_REGISTRY_ACCESSOR.get('$pipeType', $context, $cdRef); + } else if (!$pipe.supports($context)) { + $pipe.onDestroy(); + $pipe = $_PIPE_REGISTRY_ACCESSOR.get('$pipeType', $context, $cdRef); + } + + $newValue = $pipe.transform($context); + if (!$_IDENTICAL_CHECK_FN($oldValue, $newValue)) { + $newValue = $_UTIL.unwrapValue($newValue); + $change = true; + ${_genUpdateDirectiveOrElement(r)} + ${_genAddToChanges(r)} + $oldValue = $newValue; + } + ${_genLastInDirective(r)} + '''; + } + + String _genReferenceCheck(ProtoRecord r) { + var oldValue = _fieldNames[r.selfIndex]; + var newValue = _localNames[r.selfIndex]; + + var protoIndex = r.selfIndex - 1; + var check = ''' + $_CURRENT_PROTO = $_PROTOS_ACCESSOR[$protoIndex]; + ${_genUpdateCurrentValue(r)} + if (!$_IDENTICAL_CHECK_FN($newValue, $oldValue)) { + ${_changeNames[r.selfIndex]} = true; + ${_genUpdateDirectiveOrElement(r)} + ${_genAddToChanges(r)} + $oldValue = $newValue; + } + ${_genLastInDirective(r)} + '''; + if (r.isPureFunction()) { + // Add an "if changed guard" + var condition = r.args.map((a) => _changeNames[a]).join(' || '); + return 'if ($condition) { $check }'; + } else { + return check; + } + } + + String _genUpdateCurrentValue(ProtoRecord r) { + var context = r.contextIndex == -1 + ? _genGetDirective(r.directiveIndex) + : _localNames[r.contextIndex]; + + var newValue = _localNames[r.selfIndex]; + var argString = r.args.map((arg) => _localNames[arg]).join(', '); + + var rhs; + switch (r.mode) { + case RECORD_TYPE_SELF: + rhs = context; + break; + + case RECORD_TYPE_CONST: + rhs = JSON.encode(r.funcOrValue); + break; + + case RECORD_TYPE_PROPERTY: + rhs = '$context.${r.name}'; + break; + + case RECORD_TYPE_LOCAL: + rhs = '$_LOCALS_ACCESSOR.get("${r.name}")'; + break; + + case RECORD_TYPE_INVOKE_METHOD: + rhs = '$context.${r.name}($argString)'; + break; + + case RECORD_TYPE_INVOKE_CLOSURE: + rhs = '$context($argString)'; + break; + + case RECORD_TYPE_PRIMITIVE_OP: + rhs = '$_UTIL.${r.name}($argString)'; + break; + + case RECORD_TYPE_INTERPOLATE: + rhs = _genInterpolation(r); + break; + + case RECORD_TYPE_KEYED_ACCESS: + rhs = '$context[${_localNames[r.args[0]]}]'; + break; + + default: + throw new FormatException( + 'Unknown operation ${r.mode}', r.expressionAsString); + } + return '$newValue = $rhs;'; + } + + String _genInterpolation(ProtoRecord r) { + var res = new StringBuffer(); + for (var i = 0; i < r.args.length; ++i) { + res.write('${JSON.encode(r.fixedArgs[i])} + ${_localNames[r.args[i]]} +'); + } + res.write(JSON.encode(r.fixedArgs[r.args.length])); + return '$res'; + } + + String _genUpdateDirectiveOrElement(ProtoRecord r) { + if (!r.lastInBinding) return ''; + + var newValue = _localNames[r.selfIndex]; + var oldValue = _fieldNames[r.selfIndex]; + + var br = r.bindingRecord; + if (br.isDirective()) { + var directiveProperty = + '${_genGetDirective(br.directiveRecord.directiveIndex)}.${br.propertyName}'; + return ''' + ${_genThrowOnChangeCheck(oldValue, newValue)} + $directiveProperty = $newValue; + $_IS_CHANGED_LOCAL = true; + '''; + } else { + return ''' + ${_genThrowOnChangeCheck(oldValue, newValue)} + $_DISPATCHER_ACCESSOR.notifyOnBinding( + $_CURRENT_PROTO.bindingRecord, ${newValue}); + '''; + } + } + + String _genThrowOnChangeCheck(String oldValue, String newValue) { + return ''' + if(throwOnChange) { + $_UTIL.throwOnChange( + $_CURRENT_PROTO, $_UTIL.simpleChange(${oldValue}, ${newValue})); + } + '''; + } + + String _genAddToChanges(ProtoRecord r) { + var newValue = _localNames[r.selfIndex]; + var oldValue = _fieldNames[r.selfIndex]; + if (!r.bindingRecord.callOnChange()) return ''; + return ''' + $_CHANGES_LOCAL = $_UTIL.addChange( + $_CHANGES_LOCAL, + $_CURRENT_PROTO.bindingRecord.propertyName, + $_UTIL.simpleChange($oldValue, $newValue)); + '''; + } + + String _genLastInDirective(ProtoRecord r) { + return ''' + ${_genNotifyOnChanges(r)} + ${_genNotifyOnPushDetectors(r)} + $_IS_CHANGED_LOCAL = false; + '''; + } + + String _genNotifyOnChanges(ProtoRecord r) { + var br = r.bindingRecord; + if (!r.lastInDirective || !br.callOnChange()) return ''; + return ''' + if($_CHANGES_LOCAL) { + ${_genGetDirective(br.directiveRecord.directiveIndex)} + .onChange($_CHANGES_LOCAL); + $_CHANGES_LOCAL = null; + } + '''; + } + + String _genNotifyOnPushDetectors(ProtoRecord r) { + var br = r.bindingRecord; + if (!r.lastInDirective || !br.isOnPushChangeDetection()) return ''; + return ''' + if($_IS_CHANGED_LOCAL) { + ${_genGetDetector(br.directiveRecord.directiveIndex)}.markAsCheckOnce(); + } + '''; + } +} + +const PROTO_CHANGE_DETECTOR_FACTORY_METHOD = 'newProtoChangeDetector'; + +const _BASE_CLASS = '$_GEN_PREFIX.AbstractChangeDetector'; +const _CHANGES_LOCAL = 'changes'; +const _CONTEXT_ACCESSOR = '_context'; +const _CURRENT_PROTO = 'currentProto'; +const _DIRECTIVES_ACCESSOR = '_directiveRecords'; +const _DISPATCHER_ACCESSOR = '_dispatcher'; +const _GEN_PREFIX = '_gen'; +const _GEN_RECORDS_METHOD_NAME = '_createRecords'; +const _IDENTICAL_CHECK_FN = '$_GEN_PREFIX.looseIdentical'; +const _IS_CHANGED_LOCAL = 'isChanged'; +const _LOCALS_ACCESSOR = '_locals'; +const _MODE_ACCESSOR = 'mode'; +const _PREGEN_PROTO_CHANGE_DETECTOR_IMPORT = + 'package:angular2/src/change_detection/pregen_proto_change_detector.dart'; +const _PIPE_REGISTRY_ACCESSOR = '_pipeRegistry'; +const _PROTOS_ACCESSOR = '_protos'; +const _UTIL = '$_GEN_PREFIX.ChangeDetectionUtil'; diff --git a/modules/angular2/src/transform/template_compiler/generator.dart b/modules/angular2/src/transform/template_compiler/generator.dart index da9e1b80e9..a3f574ee83 100644 --- a/modules/angular2/src/transform/template_compiler/generator.dart +++ b/modules/angular2/src/transform/template_compiler/generator.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'package:angular2/src/change_detection/parser/lexer.dart' as ng; import 'package:angular2/src/change_detection/parser/parser.dart' as ng; +import 'package:angular2/src/core/compiler/proto_view_factory.dart'; import 'package:angular2/src/render/api.dart'; import 'package:angular2/src/render/dom/compiler/compiler.dart'; import 'package:angular2/src/render/dom/compiler/template_loader.dart'; @@ -11,13 +12,13 @@ import 'package:angular2/src/services/xhr.dart' show XHR; import 'package:angular2/src/reflection/reflection.dart'; import 'package:angular2/src/services/url_resolver.dart'; import 'package:angular2/src/transform/common/asset_reader.dart'; -import 'package:angular2/src/transform/common/names.dart'; -import 'package:angular2/src/transform/common/property_utils.dart' as prop; import 'package:angular2/src/transform/common/xhr_impl.dart'; import 'package:barback/barback.dart'; +import 'change_detector_codegen.dart' as change; import 'compile_step_factory.dart'; import 'recording_reflection_capabilities.dart'; +import 'reflector_register_codegen.dart' as reg; import 'view_definition_creator.dart'; /// Reads the `.ng_deps.dart` file represented by `entryPoint` and parses any @@ -25,73 +26,62 @@ import 'view_definition_creator.dart'; /// `setter`s, and `method`s that would otherwise be reflectively accessed. /// /// This method assumes a {@link DomAdapter} has been registered. -Future processTemplates(AssetReader reader, AssetId entryPoint) async { +Future processTemplates(AssetReader reader, AssetId entryPoint, + {bool generateRegistrations: true, + bool generateChangeDetectors: true}) async { var viewDefResults = await createViewDefinitions(reader, entryPoint); var extractor = new _TemplateExtractor(new XhrImpl(reader, entryPoint)); - var registrations = new StringBuffer(); - for (var viewDef in viewDefResults.viewDefinitions.values) { - var values = await extractor.extractTemplates(viewDef); - if (values == null) continue; - var calls = _generateGetters(values.getterNames); - if (calls.isNotEmpty) { - registrations.write('..${REGISTER_GETTERS_METHOD_NAME}' - '({${calls.join(', ')}})'); - } - calls = _generateSetters(values.setterNames); - if (calls.isNotEmpty) { - registrations.write('..${REGISTER_SETTERS_METHOD_NAME}' - '({${calls.join(', ')}})'); - } - calls = _generateMethods(values.methodNames); - if (calls.isNotEmpty) { - registrations.write('..${REGISTER_METHODS_METHOD_NAME}' - '({${calls.join(', ')}})'); + var registrations = new reg.Codegen(); + var changeDetectorClasses = new change.Codegen(); + for (var rType in viewDefResults.viewDefinitions.keys) { + var viewDefEntry = viewDefResults.viewDefinitions[rType]; + var result = await extractor.extractTemplates(viewDefEntry.viewDef); + if (result == null) continue; + + registrations.generate(result.recording); + if (result.protoView != null && generateChangeDetectors) { + var savedReflectionCapabilities = reflector.reflectionCapabilities; + var recordingCapabilities = new RecordingReflectionCapabilities(); + reflector.reflectionCapabilities = recordingCapabilities; + + var defs = getChangeDetectorDefinitions(viewDefEntry.hostMetadata, + result.protoView, viewDefEntry.viewDef.directives); + for (var i = 0; i < defs.length; ++i) { + changeDetectorClasses.generate( + '_${rType.typeName}_ChangeDetector$i', defs[i]); + } + + // Check that getters, setters, methods are the same as above. + assert(recordingCapabilities.getterNames + .containsAll(result.recording.getterNames)); + assert(result.recording.getterNames + .containsAll(recordingCapabilities.getterNames)); + assert(recordingCapabilities.setterNames + .containsAll(result.recording.setterNames)); + assert(result.recording.setterNames + .containsAll(recordingCapabilities.setterNames)); + assert(recordingCapabilities.methodNames + .containsAll(result.recording.methodNames)); + assert(result.recording.methodNames + .containsAll(recordingCapabilities.methodNames)); + + reflector.reflectionCapabilities = savedReflectionCapabilities; } } var code = viewDefResults.ngDeps.code; - if (registrations.length == 0) return code; + if (registrations.isEmpty && changeDetectorClasses.isEmpty) return code; + var importInjectIdx = + viewDefResults.ngDeps.lib != null ? viewDefResults.ngDeps.lib.end : 0; var codeInjectIdx = viewDefResults.ngDeps.registeredTypes.last.registerMethod.end; - return '${code.substring(0, codeInjectIdx)}' + return '${code.substring(0, importInjectIdx)}' + '${changeDetectorClasses.imports}' + '${code.substring(importInjectIdx, codeInjectIdx)}' '${registrations}' - '${code.substring(codeInjectIdx)}'; -} - -Iterable _generateGetters(Iterable getterNames) { - return getterNames.map((getterName) { - if (!prop.isValid(getterName)) { - // TODO(kegluenq): Eagerly throw here once #1295 is addressed. - return prop.lazyInvalidGetter(getterName); - } else { - return ''' '${prop.sanitize(getterName)}': (o) => o.$getterName'''; - } - }); -} - -Iterable _generateSetters(Iterable setterName) { - return setterName.map((setterName) { - if (!prop.isValid(setterName)) { - // TODO(kegluenq): Eagerly throw here once #1295 is addressed. - return prop.lazyInvalidSetter(setterName); - } else { - return ''' '${prop.sanitize(setterName)}': ''' - ''' (o, v) => o.$setterName = v '''; - } - }); -} - -Iterable _generateMethods(Iterable methodNames) { - return methodNames.map((methodName) { - if (!prop.isValid(methodName)) { - // TODO(kegluenq): Eagerly throw here once #1295 is addressed. - return prop.lazyInvalidMethod(methodName); - } else { - return ''' '${prop.sanitize(methodName)}': ''' - '(o, List args) => Function.apply(o.$methodName, args) '; - } - }); + '${code.substring(codeInjectIdx)}' + '$changeDetectorClasses'; } /// Extracts `template` and `url` values from `View` annotations, reads @@ -104,8 +94,7 @@ class _TemplateExtractor { new CompileStepFactory(new ng.Parser(new ng.Lexer())), new TemplateLoader(xhr, new UrlResolver())); - Future extractTemplates( - ViewDefinition viewDef) async { + Future<_ExtractResult> extractTemplates(ViewDefinition viewDef) async { // Check for "imperative views". if (viewDef.template == null && viewDef.absUrl == null) return null; @@ -114,9 +103,16 @@ class _TemplateExtractor { reflector.reflectionCapabilities = recordingCapabilities; // TODO(kegluneq): Rewrite url to inline `template` where possible. - await _compiler.compile(viewDef); + var protoViewDto = await _compiler.compile(viewDef); reflector.reflectionCapabilities = savedReflectionCapabilities; - return recordingCapabilities; + return new _ExtractResult(recordingCapabilities, protoViewDto); } } + +class _ExtractResult { + final RecordingReflectionCapabilities recording; + final ProtoViewDto protoView; + + _ExtractResult(this.recording, this.protoView); +} diff --git a/modules/angular2/src/transform/template_compiler/reflector_register_codegen.dart b/modules/angular2/src/transform/template_compiler/reflector_register_codegen.dart new file mode 100644 index 0000000000..8f928891e9 --- /dev/null +++ b/modules/angular2/src/transform/template_compiler/reflector_register_codegen.dart @@ -0,0 +1,69 @@ +library angular2.transform.template_compiler.reflector_register_codegen; + +import 'package:angular2/src/transform/common/names.dart'; +import 'package:angular2/src/transform/common/property_utils.dart' as prop; +import 'recording_reflection_capabilities.dart'; + +class Codegen { + final StringBuffer _buf = new StringBuffer(); + + void generate(RecordingReflectionCapabilities recording) { + if (recording != null) { + var calls = _generateGetters(recording.getterNames); + if (calls.isNotEmpty) { + _buf.write('..${REGISTER_GETTERS_METHOD_NAME}' + '({${calls.join(', ')}})'); + } + calls = _generateSetters(recording.setterNames); + if (calls.isNotEmpty) { + _buf.write('..${REGISTER_SETTERS_METHOD_NAME}' + '({${calls.join(', ')}})'); + } + calls = _generateMethods(recording.methodNames); + if (calls.isNotEmpty) { + _buf.write('..${REGISTER_METHODS_METHOD_NAME}' + '({${calls.join(', ')}})'); + } + } + } + + bool get isEmpty => _buf.isEmpty; + + @override + String toString() => '$_buf'; +} + +Iterable _generateGetters(Iterable getterNames) { + return getterNames.map((getterName) { + if (!prop.isValid(getterName)) { + // TODO(kegluenq): Eagerly throw here once #1295 is addressed. + return prop.lazyInvalidGetter(getterName); + } else { + return ''' '${prop.sanitize(getterName)}': (o) => o.$getterName'''; + } + }); +} + +Iterable _generateSetters(Iterable setterName) { + return setterName.map((setterName) { + if (!prop.isValid(setterName)) { + // TODO(kegluenq): Eagerly throw here once #1295 is addressed. + return prop.lazyInvalidSetter(setterName); + } else { + return ''' '${prop.sanitize(setterName)}': ''' + ''' (o, v) => o.$setterName = v '''; + } + }); +} + +Iterable _generateMethods(Iterable methodNames) { + return methodNames.map((methodName) { + if (!prop.isValid(methodName)) { + // TODO(kegluenq): Eagerly throw here once #1295 is addressed. + return prop.lazyInvalidMethod(methodName); + } else { + return ''' '${prop.sanitize(methodName)}': ''' + '(o, List args) => Function.apply(o.$methodName, args) '; + } + }); +} diff --git a/modules/angular2/src/transform/template_compiler/view_definition_creator.dart b/modules/angular2/src/transform/template_compiler/view_definition_creator.dart index 5db7b34266..e388aef4b2 100644 --- a/modules/angular2/src/transform/template_compiler/view_definition_creator.dart +++ b/modules/angular2/src/transform/template_compiler/view_definition_creator.dart @@ -22,10 +22,17 @@ Future createViewDefinitions( class ViewDefinitionResults { final NgDeps ngDeps; - final Map viewDefinitions; + final Map viewDefinitions; ViewDefinitionResults._(this.ngDeps, this.viewDefinitions); } +class ViewDefinitionEntry { + final DirectiveMetadata hostMetadata; + final ViewDefinition viewDef; + + ViewDefinitionEntry._(this.hostMetadata, this.viewDef); +} + String _getComponentId(AssetId assetId, String className) => '$assetId:$className'; @@ -48,21 +55,24 @@ class _ViewDefinitionCreator { Future createViewDefs() async { var ngDeps = await ngDepsFuture; - var retVal = {}; + var retVal = {}; var visitor = new _TemplateExtractVisitor(await _createMetadataMap()); ngDeps.registeredTypes.forEach((rType) { visitor.reset(); rType.annotations.accept(visitor); if (visitor.viewDef != null) { var typeName = '${rType.typeName}'; + var hostMetadata = null; if (visitor._metadataMap.containsKey(typeName)) { - visitor.viewDef.componentId = visitor._metadataMap[typeName].id; + hostMetadata = visitor._metadataMap[typeName]; + visitor.viewDef.componentId = hostMetadata.id; } else { - logger.warning('Missing component "$typeName" in metadata map', + logger.error('Missing component "$typeName" in metadata map', asset: entryPoint); visitor.viewDef.componentId = _getComponentId(entryPoint, typeName); } - retVal[rType] = visitor.viewDef; + retVal[rType] = + new ViewDefinitionEntry._(hostMetadata, visitor.viewDef); } }); return new ViewDefinitionResults._(ngDeps, retVal); diff --git a/modules/angular2/test/change_detection/change_detector_spec.ts b/modules/angular2/test/change_detection/change_detector_spec.ts index 7414291f82..8df3cb0fee 100644 --- a/modules/angular2/test/change_detection/change_detector_spec.ts +++ b/modules/angular2/test/change_detection/change_detector_spec.ts @@ -44,8 +44,120 @@ import { ProtoChangeDetector } from 'angular2/change_detection'; +import {getDefinition} from './simple_watch_config'; +import {getFactoryById} from './generated/simple_watch_classes'; + export function main() { + // These tests also run against pre-generated Dart Change Detectors. We will move tests up from + // the loop below as they are converted. + ListWrapper.forEach(['dynamic', 'JIT', 'Pregen'], (cdType) => { + + if (cdType == "JIT" && IS_DARTIUM) return; + if (cdType == "Pregen" && !IS_DARTIUM) return; + + describe(`${cdType} Change Detector`, () => { + + function _getProtoChangeDetector(def: ChangeDetectorDefinition) { + var registry = null; + switch (cdType) { + case 'dynamic': + return new DynamicProtoChangeDetector(registry, def); + case 'JIT': + return new JitProtoChangeDetector(registry, def); + case 'Pregen': + return getFactoryById(def.id)(registry, def); + default: + return null; + } + } + + function _bindConstValue(expression: string) { + var dispatcher = new TestDispatcher(); + var protoCd = _getProtoChangeDetector(getDefinition(expression, 'propName')); + var cd = protoCd.instantiate(dispatcher); + + var context = null; + var locals = null; + cd.hydrate(context, locals, null); + cd.detectChanges(); + return dispatcher.log; + } + + it('should support literals', + () => { expect(_bindConstValue('10')).toEqual(['propName=10']); }); + it('should strip quotes from literals', + () => { expect(_bindConstValue('"str"')).toEqual(['propName=str']); }); + it('should support newlines in literals', + () => { expect(_bindConstValue('"a\n\nb"')).toEqual(['propName=a\n\nb']); }); + it('should support + operations', + () => { expect(_bindConstValue('10 + 2')).toEqual(['propName=12']); }); + it('should support - operations', + () => { expect(_bindConstValue('10 - 2')).toEqual(['propName=8']); }); + it('should support * operations', + () => { expect(_bindConstValue('10 * 2')).toEqual(['propName=20']); }); + it('should support / operations', () => { + expect(_bindConstValue('10 / 2')).toEqual([`propName=${5.0}`]); + }); // dart exp=5.0, js exp=5 + it('should support % operations', + () => { expect(_bindConstValue('11 % 2')).toEqual(['propName=1']); }); + it('should support == operations on identical', + () => { expect(_bindConstValue('1 == 1')).toEqual(['propName=true']); }); + it('should support != operations', + () => { expect(_bindConstValue('1 != 1')).toEqual(['propName=false']); }); + it('should support == operations on coerceible', () => { + var expectedValue = IS_DARTIUM ? 'false' : 'true'; + expect(_bindConstValue('1 == true')).toEqual([`propName=${expectedValue}`]); + }); + it('should support === operations on identical', + () => { expect(_bindConstValue('1 === 1')).toEqual(['propName=true']); }); + it('should support !== operations', + () => { expect(_bindConstValue('1 !== 1')).toEqual(['propName=false']); }); + it('should support === operations on coerceible', + () => { expect(_bindConstValue('1 === true')).toEqual(['propName=false']); }); + it('should support true < operations', + () => { expect(_bindConstValue('1 < 2')).toEqual(['propName=true']); }); + it('should support false < operations', + () => { expect(_bindConstValue('2 < 1')).toEqual(['propName=false']); }); + it('should support false > operations', + () => { expect(_bindConstValue('1 > 2')).toEqual(['propName=false']); }); + it('should support true > operations', + () => { expect(_bindConstValue('2 > 1')).toEqual(['propName=true']); }); + it('should support true <= operations', + () => { expect(_bindConstValue('1 <= 2')).toEqual(['propName=true']); }); + it('should support equal <= operations', + () => { expect(_bindConstValue('2 <= 2')).toEqual(['propName=true']); }); + it('should support false <= operations', + () => { expect(_bindConstValue('2 <= 1')).toEqual(['propName=false']); }); + it('should support true >= operations', + () => { expect(_bindConstValue('2 >= 1')).toEqual(['propName=true']); }); + it('should support equal >= operations', + () => { expect(_bindConstValue('2 >= 2')).toEqual(['propName=true']); }); + it('should support false >= operations', + () => { expect(_bindConstValue('1 >= 2')).toEqual(['propName=false']); }); + it('should support true && operations', + () => { expect(_bindConstValue('true && true')).toEqual(['propName=true']); }); + it('should support false && operations', + () => { expect(_bindConstValue('true && false')).toEqual(['propName=false']); }); + it('should support true || operations', + () => { expect(_bindConstValue('true || false')).toEqual(['propName=true']); }); + it('should support false || operations', + () => { expect(_bindConstValue('false || false')).toEqual(['propName=false']); }); + it('should support negate', + () => { expect(_bindConstValue('!true')).toEqual(['propName=false']); }); + it('should support double negate', + () => { expect(_bindConstValue('!!true')).toEqual(['propName=true']); }); + it('should support true conditionals', + () => { expect(_bindConstValue('1 < 2 ? 1 : 2')).toEqual(['propName=1']); }); + it('should support false conditionals', + () => { expect(_bindConstValue('1 > 2 ? 1 : 2')).toEqual(['propName=2']); }); + it('should support keyed access to a list item', + () => { expect(_bindConstValue('["foo", "bar"][0]')).toEqual(['propName=foo']); }); + it('should support keyed access to a map item', + () => { expect(_bindConstValue('{"foo": "bar"}["foo"]')).toEqual(['propName=bar']); }); + }); + }); + describe("change detection", () => { StringMapWrapper.forEach( { @@ -145,12 +257,6 @@ export function main() { expect(executeWatch('a', 'a', td)).toEqual(['a=null']); }); - it("should support literals", () => { - expect(executeWatch('const', '10')).toEqual(['const=10']); - expect(executeWatch('const', '"str"')).toEqual(['const=str']); - expect(executeWatch('const', '"a\n\nb"')).toEqual(['const=a\n\nb']); - }); - it('should support simple chained property access', () => { var address = new Address('Grenoble'); var person = new Person('Victor', address); @@ -207,68 +313,6 @@ export function main() { expect(c["dispatcher"].loggedValues[0]['z']).toEqual(1); }); - it("should support binary operations", () => { - expect(executeWatch('exp', '10 + 2')).toEqual(['exp=12']); - expect(executeWatch('exp', '10 - 2')).toEqual(['exp=8']); - - expect(executeWatch('exp', '10 * 2')).toEqual(['exp=20']); - expect(executeWatch('exp', '10 / 2')) - .toEqual([`exp=${5.0}`]); // dart exp=5.0, js exp=5 - expect(executeWatch('exp', '11 % 2')).toEqual(['exp=1']); - - expect(executeWatch('exp', '1 == 1')).toEqual(['exp=true']); - if (IS_DARTIUM) { - expect(executeWatch('exp', '1 == "1"')).toEqual(['exp=false']); - } else { - expect(executeWatch('exp', '1 == "1"')).toEqual(['exp=true']); - } - expect(executeWatch('exp', '1 != 1')).toEqual(['exp=false']); - - expect(executeWatch('exp', '1 === 1')).toEqual(['exp=true']); - expect(executeWatch('exp', '1 !== 1')).toEqual(['exp=false']); - expect(executeWatch('exp', '1 === "1"')).toEqual(['exp=false']); - - expect(executeWatch('exp', '1 < 2')).toEqual(['exp=true']); - expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']); - - expect(executeWatch('exp', '2 > 1')).toEqual(['exp=true']); - expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']); - - expect(executeWatch('exp', '1 <= 2')).toEqual(['exp=true']); - expect(executeWatch('exp', '2 <= 2')).toEqual(['exp=true']); - expect(executeWatch('exp', '2 <= 1')).toEqual(['exp=false']); - - expect(executeWatch('exp', '2 >= 1')).toEqual(['exp=true']); - expect(executeWatch('exp', '2 >= 2')).toEqual(['exp=true']); - expect(executeWatch('exp', '1 >= 2')).toEqual(['exp=false']); - - expect(executeWatch('exp', 'true && true')).toEqual(['exp=true']); - expect(executeWatch('exp', 'true && false')).toEqual(['exp=false']); - - expect(executeWatch('exp', 'true || false')).toEqual(['exp=true']); - expect(executeWatch('exp', 'false || false')).toEqual(['exp=false']); - }); - - it("should support negate", () => { - expect(executeWatch('exp', '!true')).toEqual(['exp=false']); - expect(executeWatch('exp', '!!true')).toEqual(['exp=true']); - }); - - it("should support conditionals", () => { - expect(executeWatch('m', '1 < 2 ? 1 : 2')).toEqual(['m=1']); - expect(executeWatch('m', '1 > 2 ? 1 : 2')).toEqual(['m=2']); - }); - - describe("keyed access", () => { - it("should support accessing a list item", () => { - expect(executeWatch('array[0]', '["foo", "bar"][0]')).toEqual(['array[0]=foo']); - }); - - it("should support accessing a map item", () => { - expect(executeWatch('map[foo]', '{"foo": "bar"}["foo"]')).toEqual(['map[foo]=bar']); - }); - }); - it("should support interpolation", () => { var ast = parser.parseInterpolation("B{{a}}A", "location"); var pcd = createProtoChangeDetector([BindingRecord.createForElement(ast, 0, "memo")]); diff --git a/modules/angular2/test/change_detection/generated/simple_watch_classes.ts b/modules/angular2/test/change_detection/generated/simple_watch_classes.ts new file mode 100644 index 0000000000..6db6ddaa64 --- /dev/null +++ b/modules/angular2/test/change_detection/generated/simple_watch_classes.ts @@ -0,0 +1,5 @@ +// Ignore me, needed to support Angular 2 Dart. + +export function getFactoryById(id: string) { + return null; +} diff --git a/modules/angular2/test/change_detection/generator/gen_change_detectors.dart b/modules/angular2/test/change_detection/generator/gen_change_detectors.dart new file mode 100644 index 0000000000..16a67e073e --- /dev/null +++ b/modules/angular2/test/change_detection/generator/gen_change_detectors.dart @@ -0,0 +1,36 @@ +library angular2.src.transform.di_transformer; + +import 'dart:convert'; +import 'dart:io'; + +import 'package:dart_style/dart_style.dart'; +import 'package:angular2/src/transform/template_compiler/change_detector_codegen.dart'; +import '../simple_watch_config.dart'; + +void main(List args) { + var buf = new StringBuffer('var $_MAP_NAME = {'); + var codegen = new Codegen(); + var allDefs = getAllDefinitions('propName'); + for (var i = 0; i < allDefs.length; ++i) { + var className = 'ChangeDetector${i}'; + codegen.generate(className, allDefs[i]); + if (i > 0) { + buf.write(','); + } + buf.write(" '''${allDefs[i].id}''': " + "$className.$PROTO_CHANGE_DETECTOR_FACTORY_METHOD"); + } + buf.write('};'); + print(new DartFormatter().format(''' + library dart_gen_change_detectors; + + ${codegen.imports} + + $codegen + $buf + + getFactoryById(String id) => $_MAP_NAME[id]; + ''')); +} + +const _MAP_NAME = '_idToProtoMap'; diff --git a/modules/angular2/test/change_detection/simple_watch_config.ts b/modules/angular2/test/change_detection/simple_watch_config.ts new file mode 100644 index 0000000000..7f58b9c137 --- /dev/null +++ b/modules/angular2/test/change_detection/simple_watch_config.ts @@ -0,0 +1,72 @@ +import {ListWrapper} from 'angular2/src/facade/collection'; +import {BindingRecord, ChangeDetectorDefinition, Lexer, Parser} from 'angular2/change_detection'; + +var _parser = new Parser(new Lexer()); + +function _createChangeDetectorDefinition(id: string, propName: string, + expression: string): ChangeDetectorDefinition { + var ast = _parser.parseBinding(expression, 'location'); + var bindingRecords = [BindingRecord.createForElement(ast, 0, propName)]; + + var strategy = null; + var variableBindings = []; + var directiveRecords = []; + return new ChangeDetectorDefinition(id, strategy, variableBindings, bindingRecords, + directiveRecords); +} + +/** + * In this case, we expect `id` and `expression` to be the same string. + */ +export function getDefinition(id: string, propName: string): ChangeDetectorDefinition { + // TODO(kegluneq): Remove `propName`? + if (ListWrapper.indexOf(_availableDefinitions, id) < 0) { + throw `No ChangeDetectorDefinition for ${id} available. Please modify this file if necessary.`; + } + return _createChangeDetectorDefinition(id, propName, id); +} + +/** + * Get all available ChangeDetectorDefinition objects. Used to pre-generate Dart + * `ChangeDetector` classes. + */ +export function getAllDefinitions(propName: string): List { + return ListWrapper.map(_availableDefinitions, (id) => getDefinition(id, propName)); +} + +var _availableDefinitions = [ + '10', + '"str"', + '"a\n\nb"', + '10 + 2', + '10 - 2', + '10 * 2', + '10 / 2', + '11 % 2', + '1 == 1', + '1 != 1', + '1 == true', + '1 === 1', + '1 !== 1', + '1 === true', + '1 < 2', + '2 < 1', + '1 > 2', + '2 > 1', + '1 <= 2', + '2 <= 2', + '2 <= 1', + '2 >= 1', + '2 >= 2', + '1 >= 2', + 'true && true', + 'true && false', + 'true || false', + 'false || false', + '!true', + '!!true', + '1 < 2 ? 1 : 2', + '1 > 2 ? 1 : 2', + '["foo", "bar"][0]', + '{"foo": "bar"}["foo"]' +]; diff --git a/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart b/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart index 9dd8bbd021..dfcb5996fe 100644 --- a/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart +++ b/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart @@ -1,5 +1,8 @@ library bar.ng_deps.dart; +import 'package:angular2/src/change_detection/pregen_proto_change_detector.dart' + as _gen; + import 'bar.dart'; import 'package:angular2/src/core/annotations_impl/annotations.dart'; import 'package:angular2/src/core/annotations_impl/view.dart'; @@ -18,3 +21,48 @@ void initReflector(reflector) { ] }); } +class _MyComponent_ChangeDetector0 extends _gen.AbstractChangeDetector { + final dynamic _dispatcher; + final _gen.PipeRegistry _pipeRegistry; + final _gen.List<_gen.ProtoRecord> _protos; + final _gen.List<_gen.DirectiveRecord> _directiveRecords; + dynamic _locals = null; + dynamic _context = _gen.ChangeDetectionUtil.uninitialized(); + + _MyComponent_ChangeDetector0(this._dispatcher, this._pipeRegistry, + this._protos, this._directiveRecords) + : super(); + + void detectChangesInRecords(throwOnChange) { + var context = null; + var change_context = false; + var isChanged = false; + var currentProto; + var changes = null; + + context = _context; + } + + void callOnAllChangesDone() {} + + void hydrate(context, locals, directives) { + mode = 'ALWAYS_CHECK'; + _context = context; + _locals = locals; + } + + void dehydrate() { + _context = _gen.ChangeDetectionUtil.uninitialized(); + _locals = null; + } + + hydrated() => + !_gen.looseIdentical(_context, _gen.ChangeDetectionUtil.uninitialized()); + + static _gen.ProtoChangeDetector newProtoChangeDetector( + _gen.PipeRegistry registry, _gen.ChangeDetectorDefinition def) { + return new _gen.PregenProtoChangeDetector( + (a, b, c, d) => new _MyComponent_ChangeDetector0(a, b, c, d), registry, + def); + } +} diff --git a/modules/angular2/test/transform/template_compiler/all_tests.dart b/modules/angular2/test/transform/template_compiler/all_tests.dart index 043258ee60..de69ac94e5 100644 --- a/modules/angular2/test/transform/template_compiler/all_tests.dart +++ b/modules/angular2/test/transform/template_compiler/all_tests.dart @@ -1,5 +1,6 @@ library angular2.test.transform.template_compiler.all_tests; +import 'dart:async'; import 'package:barback/barback.dart'; import 'package:angular2/src/dom/html_adapter.dart'; import 'package:angular2/src/transform/common/asset_reader.dart'; @@ -18,71 +19,79 @@ void allTests() { beforeEach(() => setLogger(new PrintLogger())); - it('should parse simple expressions in inline templates.', () async { - var inputPath = - 'template_compiler/inline_expression_files/hello.ng_deps.dart'; - var expected = readFile( - 'template_compiler/inline_expression_files/expected/hello.ng_deps.dart'); - var output = await processTemplates(reader, new AssetId('a', inputPath)); - _formatThenExpectEquals(output, expected); - }); + describe('registrations', () { + Future process(AssetId assetId) => + processTemplates(reader, assetId, generateChangeDetectors: false); - it('should parse simple methods in inline templates.', () async { - var inputPath = 'template_compiler/inline_method_files/hello.ng_deps.dart'; - var expected = readFile( - 'template_compiler/inline_method_files/expected/hello.ng_deps.dart'); - var output = await processTemplates(reader, new AssetId('a', inputPath)); - _formatThenExpectEquals(output, expected); - }); + it('should parse simple expressions in inline templates.', () async { + var inputPath = + 'template_compiler/inline_expression_files/hello.ng_deps.dart'; + var expected = readFile( + 'template_compiler/inline_expression_files/expected/hello.ng_deps.dart'); + var output = await process(new AssetId('a', inputPath)); + _formatThenExpectEquals(output, expected); + }); - it('should parse simple expressions in linked templates.', () async { - var inputPath = 'template_compiler/url_expression_files/hello.ng_deps.dart'; - var expected = readFile( - 'template_compiler/url_expression_files/expected/hello.ng_deps.dart'); - var output = await processTemplates(reader, new AssetId('a', inputPath)); - _formatThenExpectEquals(output, expected); - }); + it('should parse simple methods in inline templates.', () async { + var inputPath = + 'template_compiler/inline_method_files/hello.ng_deps.dart'; + var expected = readFile( + 'template_compiler/inline_method_files/expected/hello.ng_deps.dart'); + var output = await process(new AssetId('a', inputPath)); + _formatThenExpectEquals(output, expected); + }); - it('should parse simple methods in linked templates.', () async { - var inputPath = 'template_compiler/url_method_files/hello.ng_deps.dart'; - var expected = readFile( - 'template_compiler/url_method_files/expected/hello.ng_deps.dart'); - var output = await processTemplates(reader, new AssetId('a', inputPath)); - _formatThenExpectEquals(output, expected); - }); + it('should parse simple expressions in linked templates.', () async { + var inputPath = + 'template_compiler/url_expression_files/hello.ng_deps.dart'; + var expected = readFile( + 'template_compiler/url_expression_files/expected/hello.ng_deps.dart'); + var output = await process(new AssetId('a', inputPath)); + _formatThenExpectEquals(output, expected); + }); - it('should not generated duplicate getters/setters', () async { - var inputPath = 'template_compiler/duplicate_files/hello.ng_deps.dart'; - var expected = readFile( - 'template_compiler/duplicate_files/expected/hello.ng_deps.dart'); - var output = await processTemplates(reader, new AssetId('a', inputPath)); - _formatThenExpectEquals(output, expected); - }); + it('should parse simple methods in linked templates.', () async { + var inputPath = 'template_compiler/url_method_files/hello.ng_deps.dart'; + var expected = readFile( + 'template_compiler/url_method_files/expected/hello.ng_deps.dart'); + var output = await process(new AssetId('a', inputPath)); + _formatThenExpectEquals(output, expected); + }); - it('should parse `View` directives with a single dependency.', () async { - var inputPath = 'template_compiler/one_directive_files/hello.ng_deps.dart'; - var expected = readFile( - 'template_compiler/one_directive_files/expected/hello.ng_deps.dart'); + it('should not generated duplicate getters/setters', () async { + var inputPath = 'template_compiler/duplicate_files/hello.ng_deps.dart'; + var expected = readFile( + 'template_compiler/duplicate_files/expected/hello.ng_deps.dart'); + var output = await process(new AssetId('a', inputPath)); + _formatThenExpectEquals(output, expected); + }); - var output = await processTemplates(reader, new AssetId('a', inputPath)); - _formatThenExpectEquals(output, expected); - }); + it('should parse `View` directives with a single dependency.', () async { + var inputPath = + 'template_compiler/one_directive_files/hello.ng_deps.dart'; + var expected = readFile( + 'template_compiler/one_directive_files/expected/hello.ng_deps.dart'); - it('should parse `View` directives with a single prefixed dependency.', - () async { - var inputPath = 'template_compiler/with_prefix_files/hello.ng_deps.dart'; - var expected = readFile( - 'template_compiler/with_prefix_files/expected/hello.ng_deps.dart'); + var output = await process(new AssetId('a', inputPath)); + _formatThenExpectEquals(output, expected); + }); - var output = await processTemplates(reader, new AssetId('a', inputPath)); - _formatThenExpectEquals(output, expected); + it('should parse `View` directives with a single prefixed dependency.', + () async { + var inputPath = 'template_compiler/with_prefix_files/hello.ng_deps.dart'; + var expected = readFile( + 'template_compiler/with_prefix_files/expected/hello.ng_deps.dart'); - inputPath = 'template_compiler/with_prefix_files/goodbye.ng_deps.dart'; - expected = readFile( - 'template_compiler/with_prefix_files/expected/goodbye.ng_deps.dart'); + var output = await process(new AssetId('a', inputPath)); + _formatThenExpectEquals(output, expected); - output = await processTemplates(reader, new AssetId('a', inputPath)); - _formatThenExpectEquals(output, expected); + inputPath = 'template_compiler/with_prefix_files/goodbye.ng_deps.dart'; + expected = readFile( + 'template_compiler/with_prefix_files/expected/goodbye.ng_deps.dart'); + + output = await process(new AssetId('a', inputPath)); + _formatThenExpectEquals(output, expected); + }); }); } diff --git a/tools/build/dartanalyzer.js b/tools/build/dartanalyzer.js index 2e2b08177f..9c87feb770 100644 --- a/tools/build/dartanalyzer.js +++ b/tools/build/dartanalyzer.js @@ -46,11 +46,11 @@ module.exports = function(gulp, plugins, config) { function analyze(dirName, done) { //TODO remove --package-warnings once dartanalyzer handles transitive libraries - var args = ['--fatal-warnings', '--package-warnings'].concat(tempFile); + var args = ['--fatal-warnings', '--package-warnings', '--format=machine'].concat(tempFile); var stream = spawn(config.command, args, { // inherit stdin and stderr, but filter stdout - stdio: [process.stdin, 'pipe', process.stderr], + stdio: [process.stdin, process.stdout, 'pipe'], cwd: dirName }); // Filter out unused imports from our generated file. @@ -58,7 +58,7 @@ module.exports = function(gulp, plugins, config) { // as this could lead to name clashes when two files // export the same thing. var rl = readline.createInterface({ - input: stream.stdout, + input: stream.stderr, output: process.stdout, terminal: false }); @@ -66,32 +66,29 @@ module.exports = function(gulp, plugins, config) { var errorCount = 0; var warningCount = 0; rl.on('line', function(line) { - //TODO remove once dartanalyzer handles transitive libraries - //skip errors in third-party packages - if (line.indexOf(dirName) == -1) { + var parsedLine = _AnalyzerOutputLine.parse(line); + if (!parsedLine) { + errorCount++; + console.log('Unexpected output: ' + line); return; } - if (line.match(/Unused import/)) { - if (line.match(/_analyzer\.dart/)) { - return; - } + //TODO remove once dartanalyzer handles transitive libraries + //skip errors in third-party packages + if (parsedLine.source.indexOf(dirName) == -1) { + return; } - // TODO: https://github.com/angular/ts2dart/issues/168 - if (line.match(/_stack' is not used/)) { + if (parsedLine.shouldIgnore()) { return; } - var skip = false; - if (!skip) { - if (line.match(/\[hint\]/)) { - hintCount++; - } else if (line.match(/\[warning\]/)) { - warningCount++; - } else { - errorCount ++; - } + if (parsedLine.isHint) { + hintCount++; + } else if (parsedLine.isWarning) { + warningCount++; + } else { + errorCount ++; } - console.log(dirName + ':' + line); + console.log(dirName + ':' + parsedLine); }); stream.on('close', function() { var error; @@ -113,3 +110,58 @@ module.exports = function(gulp, plugins, config) { } }; }; + +// See https://github.com/dart-lang/analyzer_cli/blob/master/lib/src/error_formatter.dart +function _AnalyzerOutputLine(result) { + this.severity = result[1]; + this.errorType = result[2]; + this.errorCode = result[3]; + this.source = result[4]; + this.lineNum = result[5]; + this.colNum = result[6]; + this.errorMsg = result[7]; + + this.isError = Boolean(this.severity.match(/ERROR/i)); + this.isHint = Boolean(this.severity.match(/INFO/i)); + this.isWarning = Boolean(this.severity.match(/WARNING/i)); +} + +_AnalyzerOutputLine.parse = function(line) { + var result = _AnalyzerOutputLine._analyzerParseRegExp.exec(line); + return result ? new _AnalyzerOutputLine(result) : null; +}; + +_AnalyzerOutputLine._analyzerParseRegExp = new RegExp( + '([^\|]+)\\|' + // #1, severity (NONE, INFO, WARNING, ERROR) + '([^\|]+)\\|' + // #2, errorCode.type (HINT, *_WARNING, *_ERROR, etc) + '([^\|]+)\\|' + // #3, errorCode (UNUSED_IMPORT, UNUSED_CATCH_STACK, etc) + '([^\|]+)\\|' + // #4, source path + '([^\|]+)\\|' + // #5, line number + '([^\|]+)\\|' + // #6, column number + '[^\|]+\\|' + // length of the ASCII line to draw (ignored) + '(.*)$'); // #7, error message + +_AnalyzerOutputLine.prototype = { + toString: function() { + return '[' + this.errorCode + '] ' + this.errorMsg + + ' (' + this.source + ', line ' + this.lineNum + ', col ' + this.colNum + ')'; + }, + + shouldIgnore: function() { + if (this.errorCode.match(/UNUSED_IMPORT/i)) { + if (this.source.match(/_analyzer\.dart/)) { + return true; + } + } + // TODO: https://github.com/angular/ts2dart/issues/168 + if (this.errorCode.match(/UNUSED_CATCH_STACK/i)) { + return true; + } + + // Don't worry about hints in generated files. + if (this.isHint && this.source.match(/generated/i)) { + return true; + } + return false; + } +}; diff --git a/tools/build/dartdetect.js b/tools/build/dartdetect.js index 9bd22c2f2b..a8813fd91b 100644 --- a/tools/build/dartdetect.js +++ b/tools/build/dartdetect.js @@ -7,15 +7,17 @@ module.exports = function(gulp) { console.log('Dart SDK detected'); if (process.platform === 'win32') { DART_SDK = { - PUB: 'pub.bat', ANALYZER: 'dartanalyzer.bat', - DARTFMT: 'dartfmt.bat' + DARTFMT: 'dartfmt.bat', + PUB: 'pub.bat', + VM: 'dart.bat' }; } else { DART_SDK = { - PUB: 'pub', ANALYZER: 'dartanalyzer', - DARTFMT: 'dartfmt' + DARTFMT: 'dartfmt', + PUB: 'pub', + VM: 'dart' }; } } catch (e) {