refactor(transformers): extract lifecycle hooks from the interfaces

This commit is contained in:
Tim Blasi 2015-09-02 10:06:10 -07:00 committed by Victor Berchet
parent 8302afffb4
commit ef88e0f804
7 changed files with 160 additions and 102 deletions

View File

@ -34,7 +34,7 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask {
List<RenderDirectiveMetadata> metaList = <RenderDirectiveMetadata>[]; List<RenderDirectiveMetadata> metaList = <RenderDirectiveMetadata>[];
for (CompilationUnitMember unitMember in unit.declarations) { for (CompilationUnitMember unitMember in unit.declarations) {
if (unitMember is ClassDeclaration) { if (unitMember is ClassDeclaration) {
RenderDirectiveMetadata meta = readDirectiveMetadata(unitMember.metadata); RenderDirectiveMetadata meta = readDirectiveMetadata(unitMember);
if (meta != null) { if (meta != null) {
metaList.add(meta); metaList.add(meta);
} }

View File

@ -6,14 +6,20 @@ import 'package:angular2/src/core/render/api.dart';
import 'package:angular2/src/core/change_detection/change_detection.dart'; import 'package:angular2/src/core/change_detection/change_detection.dart';
/// Reads [RenderDirectiveMetadata] from the `node`. `node` is expected to be an /// Reads [RenderDirectiveMetadata] from the `node`. `node` is expected to be an
/// instance of [Annotation], [NodeList<Annotation>], ListLiteral, or /// instance of [ClassDeclaration] (a class which may have a [Directive] or
/// [InstanceCreationExpression]. /// [Component] annotation) or an [InstanceCreationExpression] (an instantiation
RenderDirectiveMetadata readDirectiveMetadata(dynamic node) { /// of [ReflectionInfo]).
assert(node is Annotation || RenderDirectiveMetadata readDirectiveMetadata(AstNode node) {
node is NodeList || var visitor;
node is InstanceCreationExpression || if (node is ClassDeclaration) {
node is ListLiteral); visitor = new _DeclarationVisitor();
var visitor = new _DirectiveMetadataVisitor(); } else if (node is InstanceCreationExpression) {
visitor = new _ReflectionInfoVisitor();
} else {
throw new ArgumentError('Incorrect value passed to readDirectiveMetadata. '
'Expected types are ClassDeclaration and InstanceCreationExpression '
'Provided type was ${node.runtimeType}');
}
node.accept(visitor); node.accept(visitor);
return visitor.meta; return visitor.meta;
} }
@ -45,8 +51,79 @@ num _getDirectiveType(String annotationName, Element element) {
return byNameMatch; return byNameMatch;
} }
/// Visitor responsible for processing the `annotations` property of a class _ReflectionInfoVisitor extends _DirectiveMetadataVisitor {
/// [RegisterType] object and pulling out [RenderDirectiveMetadata]. // TODO(kegluneq): Create a more robust check that this is a real
// `ReflectionInfo` instantiation.
static bool _isReflectionInfo(InstanceCreationExpression node) {
var name = node.constructorName.type.name;
name = name is PrefixedIdentifier ? name.identifier : name;
return '$name' == 'ReflectionInfo';
}
@override
Object visitInstanceCreationExpression(InstanceCreationExpression node) {
if (_isReflectionInfo(node)) {
// NOTE(kegluneq): This is very tightly coupled with the `Reflector`
// implementation. Clean this up with a better intermediate representation
// for .ng_deps.dart files.
var reflectionInfoArgs = node.argumentList.arguments;
if (reflectionInfoArgs.length > 0) {
// Process annotations to determine information specified via
// `Component` and `Directive` parameters.
reflectionInfoArgs[0].accept(this);
if (_hasMeta && reflectionInfoArgs.length > 3) {
// Process interfaces to determine which lifecycle events we need to
// react to for this `Directive`.
_processInterfaces(reflectionInfoArgs[3]);
}
}
return null;
}
var directiveType = _getDirectiveType(
'${node.constructorName.type.name}', node.staticElement);
if (directiveType >= 0) {
if (_hasMeta) {
throw new FormatException(
'Only one Directive is allowed per class. '
'Found "$node" but already processed "$meta".',
'$node' /* source */);
}
_initializeMetadata(directiveType);
super.visitInstanceCreationExpression(node);
}
// Annotation we do not recognize - no need to visit.
return null;
}
void _processInterfaces(Expression lifecycleValue) {
_checkMeta();
if (lifecycleValue is! ListLiteral) {
throw new FormatException(
'Angular 2 expects a List but could not understand the value for interfaces. '
'$lifecycleValue');
}
ListLiteral l = lifecycleValue;
_populateLifecycle(l.elements.map((s) => s.toSource().split('.').last));
}
}
class _DeclarationVisitor extends _DirectiveMetadataVisitor {
@override
Object visitClassDeclaration(ClassDeclaration node) {
node.metadata.accept(this);
if (this._hasMeta) {
if (node.implementsClause != null &&
node.implementsClause.interfaces != null) {
_populateLifecycle(node.implementsClause.interfaces
.map((s) => s.toSource().split('.').last));
}
}
return null;
}
}
/// Visitor responsible for processing [Directive] code into a
/// [RenderDirectiveMetadata] object.
class _DirectiveMetadataVisitor extends Object class _DirectiveMetadataVisitor extends Object
with RecursiveAstVisitor<Object> { with RecursiveAstVisitor<Object> {
bool get _hasMeta => _type != null; bool get _hasMeta => _type != null;
@ -130,24 +207,6 @@ class _DirectiveMetadataVisitor extends Object
return null; return null;
} }
@override
Object visitInstanceCreationExpression(InstanceCreationExpression node) {
var directiveType = _getDirectiveType(
'${node.constructorName.type.name}', node.staticElement);
if (directiveType >= 0) {
if (_hasMeta) {
throw new FormatException(
'Only one Directive is allowed per class. '
'Found "$node" but already processed "$meta".',
'$node' /* source */);
}
_initializeMetadata(directiveType);
super.visitInstanceCreationExpression(node);
}
// Annotation we do not recognize - no need to visit.
return null;
}
@override @override
Object visitNamedExpression(NamedExpression node) { Object visitNamedExpression(NamedExpression node) {
// TODO(kegluneq): Remove this limitation. // TODO(kegluneq): Remove this limitation.
@ -170,10 +229,6 @@ class _DirectiveMetadataVisitor extends Object
case 'host': case 'host':
_populateHost(node.expression); _populateHost(node.expression);
break; break;
// TODO(vicb) should come from the interfaces on the class
case 'lifecycle':
_populateLifecycle(node.expression);
break;
case 'exportAs': case 'exportAs':
_populateExportAs(node.expression); _populateExportAs(node.expression);
break; break;
@ -207,8 +262,7 @@ class _DirectiveMetadataVisitor extends Object
if (!_hasMeta) { if (!_hasMeta) {
throw new ArgumentError( throw new ArgumentError(
'Incorrect value passed to readDirectiveMetadata. ' 'Incorrect value passed to readDirectiveMetadata. '
'Expected types are Annotation, InstanceCreationExpression, ' 'Expected types are ClassDeclaration and InstanceCreationExpression');
'NodeList or ListLiteral');
} }
} }
@ -271,23 +325,19 @@ class _DirectiveMetadataVisitor extends Object
_exportAs = _expressionToString(exportAsValue, 'Directive#exportAs'); _exportAs = _expressionToString(exportAsValue, 'Directive#exportAs');
} }
void _populateLifecycle(Expression lifecycleValue) { void _populateLifecycle(Iterable<String> lifecycleInterfaceNames) {
_checkMeta(); _checkMeta();
if (lifecycleValue is! ListLiteral) { _callOnDestroy = lifecycleInterfaceNames.contains("OnDestroy");
throw new FormatException( _callOnChange = lifecycleInterfaceNames.contains("OnChanges");
'Angular 2 expects a List but could not understand the value for lifecycle. ' _callDoCheck = lifecycleInterfaceNames.contains("DoCheck");
'$lifecycleValue'); _callOnInit = lifecycleInterfaceNames.contains("OnInit");
} _callAfterContentInit =
ListLiteral l = lifecycleValue; lifecycleInterfaceNames.contains("AfterContentInit");
var lifecycleEvents = l.elements.map((s) => s.toSource().split('.').last); _callAfterContentChecked =
_callOnDestroy = lifecycleEvents.contains("OnDestroy"); lifecycleInterfaceNames.contains("AfterContentChecked");
_callOnChange = lifecycleEvents.contains("OnChanges"); _callAfterViewInit = lifecycleInterfaceNames.contains("AfterViewInit");
_callDoCheck = lifecycleEvents.contains("DoCheck"); _callAfterViewChecked =
_callOnInit = lifecycleEvents.contains("OnInit"); lifecycleInterfaceNames.contains("AfterViewChecked");
_callAfterContentInit = lifecycleEvents.contains("AfterContentInit");
_callAfterContentChecked = lifecycleEvents.contains("AfterContentChecked");
_callAfterViewInit = lifecycleEvents.contains("AfterViewInit");
_callAfterViewChecked = lifecycleEvents.contains("AfterViewChecked");
} }
void _populateEvents(Expression eventsValue) { void _populateEvents(Expression eventsValue) {
@ -301,5 +351,6 @@ class _DirectiveMetadataVisitor extends Object
} }
} }
final Map<String, ChangeDetectionStrategy> changeDetectionStrategies final Map<String, ChangeDetectionStrategy> changeDetectionStrategies =
= new Map.fromIterable(ChangeDetectionStrategy.values, key: (v) => v.toString()); new Map.fromIterable(ChangeDetectionStrategy.values,
key: (v) => v.toString());

