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

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