2015-05-01 13:57:44 -07:00
|
|
|
library angular2.transform.common.directive_metadata_reader;
|
2015-04-10 16:58:52 -07:00
|
|
|
|
|
|
|
import 'package:analyzer/analyzer.dart';
|
2015-05-08 14:02:47 -07:00
|
|
|
import 'package:analyzer/src/generated/element.dart';
|
2015-04-10 16:58:52 -07:00
|
|
|
import 'package:angular2/src/render/api.dart';
|
|
|
|
|
2015-05-08 14:02:47 -07:00
|
|
|
/// 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);
|
2015-04-10 16:58:52 -07:00
|
|
|
var visitor = new _DirectiveMetadataVisitor();
|
2015-05-08 14:02:47 -07:00
|
|
|
node.accept(visitor);
|
2015-04-16 08:54:44 -07:00
|
|
|
return visitor.meta;
|
2015-04-10 16:58:52 -07:00
|
|
|
}
|
|
|
|
|
2015-05-08 14:02:47 -07:00
|
|
|
num _getDirectiveType(String annotationName, Element element) {
|
|
|
|
var byNameMatch = -1;
|
2015-04-10 16:58:52 -07:00
|
|
|
// TODO(kegluneq): Detect subtypes & implementations of `Directive`s.
|
|
|
|
switch (annotationName) {
|
2015-04-30 13:38:40 -07:00
|
|
|
case 'Directive':
|
2015-05-08 14:02:47 -07:00
|
|
|
byNameMatch = DirectiveMetadata.DIRECTIVE_TYPE;
|
|
|
|
break;
|
2015-04-10 16:58:52 -07:00
|
|
|
case 'Component':
|
2015-05-08 14:02:47 -07:00
|
|
|
byNameMatch = DirectiveMetadata.COMPONENT_TYPE;
|
|
|
|
break;
|
2015-04-10 16:58:52 -07:00
|
|
|
default:
|
|
|
|
return -1;
|
|
|
|
}
|
2015-05-08 14:02:47 -07:00
|
|
|
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;
|
2015-04-10 16:58:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Visitor responsible for processing the `annotations` property of a
|
|
|
|
/// [RegisterType] object and pulling out [DirectiveMetadata].
|
|
|
|
class _DirectiveMetadataVisitor extends Object
|
|
|
|
with RecursiveAstVisitor<Object> {
|
2015-04-16 08:54:44 -07:00
|
|
|
DirectiveMetadata meta;
|
2015-04-10 16:58:52 -07:00
|
|
|
|
2015-05-08 14:02:47 -07:00
|
|
|
void _createEmptyMetadata(num type) {
|
|
|
|
assert(type >= 0);
|
|
|
|
meta = new DirectiveMetadata(
|
|
|
|
type: type,
|
|
|
|
compileChildren: true,
|
|
|
|
properties: {},
|
|
|
|
hostListeners: {},
|
|
|
|
hostProperties: {},
|
|
|
|
hostAttributes: {},
|
|
|
|
readAttributes: []);
|
|
|
|
}
|
|
|
|
|
|
|
|
@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;
|
|
|
|
}
|
|
|
|
|
2015-04-10 16:58:52 -07:00
|
|
|
@override
|
|
|
|
Object visitInstanceCreationExpression(InstanceCreationExpression node) {
|
2015-05-08 14:02:47 -07:00
|
|
|
var directiveType = _getDirectiveType(
|
|
|
|
'${node.constructorName.type.name}', node.staticElement);
|
2015-04-10 16:58:52 -07:00
|
|
|
if (directiveType >= 0) {
|
2015-04-16 08:54:44 -07:00
|
|
|
if (meta != null) {
|
2015-05-08 14:02:47 -07:00
|
|
|
throw new FormatException('Only one Directive is allowed per class. '
|
|
|
|
'Found "$node" but already processed "$meta".',
|
|
|
|
'$node' /* source */);
|
2015-04-16 08:54:44 -07:00
|
|
|
}
|
2015-05-08 14:02:47 -07:00
|
|
|
_createEmptyMetadata(directiveType);
|
2015-04-10 16:58:52 -07:00
|
|
|
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) {
|
2015-05-08 14:02:47 -07:00
|
|
|
throw new FormatException(
|
|
|
|
'Angular 2 currently only supports simple identifiers in directives.',
|
|
|
|
'$node' /* source */);
|
2015-04-10 16:58:52 -07:00
|
|
|
}
|
|
|
|
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;
|
2015-04-21 11:47:53 -07:00
|
|
|
case 'hostProperties':
|
|
|
|
_populateHostProperties(node.expression);
|
|
|
|
break;
|
2015-05-08 09:35:44 -07:00
|
|
|
case 'hostAttributes':
|
|
|
|
_populateHostAttributes(node.expression);
|
|
|
|
break;
|
2015-04-10 16:58:52 -07:00
|
|
|
case 'hostListeners':
|
|
|
|
_populateHostListeners(node.expression);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
String _expressionToString(Expression node, String nodeDescription) {
|
|
|
|
// TODO(kegluneq): Accept more options.
|
|
|
|
if (node is! SimpleStringLiteral) {
|
2015-05-08 14:02:47 -07:00
|
|
|
throw new FormatException(
|
|
|
|
'Angular 2 currently only supports string literals '
|
|
|
|
'in $nodeDescription.', '$node' /* source */);
|
2015-04-10 16:58:52 -07:00
|
|
|
}
|
|
|
|
return stringLiteralToString(node);
|
|
|
|
}
|
|
|
|
|
|
|
|
void _populateSelector(Expression selectorValue) {
|
2015-04-16 08:54:44 -07:00
|
|
|
meta.selector = _expressionToString(selectorValue, 'Directive#selector');
|
2015-04-10 16:58:52 -07:00
|
|
|
}
|
|
|
|
|
2015-05-08 14:02:47 -07:00
|
|
|
void _checkMeta() {
|
|
|
|
if (meta == null) {
|
|
|
|
throw new ArgumentError(
|
|
|
|
'Incorrect value passed to readDirectiveMetadata. '
|
|
|
|
'Expected types are Annotation and InstanceCreationExpression');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-10 16:58:52 -07:00
|
|
|
void _populateCompileChildren(Expression compileChildrenValue) {
|
2015-05-08 14:02:47 -07:00
|
|
|
_checkMeta();
|
2015-04-10 16:58:52 -07:00
|
|
|
if (compileChildrenValue is! BooleanLiteral) {
|
2015-05-08 14:02:47 -07:00
|
|
|
throw new FormatException(
|
2015-04-10 16:58:52 -07:00
|
|
|
'Angular 2 currently only supports boolean literal values for '
|
2015-05-08 14:02:47 -07:00
|
|
|
'Directive#compileChildren.', '$compileChildrenValue' /* source */);
|
2015-04-10 16:58:52 -07:00
|
|
|
}
|
2015-04-16 08:54:44 -07:00
|
|
|
meta.compileChildren = (compileChildrenValue as BooleanLiteral).value;
|
2015-04-10 16:58:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void _populateProperties(Expression propertiesValue) {
|
2015-05-08 14:02:47 -07:00
|
|
|
_checkMeta();
|
2015-04-10 16:58:52 -07:00
|
|
|
if (propertiesValue is! MapLiteral) {
|
2015-05-08 14:02:47 -07:00
|
|
|
throw new FormatException(
|
|
|
|
'Angular 2 currently only supports map literal values for '
|
|
|
|
'Directive#properties.', '$propertiesValue' /* source */);
|
2015-04-10 16:58:52 -07:00
|
|
|
}
|
2015-04-14 13:55:15 -07:00
|
|
|
for (MapLiteralEntry entry in (propertiesValue as MapLiteral).entries) {
|
2015-04-10 16:58:52 -07:00
|
|
|
var sKey = _expressionToString(entry.key, 'Directive#properties keys');
|
|
|
|
var sVal = _expressionToString(entry.value, 'Direcive#properties values');
|
2015-04-16 08:54:44 -07:00
|
|
|
meta.properties[sKey] = sVal;
|
2015-04-10 16:58:52 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _populateHostListeners(Expression hostListenersValue) {
|
2015-05-08 14:02:47 -07:00
|
|
|
_checkMeta();
|
2015-04-10 16:58:52 -07:00
|
|
|
if (hostListenersValue is! MapLiteral) {
|
2015-05-08 14:02:47 -07:00
|
|
|
throw new FormatException(
|
|
|
|
'Angular 2 currently only supports map literal values for '
|
|
|
|
'Directive#hostListeners.', '$hostListenersValue' /* source */);
|
2015-04-10 16:58:52 -07:00
|
|
|
}
|
2015-04-14 13:55:15 -07:00
|
|
|
for (MapLiteralEntry entry in (hostListenersValue as MapLiteral).entries) {
|
2015-04-10 16:58:52 -07:00
|
|
|
var sKey = _expressionToString(entry.key, 'Directive#hostListeners keys');
|
|
|
|
var sVal =
|
|
|
|
_expressionToString(entry.value, 'Directive#hostListeners values');
|
2015-04-16 08:54:44 -07:00
|
|
|
meta.hostListeners[sKey] = sVal;
|
2015-04-10 16:58:52 -07:00
|
|
|
}
|
|
|
|
}
|
2015-04-21 11:47:53 -07:00
|
|
|
|
|
|
|
void _populateHostProperties(Expression hostPropertyValue) {
|
2015-05-08 14:02:47 -07:00
|
|
|
_checkMeta();
|
2015-04-21 11:47:53 -07:00
|
|
|
if (hostPropertyValue is! MapLiteral) {
|
2015-05-08 14:02:47 -07:00
|
|
|
throw new FormatException(
|
|
|
|
'Angular 2 currently only supports map literal values for '
|
|
|
|
'Directive#hostProperties.', '$hostPropertyValue' /* source */);
|
2015-04-21 11:47:53 -07:00
|
|
|
}
|
|
|
|
for (MapLiteralEntry entry in (hostPropertyValue as MapLiteral).entries) {
|
2015-04-24 10:28:09 -07:00
|
|
|
var sKey =
|
|
|
|
_expressionToString(entry.key, 'Directive#hostProperties keys');
|
2015-04-21 11:47:53 -07:00
|
|
|
var sVal =
|
|
|
|
_expressionToString(entry.value, 'Directive#hostProperties values');
|
|
|
|
meta.hostProperties[sKey] = sVal;
|
|
|
|
}
|
|
|
|
}
|
2015-05-08 09:35:44 -07:00
|
|
|
|
|
|
|
void _populateHostAttributes(Expression hostAttributeValue) {
|
2015-05-08 14:02:47 -07:00
|
|
|
_checkMeta();
|
2015-05-08 09:35:44 -07:00
|
|
|
if (hostAttributeValue is! MapLiteral) {
|
2015-05-08 14:02:47 -07:00
|
|
|
throw new FormatException(
|
|
|
|
'Angular 2 currently only supports map literal values for '
|
|
|
|
'Directive#hostAttributes.', '$hostAttributeValue' /* source */);
|
2015-05-08 09:35:44 -07:00
|
|
|
}
|
|
|
|
for (MapLiteralEntry entry in (hostAttributeValue as MapLiteral).entries) {
|
|
|
|
var sKey =
|
|
|
|
_expressionToString(entry.key, 'Directive#hostAttributes keys');
|
|
|
|
var sVal =
|
|
|
|
_expressionToString(entry.value, 'Directive#hostAttributes values');
|
|
|
|
meta.hostAttributes[sKey] = sVal;
|
|
|
|
}
|
|
|
|
}
|
2015-04-10 16:58:52 -07:00
|
|
|
}
|