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>[];
for (CompilationUnitMember unitMember in unit.declarations) {
if (unitMember is ClassDeclaration) {
RenderDirectiveMetadata meta = readDirectiveMetadata(unitMember.metadata);
RenderDirectiveMetadata meta = readDirectiveMetadata(unitMember);
if (meta != null) {
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';
/// Reads [RenderDirectiveMetadata] from the `node`. `node` is expected to be an
/// instance of [Annotation], [NodeList<Annotation>], ListLiteral, or
/// [InstanceCreationExpression].
RenderDirectiveMetadata readDirectiveMetadata(dynamic node) {
assert(node is Annotation ||
node is NodeList ||
node is InstanceCreationExpression ||
node is ListLiteral);
var visitor = new _DirectiveMetadataVisitor();
/// instance of [ClassDeclaration] (a class which may have a [Directive] or
/// [Component] annotation) or an [InstanceCreationExpression] (an instantiation
/// of [ReflectionInfo]).
RenderDirectiveMetadata readDirectiveMetadata(AstNode node) {
var visitor;
if (node is ClassDeclaration) {
visitor = new _DeclarationVisitor();
} 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);
return visitor.meta;
}
@ -45,8 +51,79 @@ num _getDirectiveType(String annotationName, Element element) {
return byNameMatch;
}
/// Visitor responsible for processing the `annotations` property of a
/// [RegisterType] object and pulling out [RenderDirectiveMetadata].
class _ReflectionInfoVisitor extends _DirectiveMetadataVisitor {
// 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
with RecursiveAstVisitor<Object> {
bool get _hasMeta => _type != null;
@ -130,24 +207,6 @@ class _DirectiveMetadataVisitor extends Object
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
Object visitNamedExpression(NamedExpression node) {
// TODO(kegluneq): Remove this limitation.
@ -170,10 +229,6 @@ class _DirectiveMetadataVisitor extends Object
case 'host':
_populateHost(node.expression);
break;
// TODO(vicb) should come from the interfaces on the class
case 'lifecycle':
_populateLifecycle(node.expression);
break;
case 'exportAs':
_populateExportAs(node.expression);
break;
@ -207,8 +262,7 @@ class _DirectiveMetadataVisitor extends Object
if (!_hasMeta) {
throw new ArgumentError(
'Incorrect value passed to readDirectiveMetadata. '
'Expected types are Annotation, InstanceCreationExpression, '
'NodeList or ListLiteral');
'Expected types are ClassDeclaration and InstanceCreationExpression');
}
}
@ -271,23 +325,19 @@ class _DirectiveMetadataVisitor extends Object
_exportAs = _expressionToString(exportAsValue, 'Directive#exportAs');
}
void _populateLifecycle(Expression lifecycleValue) {
void _populateLifecycle(Iterable<String> lifecycleInterfaceNames) {
_checkMeta();
if (lifecycleValue is! ListLiteral) {
throw new FormatException(
'Angular 2 expects a List but could not understand the value for lifecycle. '
'$lifecycleValue');
}
ListLiteral l = lifecycleValue;
var lifecycleEvents = l.elements.map((s) => s.toSource().split('.').last);
_callOnDestroy = lifecycleEvents.contains("OnDestroy");
_callOnChange = lifecycleEvents.contains("OnChanges");
_callDoCheck = lifecycleEvents.contains("DoCheck");
_callOnInit = lifecycleEvents.contains("OnInit");
_callAfterContentInit = lifecycleEvents.contains("AfterContentInit");
_callAfterContentChecked = lifecycleEvents.contains("AfterContentChecked");
_callAfterViewInit = lifecycleEvents.contains("AfterViewInit");
_callAfterViewChecked = lifecycleEvents.contains("AfterViewChecked");
_callOnDestroy = lifecycleInterfaceNames.contains("OnDestroy");
_callOnChange = lifecycleInterfaceNames.contains("OnChanges");
_callDoCheck = lifecycleInterfaceNames.contains("DoCheck");
_callOnInit = lifecycleInterfaceNames.contains("OnInit");
_callAfterContentInit =
lifecycleInterfaceNames.contains("AfterContentInit");
_callAfterContentChecked =
lifecycleInterfaceNames.contains("AfterContentChecked");
_callAfterViewInit = lifecycleInterfaceNames.contains("AfterViewInit");
_callAfterViewChecked =
lifecycleInterfaceNames.contains("AfterViewChecked");
}
void _populateEvents(Expression eventsValue) {
@ -301,5 +351,6 @@ class _DirectiveMetadataVisitor extends Object
}
}
final Map<String, ChangeDetectionStrategy> changeDetectionStrategies
= new Map.fromIterable(ChangeDetectionStrategy.values, key: (v) => v.toString());
final Map<String, ChangeDetectionStrategy> changeDetectionStrategies =
new Map.fromIterable(ChangeDetectionStrategy.values,
key: (v) => v.toString());

View File

@ -14,6 +14,9 @@ class RegisteredType {
/// The actual call to `Reflector#registerType`.
final MethodInvocation registerMethod;
/// The `ReflectionInfo` [InstanceCreationExpression]
final InstanceCreationExpression reflectionInfoCreate;
/// The factory method registered.
final Expression factoryFn;
@ -25,22 +28,28 @@ class RegisteredType {
RenderDirectiveMetadata _directiveMetadata = null;
RegisteredType._(this.typeName, this.registerMethod, this.factoryFn,
this.parameters, this.annotations);
RegisteredType._(
this.typeName,
this.registerMethod,
this.reflectionInfoCreate,
this.factoryFn,
this.parameters,
this.annotations);
/// Creates a {@link RegisteredType} given a {@link MethodInvocation} node representing
/// a call to `registerType`.
factory RegisteredType.fromMethodInvocation(MethodInvocation registerMethod) {
var visitor = new _ParseRegisterTypeVisitor();
registerMethod.accept(visitor);
return new RegisteredType._(visitor.typeName, registerMethod,
return new RegisteredType._(visitor.typeName, registerMethod, visitor.info,
visitor.factoryFn, visitor.parameters, visitor.annotations);
}
RenderDirectiveMetadata get directiveMetadata {
if (_directiveMetadata == null) {
try {
_directiveMetadata = readDirectiveMetadata(annotations);
/// TODO(kegluneq): Allow passing a lifecycle interface matcher.
_directiveMetadata = readDirectiveMetadata(reflectionInfoCreate);
if (_directiveMetadata != null) {
_directiveMetadata.id = '$typeName';
}
@ -55,6 +64,7 @@ class RegisteredType {
class _ParseRegisterTypeVisitor extends Object
with RecursiveAstVisitor<Object> {
Identifier typeName;
InstanceCreationExpression info;
Expression factoryFn;
Expression parameters;
Expression annotations;
@ -68,9 +78,9 @@ class _ParseRegisterTypeVisitor extends Object
? node.argumentList.arguments[0]
: null;
// The second argument to a `registerType` call is the RegistrationInfo
// The second argument to a `registerType` call is the `ReflectionInfo`
// object creation.
var info = node.argumentList.arguments[1] as InstanceCreationExpression;
info = node.argumentList.arguments[1] as InstanceCreationExpression;
var args = info.argumentList.arguments;
for (int i = 0; i < args.length; 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/asset_reader.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/names.dart';
import 'package:angular2/src/transform/common/xhr_impl.dart';
@ -54,12 +53,7 @@ Future<String> createNgDeps(AssetReader reader, AssetId assetId,
var declarationsCode =
await _getAllDeclarations(reader, assetId, code, directivesVisitor);
var declarationsVisitor = new _NgDepsDeclarationsVisitor(
assetId,
writer,
new XhrImpl(reader, assetId),
annotationMatcher,
_interfaceMatcher,
ngMeta,
assetId, writer, new XhrImpl(reader, assetId), annotationMatcher, ngMeta,
inlineViews: inlineViews);
parseCompilationUnit(declarationsCode, name: '${assetId.path} and parts')
.declarations
@ -75,8 +69,6 @@ Future<String> createNgDeps(AssetReader reader, AssetId assetId,
return writer.asyncToString();
}
InterfaceMatcher _interfaceMatcher = new InterfaceMatcher();
/// Processes `visitor.parts`, reading and appending their contents to the
/// original `code`.
/// 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`.
final AnnotationMatcher _annotationMatcher;
/// Responsible for testing whether interfaces are recognized by Angular2,
/// for example `OnChanges`.
final InterfaceMatcher _interfaceMatcher;
/// Used to fetch linked files.
final XHR _xhr;
_NgDepsDeclarationsVisitor(
AssetId assetId,
AsyncStringWriter writer,
XHR xhr,
AnnotationMatcher annotationMatcher,
InterfaceMatcher interfaceMatcher,
this.ngMeta,
_NgDepsDeclarationsVisitor(AssetId assetId, AsyncStringWriter writer, XHR xhr,
AnnotationMatcher annotationMatcher, this.ngMeta,
{bool inlineViews})
: writer = writer,
_copyVisitor = new ToSourceVisitor(writer),
_factoryVisitor = new FactoryTransformVisitor(writer),
_paramsVisitor = new ParameterTransformVisitor(writer),
_metaVisitor = new AnnotationsTransformVisitor(
writer, xhr, annotationMatcher, assetId, inlineViews: inlineViews),
writer, xhr, annotationMatcher, assetId,
inlineViews: inlineViews),
_annotationMatcher = annotationMatcher,
_interfaceMatcher = interfaceMatcher,
this.assetId = assetId,
_xhr = xhr;
@ -445,4 +428,5 @@ class _NgDepsDeclarationsVisitor extends Object with SimpleAstVisitor<Object> {
}
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
..registerType(
HelloCmp,
new ReflectionInfo(
const [const Component(changeDetection: ChangeDetectionStrategy.CheckOnce)],
const [const []],
() => new HelloCmp()));
new ReflectionInfo(const [
const Component(changeDetection: ChangeDetectionStrategy.CheckOnce)
], const [
const []
], () => new HelloCmp()));
}

View File

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

View File

@ -2,7 +2,19 @@ library examples.hello_world.index_common_dart.ng_deps.dart;
import 'hello.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;
void initReflector(reflector) {
@ -11,18 +23,18 @@ void initReflector(reflector) {
reflector
..registerType(
HelloCmp,
new ReflectionInfo(const [
const Component(lifecycle: [
LifecycleEvent.OnChanges,
LifecycleEvent.OnDestroy,
LifecycleEvent.OnInit,
LifecycleEvent.DoCheck,
LifecycleEvent.AfterContentInit,
LifecycleEvent.AfterContentChecked,
LifecycleEvent.AfterViewInit,
LifecycleEvent.AfterViewChecked
])
], const [
const []
], () => new HelloCmp()));
new ReflectionInfo(
const [const Component()],
const [const []],
() => new HelloCmp(),
const [
OnChanges,
OnDestroy,
OnInit,
DoCheck,
AfterContentInit,
AfterContentChecked,
AfterViewInit,
AfterViewChecked
]));
}