chore(transform): move transform module to modules_dart
The build/pure-packages.dart gulp task has also been updated to move the files into the angular2 tree. Closes #3729
This commit is contained in:
@ -1,497 +0,0 @@
|
||||
library angular2.transform.template_compiler.change_detector_codegen;
|
||||
|
||||
import 'package:angular2/src/change_detection/change_detection_util.dart';
|
||||
import 'package:angular2/src/change_detection/codegen_facade.dart';
|
||||
import 'package:angular2/src/change_detection/codegen_logic_util.dart';
|
||||
import 'package:angular2/src/change_detection/codegen_name_util.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';
|
||||
import 'package:angular2/src/change_detection/event_binding.dart';
|
||||
import 'package:angular2/src/change_detection/binding_record.dart';
|
||||
import 'package:angular2/src/change_detection/codegen_facade.dart' show codify;
|
||||
import 'package:angular2/src/facade/lang.dart' show BaseException;
|
||||
|
||||
/// 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 {
|
||||
/// Stores the generated class definitions.
|
||||
final StringBuffer _buf = new StringBuffer();
|
||||
|
||||
/// Stores all generated initialization code.
|
||||
final StringBuffer _initBuf = new StringBuffer();
|
||||
|
||||
/// The names of already generated classes.
|
||||
final Set<String> _names = new Set<String>();
|
||||
|
||||
/// Generates a change detector class with name `changeDetectorTypeName`,
|
||||
/// which must not conflict with other generated classes in the same
|
||||
/// `.ng_deps.dart` file. The change detector is used to detect changes in
|
||||
/// Objects of type `typeName`.
|
||||
void generate(String typeName, String changeDetectorTypeName,
|
||||
ChangeDetectorDefinition def) {
|
||||
if (_names.contains(changeDetectorTypeName)) {
|
||||
throw new BaseException(
|
||||
'Change detector named "${changeDetectorTypeName}" for ${typeName} '
|
||||
'conflicts with an earlier generated change detector class.');
|
||||
}
|
||||
_names.add(changeDetectorTypeName);
|
||||
new _CodegenState(typeName, changeDetectorTypeName, def)
|
||||
.._writeToBuf(_buf)
|
||||
.._writeInitToBuf(_initBuf);
|
||||
}
|
||||
|
||||
/// Gets all imports necessary for the generated code.
|
||||
String get imports {
|
||||
return _buf.isEmpty
|
||||
? ''
|
||||
: '''import '$_PREGEN_PROTO_CHANGE_DETECTOR_IMPORT' as $_GEN_PREFIX;''';
|
||||
}
|
||||
|
||||
bool get isEmpty => _buf.isEmpty;
|
||||
|
||||
/// Gets the initilization code that registers the generated classes with
|
||||
/// the Angular 2 change detection system.
|
||||
String get initialize => '$_initBuf';
|
||||
|
||||
@override
|
||||
String toString() => '$_buf';
|
||||
}
|
||||
|
||||
/// The state needed to generate a change detector for a single `Component`.
|
||||
class _CodegenState {
|
||||
/// The `id` of the `ChangeDetectorDefinition` we are generating this class
|
||||
/// for.
|
||||
final String _changeDetectorDefId;
|
||||
|
||||
/// The name of the `Type` this change detector is generated for. For example,
|
||||
/// this is `MyComponent` if the generated class will detect changes in
|
||||
/// `MyComponent` objects.
|
||||
final String _contextTypeName;
|
||||
|
||||
/// The name of the generated change detector class. This is an implementation
|
||||
/// detail and should not be visible to users.
|
||||
final String _changeDetectorTypeName;
|
||||
final String _changeDetectionStrategy;
|
||||
final List<DirectiveRecord> _directiveRecords;
|
||||
final List<ProtoRecord> _records;
|
||||
final List<EventBinding> _eventBindings;
|
||||
final CodegenLogicUtil _logic;
|
||||
final CodegenNameUtil _names;
|
||||
final ChangeDetectorGenConfig _genConfig;
|
||||
final List<BindingTarget> _propertyBindingTargets;
|
||||
|
||||
_CodegenState._(
|
||||
this._changeDetectorDefId,
|
||||
this._contextTypeName,
|
||||
this._changeDetectorTypeName,
|
||||
this._changeDetectionStrategy,
|
||||
this._records,
|
||||
this._propertyBindingTargets,
|
||||
this._eventBindings,
|
||||
this._directiveRecords,
|
||||
this._logic,
|
||||
this._names,
|
||||
this._genConfig);
|
||||
|
||||
factory _CodegenState(String typeName, String changeDetectorTypeName,
|
||||
ChangeDetectorDefinition def) {
|
||||
var protoRecords = createPropertyRecords(def);
|
||||
var eventBindings = createEventRecords(def);
|
||||
var propertyBindingTargets = def.bindingRecords.map((b) => b.target).toList();
|
||||
|
||||
var names = new CodegenNameUtil(protoRecords, eventBindings, def.directiveRecords, _UTIL);
|
||||
var logic = new CodegenLogicUtil(names, _UTIL, def.strategy);
|
||||
return new _CodegenState._(
|
||||
def.id,
|
||||
typeName,
|
||||
changeDetectorTypeName,
|
||||
def.strategy,
|
||||
protoRecords,
|
||||
propertyBindingTargets,
|
||||
eventBindings,
|
||||
def.directiveRecords,
|
||||
logic,
|
||||
names,
|
||||
def.genConfig);
|
||||
}
|
||||
|
||||
void _writeToBuf(StringBuffer buf) {
|
||||
buf.write('''\n
|
||||
class $_changeDetectorTypeName extends $_BASE_CLASS<$_contextTypeName> {
|
||||
${_genDeclareFields()}
|
||||
|
||||
$_changeDetectorTypeName(dispatcher)
|
||||
: super(${codify(_changeDetectorDefId)},
|
||||
dispatcher, ${_records.length},
|
||||
${_changeDetectorTypeName}.gen_propertyBindingTargets,
|
||||
${_changeDetectorTypeName}.gen_directiveIndices,
|
||||
${codify(_changeDetectionStrategy)}) {
|
||||
dehydrateDirectives(false);
|
||||
}
|
||||
|
||||
void detectChangesInRecordsInternal(throwOnChange) {
|
||||
${_names.genInitLocals()}
|
||||
var $_IS_CHANGED_LOCAL = false;
|
||||
var $_CHANGES_LOCAL = null;
|
||||
|
||||
${_records.map(_genRecord).join('')}
|
||||
|
||||
${_names.getAlreadyCheckedName()} = true;
|
||||
}
|
||||
|
||||
${_maybeGenHandleEventInternal()}
|
||||
|
||||
${_genCheckNoChanges()}
|
||||
|
||||
${_maybeGenCallOnAllChangesDone()}
|
||||
|
||||
${_maybeGenHydrateDirectives()}
|
||||
|
||||
${_maybeGenDehydrateDirectives()}
|
||||
|
||||
${_genPropertyBindingTargets()};
|
||||
|
||||
${_genDirectiveIndices()};
|
||||
|
||||
static $_GEN_PREFIX.ProtoChangeDetector
|
||||
$PROTO_CHANGE_DETECTOR_FACTORY_METHOD(
|
||||
$_GEN_PREFIX.ChangeDetectorDefinition def) {
|
||||
return new $_GEN_PREFIX.PregenProtoChangeDetector(
|
||||
(a) => new $_changeDetectorTypeName(a),
|
||||
def);
|
||||
}
|
||||
}
|
||||
''');
|
||||
}
|
||||
|
||||
String _genPropertyBindingTargets() {
|
||||
var targets = _logic.genPropertyBindingTargets(_propertyBindingTargets, this._genConfig.genDebugInfo);
|
||||
return "static var gen_propertyBindingTargets = ${targets}";
|
||||
}
|
||||
|
||||
String _genDirectiveIndices() {
|
||||
var indices = _logic.genDirectiveIndices(_directiveRecords);
|
||||
return "static var gen_directiveIndices = ${indices}";
|
||||
}
|
||||
|
||||
String _maybeGenHandleEventInternal() {
|
||||
if (_eventBindings.length > 0) {
|
||||
var handlers = _eventBindings.map((eb) => _genEventBinding(eb)).join("\n");
|
||||
return '''
|
||||
handleEventInternal(eventName, elIndex, locals) {
|
||||
var ${this._names.getPreventDefaultAccesor()} = false;
|
||||
${this._names.genInitEventLocals()}
|
||||
${handlers}
|
||||
return ${this._names.getPreventDefaultAccesor()};
|
||||
}
|
||||
''';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String _genEventBinding(EventBinding eb) {
|
||||
var recs = eb.records.map((r) => _genEventBindingEval(eb, r)).join("\n");
|
||||
return '''
|
||||
if (eventName == "${eb.eventName}" && elIndex == ${eb.elIndex}) {
|
||||
${recs}
|
||||
}''';
|
||||
}
|
||||
|
||||
String _genEventBindingEval(EventBinding eb, ProtoRecord r){
|
||||
if (r.lastInBinding) {
|
||||
var evalRecord = _logic.genEventBindingEvalValue(eb, r);
|
||||
var markPath = _genMarkPathToRootAsCheckOnce(r);
|
||||
var prevDefault = _genUpdatePreventDefault(eb, r);
|
||||
return "${evalRecord}\n${markPath}\n${prevDefault}";
|
||||
} else {
|
||||
return _logic.genEventBindingEvalValue(eb, r);
|
||||
}
|
||||
}
|
||||
|
||||
String _genMarkPathToRootAsCheckOnce(ProtoRecord r) {
|
||||
var br = r.bindingRecord;
|
||||
if (!br.isDefaultChangeDetection()) {
|
||||
return "${_names.getDetectorName(br.directiveRecord.directiveIndex)}.markPathToRootAsCheckOnce();";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
String _genUpdatePreventDefault(EventBinding eb, ProtoRecord r) {
|
||||
var local = this._names.getEventLocalName(eb, r.selfIndex);
|
||||
return """if (${local} == false) { ${_names.getPreventDefaultAccesor()} = true; }""";
|
||||
}
|
||||
|
||||
void _writeInitToBuf(StringBuffer buf) {
|
||||
buf.write('''
|
||||
$_GEN_PREFIX.preGeneratedProtoDetectors['$_changeDetectorDefId'] =
|
||||
$_changeDetectorTypeName.newProtoChangeDetector;
|
||||
''');
|
||||
}
|
||||
|
||||
String _maybeGenDehydrateDirectives() {
|
||||
var destroyPipesParamName = 'destroyPipes';
|
||||
var destroyPipesCode = _names.genPipeOnDestroy();
|
||||
if (destroyPipesCode.isNotEmpty) {
|
||||
destroyPipesCode = 'if (${destroyPipesParamName}) {${destroyPipesCode}}';
|
||||
}
|
||||
var dehydrateFieldsCode = _names.genDehydrateFields();
|
||||
if (destroyPipesCode.isEmpty && dehydrateFieldsCode.isEmpty) return '';
|
||||
return 'void dehydrateDirectives(${destroyPipesParamName}) '
|
||||
'{ ${destroyPipesCode} ${dehydrateFieldsCode} }';
|
||||
}
|
||||
|
||||
String _maybeGenHydrateDirectives() {
|
||||
var hydrateDirectivesCode = _logic.genHydrateDirectives(_directiveRecords);
|
||||
var hydrateDetectorsCode = _logic.genHydrateDetectors(_directiveRecords);
|
||||
if (hydrateDirectivesCode.isEmpty && hydrateDetectorsCode.isEmpty) {
|
||||
return '';
|
||||
}
|
||||
return 'void hydrateDirectives(directives) '
|
||||
'{ $hydrateDirectivesCode $hydrateDetectorsCode }';
|
||||
}
|
||||
|
||||
/// Generates calls to `onAllChangesDone` for all `Directive`s that request
|
||||
/// them.
|
||||
String _maybeGenCallOnAllChangesDone() {
|
||||
// NOTE(kegluneq): Order is important!
|
||||
var directiveNotifications = _directiveRecords.reversed
|
||||
.where((rec) => rec.callOnAllChangesDone)
|
||||
.map((rec) =>
|
||||
'${_names.getDirectiveName(rec.directiveIndex)}.onAllChangesDone();');
|
||||
|
||||
if (directiveNotifications.isNotEmpty) {
|
||||
return '''
|
||||
void callOnAllChangesDone() {
|
||||
${_names.getDispatcherName()}.notifyOnAllChangesDone();
|
||||
${directiveNotifications.join('')}
|
||||
}
|
||||
''';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String _genDeclareFields() {
|
||||
var fields = _names.getAllFieldNames();
|
||||
// If there's only one field, it's `context`, declared in the superclass.
|
||||
if (fields.length == 1) return '';
|
||||
fields.removeAt(CONTEXT_INDEX);
|
||||
var toRemove = 'this.';
|
||||
var declareNames = fields
|
||||
.map((f) => f.startsWith(toRemove) ? f.substring(toRemove.length) : f);
|
||||
return 'var ${declareNames.join(', ')};';
|
||||
}
|
||||
|
||||
String _genRecord(ProtoRecord r) {
|
||||
var rec = null;
|
||||
if (r.isLifeCycleRecord()) {
|
||||
rec = _genDirectiveLifecycle(r);
|
||||
} else if (r.isPipeRecord()) {
|
||||
rec = _genPipeCheck(r);
|
||||
} else {
|
||||
rec = _genReferenceCheck(r);
|
||||
}
|
||||
return '''
|
||||
${this._maybeFirstInBinding(r)}
|
||||
${rec}
|
||||
${this._maybeGenLastInDirective(r)}
|
||||
''';
|
||||
}
|
||||
|
||||
String _genDirectiveLifecycle(ProtoRecord r) {
|
||||
if (r.name == 'onCheck') {
|
||||
return _genOnCheck(r);
|
||||
} else if (r.name == 'onInit') {
|
||||
return _genOnInit(r);
|
||||
} else if (r.name == 'onChange') {
|
||||
return _genOnChange(r);
|
||||
} else {
|
||||
throw new BaseException("Unknown lifecycle event '${r.name}'");
|
||||
}
|
||||
}
|
||||
|
||||
String _genPipeCheck(ProtoRecord r) {
|
||||
var context = _names.getLocalName(r.contextIndex);
|
||||
var argString = r.args.map((arg) => _names.getLocalName(arg)).join(", ");
|
||||
|
||||
var oldValue = _names.getFieldName(r.selfIndex);
|
||||
var newValue = _names.getLocalName(r.selfIndex);
|
||||
|
||||
var pipe = _names.getPipeName(r.selfIndex);
|
||||
var pipeType = r.name;
|
||||
|
||||
var read = '''
|
||||
if ($_IDENTICAL_CHECK_FN($pipe, $_UTIL.uninitialized)) {
|
||||
$pipe = ${_names.getPipesAccessorName()}.get('$pipeType');
|
||||
}
|
||||
$newValue = $pipe.transform($context, [$argString]);
|
||||
''';
|
||||
|
||||
var check = '''
|
||||
if ($_NOT_IDENTICAL_CHECK_FN($oldValue, $newValue)) {
|
||||
$newValue = $_UTIL.unwrapValue($newValue);
|
||||
${_genChangeMarker(r)}
|
||||
${_genUpdateDirectiveOrElement(r)}
|
||||
${_genAddToChanges(r)}
|
||||
$oldValue = $newValue;
|
||||
}
|
||||
''';
|
||||
|
||||
return r.shouldBeChecked() ? "${read}${check}" : read;
|
||||
}
|
||||
|
||||
String _genReferenceCheck(ProtoRecord r) {
|
||||
var oldValue = _names.getFieldName(r.selfIndex);
|
||||
var newValue = _names.getLocalName(r.selfIndex);
|
||||
var read = '''
|
||||
${_logic.genPropertyBindingEvalValue(r)}
|
||||
''';
|
||||
|
||||
var check = '''
|
||||
if ($_NOT_IDENTICAL_CHECK_FN($newValue, $oldValue)) {
|
||||
${_genChangeMarker(r)}
|
||||
${_genUpdateDirectiveOrElement(r)}
|
||||
${_genAddToChanges(r)}
|
||||
$oldValue = $newValue;
|
||||
}
|
||||
''';
|
||||
|
||||
var genCode = r.shouldBeChecked() ? "${read}${check}" : read;
|
||||
|
||||
if (r.isPureFunction()) {
|
||||
// Add an "if changed guard"
|
||||
var condition = r.args.map((a) => _names.getChangeName(a)).join(' || ');
|
||||
if (r.isUsedByOtherRecord()) {
|
||||
return 'if ($condition) { $genCode } else { $newValue = $oldValue; }';
|
||||
} else {
|
||||
return 'if ($condition) { $genCode }';
|
||||
}
|
||||
} else {
|
||||
return genCode;
|
||||
}
|
||||
}
|
||||
|
||||
String _genChangeMarker(ProtoRecord r) {
|
||||
return r.argumentToPureFunction
|
||||
? "${this._names.getChangeName(r.selfIndex)} = true;"
|
||||
: "";
|
||||
}
|
||||
|
||||
String _genUpdateDirectiveOrElement(ProtoRecord r) {
|
||||
if (!r.lastInBinding) return '';
|
||||
|
||||
var newValue = _names.getLocalName(r.selfIndex);
|
||||
var oldValue = _names.getFieldName(r.selfIndex);
|
||||
var notifyDebug = _genConfig.logBindingUpdate ? "this.logBindingUpdate(${newValue});" : "";
|
||||
|
||||
var br = r.bindingRecord;
|
||||
if (br.target.isDirective()) {
|
||||
var directiveProperty =
|
||||
'${_names.getDirectiveName(br.directiveRecord.directiveIndex)}.${br.target.name}';
|
||||
return '''
|
||||
${_genThrowOnChangeCheck(oldValue, newValue)}
|
||||
$directiveProperty = $newValue;
|
||||
${notifyDebug}
|
||||
$_IS_CHANGED_LOCAL = true;
|
||||
''';
|
||||
} else {
|
||||
return '''
|
||||
${_genThrowOnChangeCheck(oldValue, newValue)}
|
||||
this.notifyDispatcher(${newValue});
|
||||
${notifyDebug}
|
||||
''';
|
||||
}
|
||||
}
|
||||
|
||||
String _genThrowOnChangeCheck(String oldValue, String newValue) {
|
||||
if (this._genConfig.genCheckNoChanges) {
|
||||
return '''
|
||||
if(throwOnChange) {
|
||||
this.throwOnChangeError(${oldValue}, ${newValue});
|
||||
}
|
||||
''';
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
String _genCheckNoChanges() {
|
||||
if (this._genConfig.genCheckNoChanges) {
|
||||
return 'void checkNoChanges() { runDetectChanges(true); }';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String _maybeFirstInBinding(ProtoRecord r) {
|
||||
var prev = ChangeDetectionUtil.protoByIndex(_records, r.selfIndex - 1);
|
||||
var firstInBindng = prev == null || prev.bindingRecord != r.bindingRecord;
|
||||
return firstInBindng && !r.bindingRecord.isDirectiveLifecycle()
|
||||
? "${_names.getPropertyBindingIndex()} = ${r.propertyBindingIndex};"
|
||||
: '';
|
||||
}
|
||||
|
||||
String _genAddToChanges(ProtoRecord r) {
|
||||
var newValue = _names.getLocalName(r.selfIndex);
|
||||
var oldValue = _names.getFieldName(r.selfIndex);
|
||||
if (!r.bindingRecord.callOnChange()) return '';
|
||||
return "$_CHANGES_LOCAL = addChange($_CHANGES_LOCAL, $oldValue, $newValue);";
|
||||
}
|
||||
|
||||
String _maybeGenLastInDirective(ProtoRecord r) {
|
||||
if (!r.lastInDirective) return '';
|
||||
return '''
|
||||
$_CHANGES_LOCAL = null;
|
||||
${_genNotifyOnPushDetectors(r)}
|
||||
$_IS_CHANGED_LOCAL = false;
|
||||
''';
|
||||
}
|
||||
|
||||
String _genOnCheck(ProtoRecord r) {
|
||||
var br = r.bindingRecord;
|
||||
return 'if (!throwOnChange) '
|
||||
'${_names.getDirectiveName(br.directiveRecord.directiveIndex)}.onCheck();';
|
||||
}
|
||||
|
||||
String _genOnInit(ProtoRecord r) {
|
||||
var br = r.bindingRecord;
|
||||
return 'if (!throwOnChange && !${_names.getAlreadyCheckedName()}) '
|
||||
'${_names.getDirectiveName(br.directiveRecord.directiveIndex)}.onInit();';
|
||||
}
|
||||
|
||||
String _genOnChange(ProtoRecord r) {
|
||||
var br = r.bindingRecord;
|
||||
return 'if (!throwOnChange && $_CHANGES_LOCAL != null) '
|
||||
'${_names.getDirectiveName(br.directiveRecord.directiveIndex)}'
|
||||
'.onChange($_CHANGES_LOCAL);';
|
||||
}
|
||||
|
||||
String _genNotifyOnPushDetectors(ProtoRecord r) {
|
||||
var br = r.bindingRecord;
|
||||
if (!r.lastInDirective || br.isDefaultChangeDetection()) return '';
|
||||
return '''
|
||||
if($_IS_CHANGED_LOCAL) {
|
||||
${_names.getDetectorName(br.directiveRecord.directiveIndex)}.markAsCheckOnce();
|
||||
}
|
||||
''';
|
||||
}
|
||||
}
|
||||
|
||||
const PROTO_CHANGE_DETECTOR_FACTORY_METHOD = 'newProtoChangeDetector';
|
||||
|
||||
const _BASE_CLASS = '$_GEN_PREFIX.AbstractChangeDetector';
|
||||
const _CHANGES_LOCAL = 'changes';
|
||||
const _GEN_PREFIX = '_gen';
|
||||
const _GEN_RECORDS_METHOD_NAME = '_createRecords';
|
||||
const _IDENTICAL_CHECK_FN = '$_GEN_PREFIX.looseIdentical';
|
||||
const _NOT_IDENTICAL_CHECK_FN = '$_GEN_PREFIX.looseNotIdentical';
|
||||
const _IS_CHANGED_LOCAL = 'isChanged';
|
||||
const _PREGEN_PROTO_CHANGE_DETECTOR_IMPORT =
|
||||
'package:angular2/src/change_detection/pregen_proto_change_detector.dart';
|
||||
const _UTIL = '$_GEN_PREFIX.ChangeDetectionUtil';
|
@ -1,25 +0,0 @@
|
||||
library angular2.transform.template_compiler.compile_step_factory;
|
||||
|
||||
import 'package:angular2/src/change_detection/parser/parser.dart' as ng;
|
||||
import 'package:angular2/src/render/api.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/compile_step.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/compile_step_factory.dart'
|
||||
as base;
|
||||
import 'package:angular2/src/render/dom/compiler/directive_parser.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/property_binding_parser.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/text_interpolation_parser.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/view_splitter.dart';
|
||||
|
||||
class CompileStepFactory implements base.CompileStepFactory {
|
||||
final ng.Parser _parser;
|
||||
CompileStepFactory(this._parser);
|
||||
|
||||
List<CompileStep> createSteps(ViewDefinition template) {
|
||||
return [
|
||||
new ViewSplitter(_parser),
|
||||
new PropertyBindingParser(_parser),
|
||||
new DirectiveParser(_parser, template.directives),
|
||||
new TextInterpolationParser(_parser)
|
||||
];
|
||||
}
|
||||
}
|
@ -1,139 +0,0 @@
|
||||
library angular2.transform.template_compiler.generator;
|
||||
|
||||
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/change_detection/interfaces.dart';
|
||||
import 'package:angular2/src/core/compiler/proto_view_factory.dart';
|
||||
import 'package:angular2/src/dom/dom_adapter.dart';
|
||||
import 'package:angular2/src/render/api.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/compile_pipeline.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/style_inliner.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/style_url_resolver.dart';
|
||||
import 'package:angular2/src/render/dom/compiler/view_loader.dart';
|
||||
import 'package:angular2/src/render/dom/schema/element_schema_registry.dart';
|
||||
import 'package:angular2/src/render/dom/schema/dom_element_schema_registry.dart';
|
||||
import 'package:angular2/src/render/dom/template_cloner.dart';
|
||||
import 'package:angular2/src/render/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/xhr_impl.dart';
|
||||
import 'package:angular2/src/facade/lang.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
|
||||
import 'change_detector_codegen.dart' as change;
|
||||
import 'compile_step_factory.dart';
|
||||
import 'reflection/codegen.dart' as reg;
|
||||
import 'reflection/processor.dart' as reg;
|
||||
import 'reflection/reflection_capabilities.dart';
|
||||
import 'view_definition_creator.dart';
|
||||
|
||||
/// Reads the `.ng_deps.dart` file represented by `entryPoint` and parses any
|
||||
/// Angular 2 `View` annotations it declares to generate `getter`s,
|
||||
/// `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,
|
||||
{bool generateRegistrations: true,
|
||||
bool generateChangeDetectors: true, bool reflectPropertiesAsAttributes: false}) async {
|
||||
var viewDefResults = await createViewDefinitions(reader, entryPoint);
|
||||
// Note: TemplateCloner(-1) never serializes Nodes into strings.
|
||||
// we might want to change this to TemplateCloner(0) to force the serialization
|
||||
// later when the transformer also stores the proto view template.
|
||||
var extractor = new _TemplateExtractor(new DomElementSchemaRegistry(),
|
||||
new TemplateCloner(-1), new XhrImpl(reader, entryPoint));
|
||||
|
||||
final processor = new reg.Processor();
|
||||
|
||||
var changeDetectorClasses = new change.Codegen();
|
||||
for (var rType in viewDefResults.viewDefinitions.keys) {
|
||||
var viewDefEntry = viewDefResults.viewDefinitions[rType];
|
||||
var protoView = await extractor.extractTemplates(viewDefEntry.viewDef);
|
||||
if (protoView == null) continue;
|
||||
|
||||
if (generateRegistrations) {
|
||||
processor.process(viewDefEntry, protoView);
|
||||
}
|
||||
if (generateChangeDetectors) {
|
||||
var saved = reflector.reflectionCapabilities;
|
||||
var genConfig = new ChangeDetectorGenConfig(assertionsEnabled(), assertionsEnabled(), reflectPropertiesAsAttributes);
|
||||
|
||||
reflector.reflectionCapabilities = const NullReflectionCapabilities();
|
||||
var defs = getChangeDetectorDefinitions(viewDefEntry.hostMetadata,
|
||||
protoView, viewDefEntry.viewDef.directives, genConfig);
|
||||
for (var i = 0; i < defs.length; ++i) {
|
||||
changeDetectorClasses.generate('${rType.typeName}',
|
||||
'_${rType.typeName}_ChangeDetector$i', defs[i]);
|
||||
}
|
||||
reflector.reflectionCapabilities = saved;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(kegluneq): Do not hard-code `false` here once i/3436 is fixed.
|
||||
final registrations = new reg.Codegen(generateChangeDetectors: false);
|
||||
registrations.generate(processor);
|
||||
|
||||
var code = viewDefResults.ngDeps.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;
|
||||
var initInjectIdx = viewDefResults.ngDeps.setupMethod.end - 1;
|
||||
return '${code.substring(0, importInjectIdx)}'
|
||||
'${changeDetectorClasses.imports}'
|
||||
'${code.substring(importInjectIdx, codeInjectIdx)}'
|
||||
'${registrations}'
|
||||
'${code.substring(codeInjectIdx, initInjectIdx)}'
|
||||
'${changeDetectorClasses.initialize}'
|
||||
'${code.substring(initInjectIdx)}'
|
||||
'$changeDetectorClasses';
|
||||
}
|
||||
|
||||
/// Extracts `template` and `url` values from `View` annotations, reads
|
||||
/// template code if necessary, and determines what values will be
|
||||
/// reflectively accessed from that template.
|
||||
class _TemplateExtractor {
|
||||
final CompileStepFactory _factory;
|
||||
ViewLoader _loader;
|
||||
ElementSchemaRegistry _schemaRegistry;
|
||||
TemplateCloner _templateCloner;
|
||||
|
||||
_TemplateExtractor(this._schemaRegistry, this._templateCloner, XHR xhr)
|
||||
: _factory = new CompileStepFactory(new ng.Parser(new ng.Lexer())) {
|
||||
var urlResolver = new UrlResolver();
|
||||
var styleUrlResolver = new StyleUrlResolver(urlResolver);
|
||||
var styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
|
||||
|
||||
_loader = new ViewLoader(xhr, styleInliner, styleUrlResolver);
|
||||
}
|
||||
|
||||
Future<ProtoViewDto> extractTemplates(ViewDefinition viewDef) async {
|
||||
// Check for "imperative views".
|
||||
if (viewDef.template == null && viewDef.templateAbsUrl == null) return null;
|
||||
|
||||
var templateAndStyles = await _loader.load(viewDef);
|
||||
|
||||
// NOTE(kegluneq): Since this is a global, we must not have any async
|
||||
// operations between saving and restoring it, otherwise we can get into
|
||||
// a bad state. See issue #2359 for additional context.
|
||||
var savedReflectionCapabilities = reflector.reflectionCapabilities;
|
||||
reflector.reflectionCapabilities = const NullReflectionCapabilities();
|
||||
|
||||
var pipeline = new CompilePipeline(_factory.createSteps(viewDef));
|
||||
|
||||
var compileElements = pipeline.processElements(
|
||||
DOM.createTemplate(templateAndStyles.template),
|
||||
ViewType.COMPONENT,
|
||||
viewDef);
|
||||
var protoViewDto = compileElements[0]
|
||||
.inheritedProtoView
|
||||
.build(_schemaRegistry, _templateCloner);
|
||||
|
||||
reflector.reflectionCapabilities = savedReflectionCapabilities;
|
||||
|
||||
return protoViewDto;
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
library angular2.transform.template_compiler.reflection.codegen;
|
||||
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:angular2/src/transform/common/property_utils.dart' as prop;
|
||||
|
||||
import 'model.dart';
|
||||
|
||||
class Codegen {
|
||||
final StringBuffer _buf = new StringBuffer();
|
||||
|
||||
/// Whether we are pre-generating change detectors.
|
||||
/// If we have pre-generated change detectors, we need
|
||||
final bool generateChangeDetectors;
|
||||
|
||||
Codegen({this.generateChangeDetectors});
|
||||
|
||||
void generate(CodegenModel model) {
|
||||
if (model != null) {
|
||||
var calls = _generateGetters(_extractNames(model.getterNames));
|
||||
if (calls.isNotEmpty) {
|
||||
_buf.write('..${REGISTER_GETTERS_METHOD_NAME}'
|
||||
'({${calls.join(', ')}})');
|
||||
}
|
||||
calls = _generateSetters(_extractNames(model.setterNames));
|
||||
if (calls.isNotEmpty) {
|
||||
_buf.write('..${REGISTER_SETTERS_METHOD_NAME}'
|
||||
'({${calls.join(', ')}})');
|
||||
}
|
||||
calls = _generateMethods(_extractNames(model.methodNames));
|
||||
if (calls.isNotEmpty) {
|
||||
_buf.write('..${REGISTER_METHODS_METHOD_NAME}'
|
||||
'({${calls.join(', ')}})');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Iterable<String> _extractNames(Iterable<ReflectiveAccessor> accessors) {
|
||||
var names = accessors.where((accessor) {
|
||||
return accessor.isStaticallyNecessary || !generateChangeDetectors;
|
||||
}).map((accessor) => accessor.sanitizedName);
|
||||
var nameList = names.toList();
|
||||
nameList.sort();
|
||||
return nameList;
|
||||
}
|
||||
|
||||
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) ';
|
||||
}
|
||||
});
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
library angular2.transform.template_compiler.reflection.model;
|
||||
|
||||
import 'package:angular2/src/render/dom/util.dart';
|
||||
|
||||
/// Defines the names of getters, setters, and methods which need to be
|
||||
/// available to Angular 2 via the `reflector` at runtime.
|
||||
/// See [angular2.src.reflection.reflector] for details.
|
||||
abstract class CodegenModel {
|
||||
Iterable<ReflectiveAccessor> get getterNames;
|
||||
Iterable<ReflectiveAccessor> get methodNames;
|
||||
Iterable<ReflectiveAccessor> get setterNames;
|
||||
}
|
||||
|
||||
/// Wraps a getter, setter, or method that we may need to access reflectively in
|
||||
/// an Angular2 app.
|
||||
/// This is essentially a wrapper for `sanitizedName`, which is the name of the
|
||||
/// actual getter, setter, or method that will be registered. Note that
|
||||
/// `operator==` and `hashCode` basically forward to `sanitizedName`.
|
||||
class ReflectiveAccessor {
|
||||
/// The value in the Ast determining that we need this accessor. This is the
|
||||
/// value that is actually present in the template, which may not directly
|
||||
/// correspond to the model on the `Component`.
|
||||
final String astValue;
|
||||
|
||||
/// The sanitized name of this accessor. This is the name of the getter,
|
||||
/// setter, or method on the `Component`.
|
||||
final String sanitizedName;
|
||||
|
||||
/// Whether this getter, setter, or method is still necessary when we have
|
||||
/// pre-generated change detectors.
|
||||
final bool isStaticallyNecessary;
|
||||
|
||||
ReflectiveAccessor(String astValue, {this.isStaticallyNecessary})
|
||||
: this.astValue = astValue,
|
||||
this.sanitizedName = sanitizePropertyName(astValue);
|
||||
|
||||
@override
|
||||
bool operator ==(other) {
|
||||
if (other is! ReflectiveAccessor) return false;
|
||||
return sanitizedName == other.sanitizedName;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => sanitizedName.hashCode;
|
||||
}
|
||||
|
||||
String sanitizePropertyName(String name) {
|
||||
return dashCaseToCamelCase(EventConfig.parse(name).fieldName);
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
library angular2.transform.template_compiler.reflection.processor;
|
||||
|
||||
import 'package:angular2/src/change_detection/parser/ast.dart';
|
||||
import 'package:angular2/src/render/api.dart';
|
||||
import 'package:angular2/src/transform/template_compiler/view_definition_creator.dart';
|
||||
|
||||
import 'model.dart';
|
||||
|
||||
class Processor implements CodegenModel {
|
||||
/// The names of all requested `getter`s.
|
||||
final Set<ReflectiveAccessor> getterNames = new Set<ReflectiveAccessor>();
|
||||
|
||||
/// The names of all requested `setter`s.
|
||||
final Set<ReflectiveAccessor> setterNames = new Set<ReflectiveAccessor>();
|
||||
|
||||
/// The names of all requested `method`s.
|
||||
final Set<ReflectiveAccessor> methodNames = new Set<ReflectiveAccessor>();
|
||||
|
||||
_NgAstVisitor _visitor;
|
||||
|
||||
Processor() {
|
||||
_visitor = new _NgAstVisitor(this);
|
||||
}
|
||||
|
||||
void process(ViewDefinitionEntry viewDefEntry, ProtoViewDto protoViewDto) {
|
||||
_processViewDefinition(viewDefEntry);
|
||||
_processProtoViewDto(protoViewDto);
|
||||
}
|
||||
|
||||
/// Extracts the names of necessary getters from the events in host and
|
||||
/// dependent [DirectiveMetadata].
|
||||
void _processViewDefinition(ViewDefinitionEntry viewDefEntry) {
|
||||
// These are necessary even with generated change detectors.
|
||||
if (viewDefEntry.hostMetadata != null &&
|
||||
viewDefEntry.hostMetadata.events != null) {
|
||||
viewDefEntry.hostMetadata.events.forEach((eventName) {
|
||||
getterNames.add(
|
||||
new ReflectiveAccessor(eventName, isStaticallyNecessary: true));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _processProtoViewDto(ProtoViewDto protoViewDto) {
|
||||
_visitor.isStaticallyNecessary = false;
|
||||
|
||||
protoViewDto.textBindings.forEach((ast) => ast.visit(_visitor));
|
||||
protoViewDto.elementBinders.forEach((binder) {
|
||||
binder.propertyBindings.forEach((binding) {
|
||||
binding.astWithSource.visit(_visitor);
|
||||
setterNames.add(new ReflectiveAccessor(binding.property,
|
||||
isStaticallyNecessary: false));
|
||||
});
|
||||
|
||||
binder.directives.forEach((directiveBinding) {
|
||||
directiveBinding.propertyBindings.values
|
||||
.forEach((propBinding) => propBinding.visit(_visitor));
|
||||
directiveBinding.propertyBindings.keys.forEach((bindingName) {
|
||||
setterNames.add(new ReflectiveAccessor(bindingName,
|
||||
isStaticallyNecessary: false));
|
||||
});
|
||||
|
||||
directiveBinding.hostPropertyBindings.forEach((elementBinding) {
|
||||
elementBinding.astWithSource.visit(_visitor);
|
||||
setterNames.add(new ReflectiveAccessor(elementBinding.property,
|
||||
isStaticallyNecessary: false));
|
||||
});
|
||||
});
|
||||
|
||||
binder.eventBindings
|
||||
.forEach((eventBinding) => eventBinding.source.visit(_visitor));
|
||||
|
||||
binder.directives.forEach((directiveBinding) {
|
||||
directiveBinding.eventBindings
|
||||
.forEach((eventBinding) => eventBinding.source.visit(_visitor));
|
||||
});
|
||||
|
||||
if (binder.nestedProtoView != null) {
|
||||
_processProtoViewDto(binder.nestedProtoView);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _NgAstVisitor extends RecursiveAstVisitor {
|
||||
final Processor _result;
|
||||
|
||||
/// Whether any getters or setters recorded are necessary when running
|
||||
/// statically. A getter or setter that is necessary only for change detection
|
||||
/// is not necessary when running statically because all accesses are handled
|
||||
/// by the dedicated change detector classes.
|
||||
bool isStaticallyNecessary = false;
|
||||
|
||||
_NgAstVisitor(this._result);
|
||||
|
||||
visitMethodCall(MethodCall ast) {
|
||||
_result.methodNames
|
||||
.add(new ReflectiveAccessor(ast.name, isStaticallyNecessary: true));
|
||||
super.visitMethodCall(ast);
|
||||
}
|
||||
|
||||
visitPropertyRead(PropertyRead ast) {
|
||||
_result.getterNames.add(new ReflectiveAccessor(ast.name,
|
||||
isStaticallyNecessary: isStaticallyNecessary));
|
||||
super.visitPropertyRead(ast);
|
||||
}
|
||||
|
||||
visitPropertyWrite(PropertyWrite ast) {
|
||||
_result.setterNames.add(new ReflectiveAccessor(ast.name,
|
||||
isStaticallyNecessary: isStaticallyNecessary));
|
||||
super.visitPropertyWrite(ast);
|
||||
}
|
||||
|
||||
visitSafeMethodCall(SafeMethodCall ast) {
|
||||
_result.methodNames
|
||||
.add(new ReflectiveAccessor(ast.name, isStaticallyNecessary: true));
|
||||
super.visitSafeMethodCall(ast);
|
||||
}
|
||||
|
||||
visitSafePropertyRead(SafePropertyRead ast) {
|
||||
_result.getterNames.add(new ReflectiveAccessor(ast.name,
|
||||
isStaticallyNecessary: isStaticallyNecessary));
|
||||
super.visitSafePropertyRead(ast);
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
library angular2.transform.template_compiler.reflection.reflection_capabilities;
|
||||
|
||||
import 'package:angular2/src/reflection/reflection_capabilities.dart';
|
||||
import 'package:angular2/src/reflection/types.dart';
|
||||
|
||||
/// ReflectionCapabilities object that responds to all requests for `getter`s,
|
||||
/// `setter`s, and `method`s with `null`.
|
||||
class NullReflectionCapabilities implements ReflectionCapabilities {
|
||||
const NullReflectionCapabilities();
|
||||
|
||||
_notImplemented(String name) => throw 'Not implemented: $name';
|
||||
|
||||
bool isReflectionEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
Function factory(Type type) => _notImplemented("factory");
|
||||
|
||||
List<List> parameters(typeOrFunc) => _notImplemented('parameters');
|
||||
|
||||
List<List> interfaces(typeOrFunc) => _notImplemented('interfaces');
|
||||
|
||||
List annotations(typeOrFunc) => _notImplemented('annotations');
|
||||
|
||||
GetterFn getter(String name) => _nullGetter;
|
||||
|
||||
SetterFn setter(String name) => _nullSetter;
|
||||
|
||||
MethodFn method(String name) => _nullMethod;
|
||||
}
|
||||
|
||||
_nullGetter(Object p) => null;
|
||||
_nullSetter(Object p, v) => null;
|
||||
_nullMethod(Object p, List a) => null;
|
@ -1,41 +0,0 @@
|
||||
library angular2.transform.template_compiler.transformer;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:angular2/src/dom/html_adapter.dart';
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/formatter.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart' as log;
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:angular2/src/transform/common/options.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
|
||||
import 'generator.dart';
|
||||
|
||||
/// {@link Transformer} responsible for detecting and processing Angular 2 templates.
|
||||
///
|
||||
/// {@link TemplateCompiler} uses the Angular 2 `Compiler` to process the templates,
|
||||
/// extracting information about what reflection is necessary to render and
|
||||
/// use that template. It then generates code in place of those reflective
|
||||
/// accesses.
|
||||
class TemplateCompiler extends Transformer {
|
||||
final TransformerOptions options;
|
||||
|
||||
TemplateCompiler(this.options);
|
||||
|
||||
@override
|
||||
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_EXTENSION);
|
||||
|
||||
@override
|
||||
Future apply(Transform transform) async {
|
||||
await log.initZoned(transform, () async {
|
||||
Html5LibDomAdapter.makeCurrent();
|
||||
var id = transform.primaryInput.id;
|
||||
var reader = new AssetReader.fromTransform(transform);
|
||||
var transformedCode = formatter.format(await processTemplates(reader, id,
|
||||
generateChangeDetectors: options.generateChangeDetectors,
|
||||
reflectPropertiesAsAttributes: options.reflectPropertiesAsAttributes));
|
||||
transform.addOutput(new Asset.fromString(id, transformedCode));
|
||||
});
|
||||
}
|
||||
}
|
@ -1,274 +0,0 @@
|
||||
library angular2.transform.template_compiler.view_definition_creator;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:angular2/src/render/api.dart';
|
||||
import 'package:angular2/src/transform/common/asset_reader.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:angular2/src/transform/common/ng_deps.dart';
|
||||
import 'package:angular2/src/transform/common/ng_meta.dart';
|
||||
import 'package:barback/barback.dart';
|
||||
import 'package:code_transformers/assets.dart';
|
||||
|
||||
/// Creates [ViewDefinition] objects for all `View` `Directive`s defined in
|
||||
/// `entryPoint`.
|
||||
Future<ViewDefinitionResults> createViewDefinitions(
|
||||
AssetReader reader, AssetId entryPoint) async {
|
||||
return await new _ViewDefinitionCreator(reader, entryPoint).createViewDefs();
|
||||
}
|
||||
|
||||
class ViewDefinitionResults {
|
||||
final NgDeps ngDeps;
|
||||
final Map<RegisteredType, ViewDefinitionEntry> viewDefinitions;
|
||||
ViewDefinitionResults._(this.ngDeps, this.viewDefinitions);
|
||||
}
|
||||
|
||||
class ViewDefinitionEntry {
|
||||
final RenderDirectiveMetadata hostMetadata;
|
||||
final ViewDefinition viewDef;
|
||||
|
||||
ViewDefinitionEntry._(this.hostMetadata, this.viewDef);
|
||||
}
|
||||
|
||||
String _getComponentId(AssetId assetId, String className) => '$className';
|
||||
|
||||
// TODO(kegluenq): Improve this test.
|
||||
bool _isViewAnnotation(InstanceCreationExpression node) {
|
||||
var constructorName = node.constructorName.type.name;
|
||||
if (constructorName is PrefixedIdentifier) {
|
||||
constructorName = constructorName.identifier;
|
||||
}
|
||||
return constructorName.name == 'View';
|
||||
}
|
||||
|
||||
/// Creates [ViewDefinition] objects for all `View` `Directive`s defined in
|
||||
/// `entryPoint`.
|
||||
class _ViewDefinitionCreator {
|
||||
final AssetReader reader;
|
||||
final AssetId entryPoint;
|
||||
final Future<NgDeps> ngDepsFuture;
|
||||
|
||||
_ViewDefinitionCreator(AssetReader reader, AssetId entryPoint)
|
||||
: this.reader = reader,
|
||||
this.entryPoint = entryPoint,
|
||||
ngDepsFuture = NgDeps.parse(reader, entryPoint);
|
||||
|
||||
Future<ViewDefinitionResults> createViewDefs() async {
|
||||
var ngDeps = await ngDepsFuture;
|
||||
|
||||
var retVal = <RegisteredType, ViewDefinitionEntry>{};
|
||||
var visitor = new _TemplateExtractVisitor(await _extractNgMeta());
|
||||
ngDeps.registeredTypes.forEach((rType) {
|
||||
visitor.reset();
|
||||
rType.annotations.accept(visitor);
|
||||
if (visitor.viewDef != null) {
|
||||
// Note: we use '' because the current file maps to the default prefix.
|
||||
var ngMeta = visitor._metadataMap[''];
|
||||
var typeName = '${rType.typeName}';
|
||||
var hostMetadata = null;
|
||||
if (ngMeta.types.containsKey(typeName)) {
|
||||
hostMetadata = ngMeta.types[typeName];
|
||||
visitor.viewDef.componentId = hostMetadata.id;
|
||||
} else {
|
||||
logger.warning('Missing component "$typeName" in metadata map',
|
||||
asset: entryPoint);
|
||||
visitor.viewDef.componentId = _getComponentId(entryPoint, typeName);
|
||||
}
|
||||
retVal[rType] =
|
||||
new ViewDefinitionEntry._(hostMetadata, visitor.viewDef);
|
||||
}
|
||||
});
|
||||
return new ViewDefinitionResults._(ngDeps, retVal);
|
||||
}
|
||||
|
||||
/// Creates a map from [AssetId] to import prefix for `.dart` libraries
|
||||
/// imported by `entryPoint`, excluding any `.ng_deps.dart` files it imports.
|
||||
/// Unprefixed imports have `null` as their value. `entryPoint` is included
|
||||
/// in the map with no prefix.
|
||||
Future<Map<AssetId, String>> _createImportAssetToPrefixMap() async {
|
||||
// TODO(kegluneq): Support `part` directives.
|
||||
var ngDeps = await ngDepsFuture;
|
||||
|
||||
var importAssetToPrefix = <AssetId, String>{};
|
||||
// Include the `.ng_meta.json` file associated with `entryPoint`.
|
||||
importAssetToPrefix[new AssetId(
|
||||
entryPoint.package, toMetaExtension(entryPoint.path))] = null;
|
||||
|
||||
for (ImportDirective node in ngDeps.imports) {
|
||||
var uri = stringLiteralToString(node.uri);
|
||||
if (uri.endsWith('.dart') && !uri.endsWith(DEPS_EXTENSION)) {
|
||||
var prefix = node.prefix != null && node.prefix.name != null
|
||||
? '${node.prefix.name}'
|
||||
: null;
|
||||
importAssetToPrefix[uriToAssetId(
|
||||
entryPoint, uri, logger, null /* span */,
|
||||
errorOnAbsolute: false)] = prefix;
|
||||
}
|
||||
}
|
||||
return importAssetToPrefix;
|
||||
}
|
||||
|
||||
/// Reads the `.ng_meta.json` files associated with all of `entryPoint`'s
|
||||
/// imports and creates a map `Type` name, prefixed if appropriate to the
|
||||
/// associated [RenderDirectiveMetadata].
|
||||
///
|
||||
/// For example, if in `entryPoint` we have:
|
||||
///
|
||||
/// ```
|
||||
/// import 'component.dart' as prefix;
|
||||
/// ```
|
||||
///
|
||||
/// and in 'component.dart' we have:
|
||||
///
|
||||
/// ```
|
||||
/// @Component(...)
|
||||
/// class MyComponent {...}
|
||||
/// ```
|
||||
///
|
||||
/// This method will look for `component.ng_meta.json`to contain the
|
||||
/// serialized [RenderDirectiveMetadata] for `MyComponent` and any other
|
||||
/// `Directive`s declared in `component.dart`. We use this information to
|
||||
/// build a map:
|
||||
///
|
||||
/// ```
|
||||
/// {
|
||||
/// "prefix.MyComponent": [RenderDirectiveMetadata for MyComponent],
|
||||
/// ...<any other entries>...
|
||||
/// }
|
||||
/// ```
|
||||
Future<Map<String, NgMeta>> _extractNgMeta() async {
|
||||
var importAssetToPrefix = await _createImportAssetToPrefixMap();
|
||||
|
||||
var retVal = <String, NgMeta>{};
|
||||
for (var importAssetId in importAssetToPrefix.keys) {
|
||||
var prefix = importAssetToPrefix[importAssetId];
|
||||
if (prefix == null) prefix = '';
|
||||
var ngMeta = retVal.putIfAbsent(prefix, () => new NgMeta.empty());
|
||||
var metaAssetId = new AssetId(
|
||||
importAssetId.package, toMetaExtension(importAssetId.path));
|
||||
if (await reader.hasInput(metaAssetId)) {
|
||||
try {
|
||||
var json = JSON.decode(await reader.readAsString(metaAssetId));
|
||||
var newMetadata = new NgMeta.fromJson(json);
|
||||
newMetadata.types.forEach((className, metadata) {
|
||||
metadata.id = _getComponentId(importAssetId, className);
|
||||
});
|
||||
ngMeta.addAll(newMetadata);
|
||||
} catch (ex, stackTrace) {
|
||||
logger.warning('Failed to decode: $ex, $stackTrace',
|
||||
asset: metaAssetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
/// Visitor responsible for processing the `annotations` property of a
|
||||
/// [RegisterType] object and pulling out [ViewDefinition] information.
|
||||
class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> {
|
||||
ViewDefinition viewDef = null;
|
||||
final Map<String, NgMeta> _metadataMap;
|
||||
final ConstantEvaluator _evaluator = new ConstantEvaluator();
|
||||
|
||||
_TemplateExtractVisitor(this._metadataMap);
|
||||
|
||||
void reset() {
|
||||
viewDef = null;
|
||||
}
|
||||
|
||||
/// These correspond to the annotations themselves.
|
||||
@override
|
||||
Object visitInstanceCreationExpression(InstanceCreationExpression node) {
|
||||
if (_isViewAnnotation(node)) {
|
||||
viewDef = new ViewDefinition(directives: <RenderDirectiveMetadata>[]);
|
||||
node.visitChildren(this);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// These correspond to the annotation parameters.
|
||||
@override
|
||||
Object visitNamedExpression(NamedExpression node) {
|
||||
// TODO(kegluneq): Remove this limitation.
|
||||
if (node.name is! Label || node.name.label is! SimpleIdentifier) {
|
||||
logger.error(
|
||||
'Angular 2 currently only supports simple identifiers in directives.'
|
||||
' Source: ${node}');
|
||||
return null;
|
||||
}
|
||||
var keyString = '${node.name.label}';
|
||||
if (keyString == 'directives') {
|
||||
_readDirectives(node.expression);
|
||||
}
|
||||
if (keyString == 'template' || keyString == 'templateUrl') {
|
||||
// This could happen in a non-View annotation with a `template` or
|
||||
// `templateUrl` property.
|
||||
if (viewDef == null) return null;
|
||||
|
||||
var valueString = node.expression.accept(_evaluator);
|
||||
if (valueString is! String) {
|
||||
logger.error(
|
||||
'Angular 2 currently only supports string literals in directives.'
|
||||
' Source: ${node}');
|
||||
return null;
|
||||
}
|
||||
if (keyString == 'templateUrl') {
|
||||
if (viewDef.templateAbsUrl != null) {
|
||||
logger.error(
|
||||
'Found multiple values for "templateUrl". Source: ${node}');
|
||||
}
|
||||
viewDef.templateAbsUrl = valueString;
|
||||
} else {
|
||||
// keyString == 'template'
|
||||
if (viewDef.template != null) {
|
||||
logger.error('Found multiple values for "template". Source: ${node}');
|
||||
}
|
||||
viewDef.template = valueString;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void _readDirectives(Expression node) {
|
||||
// This could happen in a non-View annotation with a `directives`
|
||||
// parameter.
|
||||
if (viewDef == null) return;
|
||||
|
||||
if (node is! ListLiteral) {
|
||||
logger.error('Angular 2 currently only supports list literals as values '
|
||||
'for "directives". Source: $node');
|
||||
return;
|
||||
}
|
||||
var directiveList = (node as ListLiteral);
|
||||
for (var node in directiveList.elements) {
|
||||
var ngMeta;
|
||||
var name;
|
||||
if (node is SimpleIdentifier) {
|
||||
ngMeta = _metadataMap[''];
|
||||
name = node.name;
|
||||
} else if (node is PrefixedIdentifier) {
|
||||
ngMeta = _metadataMap[node.prefix.name];
|
||||
name = node.name;
|
||||
} else {
|
||||
logger.error(
|
||||
'Angular 2 currently only supports simple and prefixed identifiers '
|
||||
'as values for "directives". Source: $node');
|
||||
return;
|
||||
}
|
||||
if (ngMeta.types.containsKey(name)) {
|
||||
viewDef.directives.add(ngMeta.types[name]);
|
||||
} else if (ngMeta.aliases.containsKey(name)) {
|
||||
viewDef.directives.addAll(ngMeta.flatten(name));
|
||||
} else {
|
||||
logger.warning('Could not find Directive entry for $node. '
|
||||
'Please be aware that Dart transformers have limited support for '
|
||||
'reusable, pre-defined lists of Directives (aka '
|
||||
'"directive aliases"). See https://goo.gl/d8XPt0 for details.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user