View File

@ -14,6 +14,9 @@ class RegisteredType {
/// The actual call to `Reflector#registerType`. /// The actual call to `Reflector#registerType`.
final MethodInvocation registerMethod; final MethodInvocation registerMethod;
/// The `ReflectionInfo` [InstanceCreationExpression]
final InstanceCreationExpression reflectionInfoCreate;
/// The factory method registered. /// The factory method registered.
final Expression factoryFn; final Expression factoryFn;
@ -25,22 +28,28 @@ class RegisteredType {
RenderDirectiveMetadata _directiveMetadata = null; RenderDirectiveMetadata _directiveMetadata = null;
RegisteredType._(this.typeName, this.registerMethod, this.factoryFn, RegisteredType._(
this.parameters, this.annotations); this.typeName,
this.registerMethod,
this.reflectionInfoCreate,
this.factoryFn,
this.parameters,
this.annotations);
/// Creates a {@link RegisteredType} given a {@link MethodInvocation} node representing /// Creates a {@link RegisteredType} given a {@link MethodInvocation} node representing
/// a call to `registerType`. /// a call to `registerType`.
factory RegisteredType.fromMethodInvocation(MethodInvocation registerMethod) { factory RegisteredType.fromMethodInvocation(MethodInvocation registerMethod) {
var visitor = new _ParseRegisterTypeVisitor(); var visitor = new _ParseRegisterTypeVisitor();
registerMethod.accept(visitor); registerMethod.accept(visitor);
return new RegisteredType._(visitor.typeName, registerMethod, return new RegisteredType._(visitor.typeName, registerMethod, visitor.info,
visitor.factoryFn, visitor.parameters, visitor.annotations); visitor.factoryFn, visitor.parameters, visitor.annotations);
} }
RenderDirectiveMetadata get directiveMetadata { RenderDirectiveMetadata get directiveMetadata {
if (_directiveMetadata == null) { if (_directiveMetadata == null) {
try { try {
_directiveMetadata = readDirectiveMetadata(annotations); /// TODO(kegluneq): Allow passing a lifecycle interface matcher.
_directiveMetadata = readDirectiveMetadata(reflectionInfoCreate);
if (_directiveMetadata != null) { if (_directiveMetadata != null) {
_directiveMetadata.id = '$typeName'; _directiveMetadata.id = '$typeName';
} }
@ -55,6 +64,7 @@ class RegisteredType {
class _ParseRegisterTypeVisitor extends Object class _ParseRegisterTypeVisitor extends Object
with RecursiveAstVisitor<Object> { with RecursiveAstVisitor<Object> {
Identifier typeName; Identifier typeName;
InstanceCreationExpression info;
Expression factoryFn; Expression factoryFn;
Expression parameters; Expression parameters;
Expression annotations; Expression annotations;
@ -68,9 +78,9 @@ class _ParseRegisterTypeVisitor extends Object
? node.argumentList.arguments[0] ? node.argumentList.arguments[0]
: null; : null;
// The second argument to a `registerType` call is the RegistrationInfo // The second argument to a `registerType` call is the `ReflectionInfo`
// object creation. // object creation.
var info = node.argumentList.arguments[1] as InstanceCreationExpression; info = node.argumentList.arguments[1] as InstanceCreationExpression;
var args = info.argumentList.arguments; var args = info.argumentList.arguments;
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
var arg = args[i]; var arg = args[i];

View File

@ -8,7 +8,6 @@ import 'package:angular2/src/core/render/xhr.dart' show XHR;
import 'package:angular2/src/transform/common/annotation_matcher.dart'; import 'package:angular2/src/transform/common/annotation_matcher.dart';
import 'package:angular2/src/transform/common/asset_reader.dart'; import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/async_string_writer.dart'; import 'package:angular2/src/transform/common/async_string_writer.dart';
import 'package:angular2/src/transform/common/interface_matcher.dart';
import 'package:angular2/src/transform/common/logging.dart'; import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart'; import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/xhr_impl.dart'; import 'package:angular2/src/transform/common/xhr_impl.dart';
@ -54,12 +53,7 @@ Future<String> createNgDeps(AssetReader reader, AssetId assetId,
var declarationsCode = var declarationsCode =
await _getAllDeclarations(reader, assetId, code, directivesVisitor); await _getAllDeclarations(reader, assetId, code, directivesVisitor);
var declarationsVisitor = new _NgDepsDeclarationsVisitor( var declarationsVisitor = new _NgDepsDeclarationsVisitor(
assetId, assetId, writer, new XhrImpl(reader, assetId), annotationMatcher, ngMeta,
writer,
new XhrImpl(reader, assetId),
annotationMatcher,
_interfaceMatcher,
ngMeta,
inlineViews: inlineViews); inlineViews: inlineViews);
parseCompilationUnit(declarationsCode, name: '${assetId.path} and parts') parseCompilationUnit(declarationsCode, name: '${assetId.path} and parts')
.declarations .declarations
@ -75,8 +69,6 @@ Future<String> createNgDeps(AssetReader reader, AssetId assetId,
return writer.asyncToString(); return writer.asyncToString();
} }
InterfaceMatcher _interfaceMatcher = new InterfaceMatcher();
/// Processes `visitor.parts`, reading and appending their contents to the /// Processes `visitor.parts`, reading and appending their contents to the
/// original `code`. /// original `code`.
/// Order of `part`s is preserved. That is, if in the main library we have /// Order of `part`s is preserved. That is, if in the main library we have
@ -278,29 +270,20 @@ class _NgDepsDeclarationsVisitor extends Object with SimpleAstVisitor<Object> {
/// Angular 2, for example `@Component`. /// Angular 2, for example `@Component`.
final AnnotationMatcher _annotationMatcher; final AnnotationMatcher _annotationMatcher;
/// Responsible for testing whether interfaces are recognized by Angular2,
/// for example `OnChanges`.
final InterfaceMatcher _interfaceMatcher;
/// Used to fetch linked files. /// Used to fetch linked files.
final XHR _xhr; final XHR _xhr;
_NgDepsDeclarationsVisitor( _NgDepsDeclarationsVisitor(AssetId assetId, AsyncStringWriter writer, XHR xhr,
AssetId assetId, AnnotationMatcher annotationMatcher, this.ngMeta,
AsyncStringWriter writer,
XHR xhr,
AnnotationMatcher annotationMatcher,
InterfaceMatcher interfaceMatcher,
this.ngMeta,
{bool inlineViews}) {bool inlineViews})
: writer = writer, : writer = writer,
_copyVisitor = new ToSourceVisitor(writer), _copyVisitor = new ToSourceVisitor(writer),
_factoryVisitor = new FactoryTransformVisitor(writer), _factoryVisitor = new FactoryTransformVisitor(writer),
_paramsVisitor = new ParameterTransformVisitor(writer), _paramsVisitor = new ParameterTransformVisitor(writer),
_metaVisitor = new AnnotationsTransformVisitor( _metaVisitor = new AnnotationsTransformVisitor(
writer, xhr, annotationMatcher, assetId, inlineViews: inlineViews), writer, xhr, annotationMatcher, assetId,
inlineViews: inlineViews),
_annotationMatcher = annotationMatcher, _annotationMatcher = annotationMatcher,
_interfaceMatcher = interfaceMatcher,
this.assetId = assetId, this.assetId = assetId,
_xhr = xhr; _xhr = xhr;
@ -445,4 +428,5 @@ class _NgDepsDeclarationsVisitor extends Object with SimpleAstVisitor<Object> {
} }
const _REF_PREFIX = '_ngRef'; const _REF_PREFIX = '_ngRef';
const _REFLECTOR_IMPORT = 'package:angular2/src/core/reflection/reflection.dart'; const _REFLECTOR_IMPORT =
'package:angular2/src/core/reflection/reflection.dart';

View File

@ -11,8 +11,9 @@ void initReflector(reflector) {
reflector reflector
..registerType( ..registerType(
HelloCmp, HelloCmp,
new ReflectionInfo( new ReflectionInfo(const [
const [const Component(changeDetection: ChangeDetectionStrategy.CheckOnce)], const Component(changeDetection: ChangeDetectionStrategy.CheckOnce)
const [const []], ], const [
() => new HelloCmp())); const []
], () => new HelloCmp()));
} }

View File

@ -12,7 +12,7 @@ void initReflector(reflector) {
..registerType( ..registerType(
HelloCmp, HelloCmp,
new ReflectionInfo(const [ new ReflectionInfo(const [
const Component(events: ['onFoo', 'onBar']) const Component(events: const ['onFoo', 'onBar'])
], const [ ], const [
const [] const []
], () => new HelloCmp())); ], () => new HelloCmp()));

View File

@ -2,7 +2,19 @@ library examples.hello_world.index_common_dart.ng_deps.dart;
import 'hello.dart'; import 'hello.dart';
import 'package:angular2/angular2.dart' import 'package:angular2/angular2.dart'
show Component, Directive, View, NgElement, LifecycleEvent; show
Component,
Directive,
View,
NgElement,
OnChanges,
OnDestroy,
OnInit,
DoCheck,
AfterContentInit,
AfterContentChecked,
AfterViewInit,
AfterViewChecked;
var _visited = false; var _visited = false;
void initReflector(reflector) { void initReflector(reflector) {
@ -11,18 +23,18 @@ void initReflector(reflector) {
reflector reflector
..registerType( ..registerType(
HelloCmp, HelloCmp,
new ReflectionInfo(const [ new ReflectionInfo(
const Component(lifecycle: [ const [const Component()],
LifecycleEvent.OnChanges, const [const []],
LifecycleEvent.OnDestroy, () => new HelloCmp(),
LifecycleEvent.OnInit, const [
LifecycleEvent.DoCheck, OnChanges,
LifecycleEvent.AfterContentInit, OnDestroy,
LifecycleEvent.AfterContentChecked, OnInit,
LifecycleEvent.AfterViewInit, DoCheck,
LifecycleEvent.AfterViewChecked AfterContentInit,
]) AfterContentChecked,
], const [ AfterViewInit,
const [] AfterViewChecked
], () => new HelloCmp())); ]));
} }