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:
@ -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";
|
||||
|
@ -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);
|
||||
}
|
@ -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';
|
@ -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);
|
||||
}
|
||||
|
@ -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) ';
|
||||
}
|
||||
});
|
||||
}
|
@ -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);
|
||||
|
Reference in New Issue
Block a user