feat(dart/transform): Generate ChangeDetector classes

Use the `ProtoViewDto` created by the render `Compiler` to create a
`ChangeDetectorDefinition`.

From there, generate a subclass of `AbstractChangeDetector` for each
`ChangeDetectorDefinition`.

Run some basic unit tests for the dynamic and JIT change detectors on
pre-generated change detectors.
This commit is contained in:
Tim Blasi 2015-05-14 13:14:45 -07:00
parent 383f0a1f30
commit 8a3b0b366f
15 changed files with 1105 additions and 217 deletions

View File

@ -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

View File

@ -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";

View File

@ -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<ProtoRecord> protoRecords,
List<DirectiveRecord> 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<ProtoRecord> _protoRecords;
final List<DirectiveRecord> _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);
}

View File

@ -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<ProtoRecord> _records;
final List<DirectiveRecord> _directiveRecords;
final List<String> _localNames;
final List<String> _changeNames;
final List<String> _fieldNames;
final List<String> _pipeNames;
_CodegenState._(this._typeName, String changeDetectionStrategy, this._records,
this._directiveRecords, List<String> 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<String> _getLocalNames(List<ProtoRecord> records) {
var localNames = new List<String>(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<String> _getChangeNames(List<String> localNames) =>
localNames.map((name) => 'change_$name').toList();
/// Generates names for use as private fields.
static List<String> _getFieldNames(List<String> localNames) =>
localNames.map((name) => '_$name').toList();
/// Generates names for use as private pipe variables.
static List<String> _getPipeNames(List<String> 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<String> _genGetDirectiveFieldNames() {
return _directiveRecords
.map((d) => _genGetDirective(d.directiveIndex))
.toList();
}
List<String> _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<String> _getNonNullPipeNames() {
return _records
.where((r) =>
r.mode == RECORD_TYPE_PIPE || r.mode == RECORD_TYPE_BINDING_PIPE)
.map((r) => _pipeNames[r.selfIndex])
.toList();
}
List<String> _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';

View File

@ -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<String> processTemplates(AssetReader reader, AssetId entryPoint) async {
Future<String> 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<String> _generateGetters(Iterable<String> 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<String> _generateSetters(Iterable<String> 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<String> _generateMethods(Iterable<String> 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<RecordingReflectionCapabilities> 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);
}

View File

@ -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<String> _generateGetters(Iterable<String> 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<String> _generateSetters(Iterable<String> 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<String> _generateMethods(Iterable<String> 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) ';
}
});
}

View File

@ -22,10 +22,17 @@ Future<ViewDefinitionResults> createViewDefinitions(
class ViewDefinitionResults {
final NgDeps ngDeps;
final Map<RegisteredType, ViewDefinition> viewDefinitions;
final Map<RegisteredType, ViewDefinitionEntry> 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<ViewDefinitionResults> createViewDefs() async {
var ngDeps = await ngDepsFuture;
var retVal = <RegisteredType, ViewDefinition>{};
var retVal = <RegisteredType, ViewDefinitionEntry>{};
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);

View File

@ -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")]);

View File

@ -0,0 +1,5 @@
// Ignore me, needed to support Angular 2 Dart.
export function getFactoryById(id: string) {
return null;
}

View File

@ -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<String> 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';

View File

@ -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<ChangeDetectorDefinition> {
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"]'
];

View File

@ -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);
}
}

View File

@ -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<String> 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);
});
});
}

View File

@ -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;
}
};

View File

@ -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) {