angular-docs-cn/modules/angular2/src/transform/common/directive_metadata_reader.dart

244 lines
8.2 KiB
Dart

library angular2.transform.common.directive_metadata_reader;
import 'package:analyzer/analyzer.dart';
import 'package:analyzer/src/generated/element.dart';
import 'package:angular2/src/render/api.dart';
/// Reads [DirectiveMetadata] from the `node`. `node` is expected to be an
/// instance of [Annotation], [NodeList<Annotation>], ListLiteral, or
/// [InstanceCreationExpression].
DirectiveMetadata readDirectiveMetadata(dynamic node) {
assert(node is Annotation ||
node is NodeList ||
node is InstanceCreationExpression ||
node is ListLiteral);
var visitor = new _DirectiveMetadataVisitor();
node.accept(visitor);
return visitor.meta;
}
num _getDirectiveType(String annotationName, Element element) {
var byNameMatch = -1;
// TODO(kegluneq): Detect subtypes & implementations of `Directive`s.
switch (annotationName) {
case 'Directive':
byNameMatch = DirectiveMetadata.DIRECTIVE_TYPE;
break;
case 'Component':
byNameMatch = DirectiveMetadata.COMPONENT_TYPE;
break;
default:
return -1;
}
if (element != null) {
var byResolvedAst = -1;
var libName = element.library.name;
// If we have resolved, ensure the library is correct.
if (libName == 'angular2.src.core.annotations.annotations' ||
libName == 'angular2.src.core.annotations_impl.annotations') {
byResolvedAst = byNameMatch;
}
// TODO(kegluneq): @keertip, can we expose this as a warning?
assert(byNameMatch == byResolvedAst);
}
return byNameMatch;
}
/// Visitor responsible for processing the `annotations` property of a
/// [RegisterType] object and pulling out [DirectiveMetadata].
class _DirectiveMetadataVisitor extends Object
with RecursiveAstVisitor<Object> {
DirectiveMetadata meta;
final ConstantEvaluator _evaluator = new ConstantEvaluator();
void _createEmptyMetadata(num type) {
assert(type >= 0);
meta = new DirectiveMetadata(
type: type,
compileChildren: true,
properties: [],
hostListeners: {},
hostProperties: {},
hostAttributes: {},
readAttributes: [],
callOnDestroy: false,
callOnChange: false,
callOnCheck: false,
callOnInit: false,
callOnAllChangesDone: false
);
}
@override
Object visitAnnotation(Annotation node) {
var directiveType = _getDirectiveType('${node.name}', node.element);
if (directiveType >= 0) {
if (meta != null) {
throw new FormatException('Only one Directive is allowed per class. '
'Found "$node" but already processed "$meta".',
'$node' /* source */);
}
_createEmptyMetadata(directiveType);
super.visitAnnotation(node);
}
// Annotation we do not recognize - no need to visit.
return null;
}
@override
Object visitInstanceCreationExpression(InstanceCreationExpression node) {
var directiveType = _getDirectiveType(
'${node.constructorName.type.name}', node.staticElement);
if (directiveType >= 0) {
if (meta != null) {
throw new FormatException('Only one Directive is allowed per class. '
'Found "$node" but already processed "$meta".',
'$node' /* source */);
}
_createEmptyMetadata(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.
if (node.name is! Label || node.name.label is! SimpleIdentifier) {
throw new FormatException(
'Angular 2 currently only supports simple identifiers in directives.',
'$node' /* source */);
}
var keyString = '${node.name.label}';
// TODO(kegluneq): Populate the other values in [DirectiveMetadata] once
// they are specified as `hostAttributes` and `hostSetters`.
// See [https://github.com/angular/angular/issues/1244]
switch (keyString) {
case 'selector':
_populateSelector(node.expression);
break;
case 'compileChildren':
_populateCompileChildren(node.expression);
break;
case 'properties':
_populateProperties(node.expression);
break;
case 'hostProperties':
_populateHostProperties(node.expression);
break;
case 'hostAttributes':
_populateHostAttributes(node.expression);
break;
case 'hostListeners':
_populateHostListeners(node.expression);
break;
case 'lifecycle':
_populateLifecycle(node.expression);
break;
}
return null;
}
String _expressionToString(Expression node, String nodeDescription) {
var value = node.accept(_evaluator);
if (value is! String) {
throw new FormatException('Angular 2 could not understand the value '
'in $nodeDescription.', '$node' /* source */);
}
return value;
}
void _populateSelector(Expression selectorValue) {
meta.selector = _expressionToString(selectorValue, 'Directive#selector');
}
void _checkMeta() {
if (meta == null) {
throw new ArgumentError(
'Incorrect value passed to readDirectiveMetadata. '
'Expected types are Annotation and InstanceCreationExpression');
}
}
void _populateCompileChildren(Expression compileChildrenValue) {
_checkMeta();
var evaluated = compileChildrenValue.accept(_evaluator);
if (evaluated is! bool) {
throw new FormatException(
'Angular 2 expects a bool but could not understand the value for '
'Directive#compileChildren.', '$compileChildrenValue' /* source */);
}
meta.compileChildren = evaluated;
}
/// Evaluates the [Map] represented by `expression` and adds all `key`,
/// `value` pairs to `map`. If `expression` does not evaluate to a [Map],
/// throws a descriptive [FormatException].
void _populateMap(Expression expression, Map map, String propertyName) {
var evaluated = expression.accept(_evaluator);
if (evaluated is! Map) {
throw new FormatException(
'Angular 2 expects a Map but could not understand the value for '
'$propertyName.', '$expression' /* source */);
}
evaluated.forEach((key, value) {
if (value != null) {
map[key] = '$value';
}
});
}
/// Evaluates the [List] represented by `expression` and adds all values,
/// to `list`. If `expression` does not evaluate to a [List], throws a
/// descriptive [FormatException].
void _populateList(Expression expression, List list, String propertyName) {
var evaluated = expression.accept(_evaluator);
if (evaluated is! List) {
throw new FormatException(
'Angular 2 expects a List but could not understand the value for '
'$propertyName.', '$expression' /* source */);
}
list.addAll(evaluated);
}
void _populateProperties(Expression propertiesValue) {
_checkMeta();
_populateList(propertiesValue, meta.properties, 'Directive#properties');
}
void _populateHostListeners(Expression hostListenersValue) {
_checkMeta();
_populateMap(
hostListenersValue, meta.hostListeners, 'Directive#hostListeners');
}
void _populateHostProperties(Expression hostPropertyValue) {
_checkMeta();
_populateMap(
hostPropertyValue, meta.hostProperties, 'Directive#hostProperties');
}
void _populateHostAttributes(Expression hostAttributeValue) {
_checkMeta();
_populateMap(
hostAttributeValue, meta.hostAttributes, 'Directive#hostAttributes');
}
void _populateLifecycle(Expression lifecycleValue) {
_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());
meta.callOnDestroy = lifecycleEvents.contains("onDestroy");
meta.callOnChange = lifecycleEvents.contains("onChange");
meta.callOnCheck = lifecycleEvents.contains("onCheck");
meta.callOnInit = lifecycleEvents.contains("onInit");
meta.callOnAllChangesDone = lifecycleEvents.contains("onAllChangesDone");
}
}