feat(dart/transform): Reuse readDirectiveMetadata in plugin
Share code for parsing `DirectiveMetadata` values between the transformer and the analyzer plugin.
This commit is contained in:
parent
75e9d3f634
commit
abc8878547
|
@ -5,8 +5,7 @@ import 'types.dart';
|
|||
import 'dart:mirrors';
|
||||
|
||||
class ReflectionCapabilities {
|
||||
ReflectionCapabilities([metadataReader]) {
|
||||
}
|
||||
ReflectionCapabilities([metadataReader]) {}
|
||||
|
||||
Function factory(Type type) {
|
||||
ClassMirror classMirror = reflectType(type);
|
||||
|
|
|
@ -1,30 +1,47 @@
|
|||
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';
|
||||
import 'logging.dart';
|
||||
import 'parser.dart';
|
||||
|
||||
/// Reads [DirectiveMetadata] from the `attributes` of `t`.
|
||||
DirectiveMetadata readDirectiveMetadata(RegisteredType t) {
|
||||
/// 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();
|
||||
t.annotations.accept(visitor);
|
||||
if (visitor.meta != null) {
|
||||
visitor.meta.id = '${t.typeName}';
|
||||
}
|
||||
node.accept(visitor);
|
||||
return visitor.meta;
|
||||
}
|
||||
|
||||
num _getDirectiveType(String annotationName) {
|
||||
num _getDirectiveType(String annotationName, Element element) {
|
||||
var byNameMatch = -1;
|
||||
// TODO(kegluneq): Detect subtypes & implementations of `Directive`s.
|
||||
switch (annotationName) {
|
||||
case 'Directive':
|
||||
return DirectiveMetadata.DIRECTIVE_TYPE;
|
||||
byNameMatch = DirectiveMetadata.DIRECTIVE_TYPE;
|
||||
break;
|
||||
case 'Component':
|
||||
return DirectiveMetadata.COMPONENT_TYPE;
|
||||
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
|
||||
|
@ -33,22 +50,45 @@ class _DirectiveMetadataVisitor extends Object
|
|||
with RecursiveAstVisitor<Object> {
|
||||
DirectiveMetadata meta;
|
||||
|
||||
@override
|
||||
Object visitInstanceCreationExpression(InstanceCreationExpression node) {
|
||||
var directiveType = _getDirectiveType('${node.constructorName.type.name}');
|
||||
if (directiveType >= 0) {
|
||||
if (meta != null) {
|
||||
logger.error('Only one Directive is allowed per class. '
|
||||
'Found "$node" but already processed "$meta".');
|
||||
}
|
||||
void _createEmptyMetadata(num type) {
|
||||
assert(type >= 0);
|
||||
meta = new DirectiveMetadata(
|
||||
type: directiveType,
|
||||
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;
|
||||
}
|
||||
|
||||
@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.
|
||||
|
@ -59,10 +99,9 @@ class _DirectiveMetadataVisitor extends Object
|
|||
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;
|
||||
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
|
||||
|
@ -93,9 +132,9 @@ class _DirectiveMetadataVisitor extends Object
|
|||
String _expressionToString(Expression node, String nodeDescription) {
|
||||
// TODO(kegluneq): Accept more options.
|
||||
if (node is! SimpleStringLiteral) {
|
||||
logger.error('Angular 2 currently only supports string literals '
|
||||
'in $nodeDescription. Source: ${node}');
|
||||
return null;
|
||||
throw new FormatException(
|
||||
'Angular 2 currently only supports string literals '
|
||||
'in $nodeDescription.', '$node' /* source */);
|
||||
}
|
||||
return stringLiteralToString(node);
|
||||
}
|
||||
|
@ -104,23 +143,30 @@ class _DirectiveMetadataVisitor extends Object
|
|||
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();
|
||||
if (compileChildrenValue is! BooleanLiteral) {
|
||||
logger.error(
|
||||
throw new FormatException(
|
||||
'Angular 2 currently only supports boolean literal values for '
|
||||
'Directive#compileChildren.'
|
||||
' Source: ${compileChildrenValue}');
|
||||
return;
|
||||
'Directive#compileChildren.', '$compileChildrenValue' /* source */);
|
||||
}
|
||||
meta.compileChildren = (compileChildrenValue as BooleanLiteral).value;
|
||||
}
|
||||
|
||||
void _populateProperties(Expression propertiesValue) {
|
||||
_checkMeta();
|
||||
if (propertiesValue is! MapLiteral) {
|
||||
logger.error('Angular 2 currently only supports map literal values for '
|
||||
'Directive#properties.'
|
||||
' Source: ${propertiesValue}');
|
||||
return;
|
||||
throw new FormatException(
|
||||
'Angular 2 currently only supports map literal values for '
|
||||
'Directive#properties.', '$propertiesValue' /* source */);
|
||||
}
|
||||
for (MapLiteralEntry entry in (propertiesValue as MapLiteral).entries) {
|
||||
var sKey = _expressionToString(entry.key, 'Directive#properties keys');
|
||||
|
@ -130,11 +176,11 @@ class _DirectiveMetadataVisitor extends Object
|
|||
}
|
||||
|
||||
void _populateHostListeners(Expression hostListenersValue) {
|
||||
_checkMeta();
|
||||
if (hostListenersValue is! MapLiteral) {
|
||||
logger.error('Angular 2 currently only supports map literal values for '
|
||||
'Directive#hostListeners.'
|
||||
' Source: ${hostListenersValue}');
|
||||
return;
|
||||
throw new FormatException(
|
||||
'Angular 2 currently only supports map literal values for '
|
||||
'Directive#hostListeners.', '$hostListenersValue' /* source */);
|
||||
}
|
||||
for (MapLiteralEntry entry in (hostListenersValue as MapLiteral).entries) {
|
||||
var sKey = _expressionToString(entry.key, 'Directive#hostListeners keys');
|
||||
|
@ -145,11 +191,11 @@ class _DirectiveMetadataVisitor extends Object
|
|||
}
|
||||
|
||||
void _populateHostProperties(Expression hostPropertyValue) {
|
||||
_checkMeta();
|
||||
if (hostPropertyValue is! MapLiteral) {
|
||||
logger.error('Angular 2 currently only supports map literal values for '
|
||||
'Directive#hostProperties.'
|
||||
' Source: ${hostPropertyValue}');
|
||||
return;
|
||||
throw new FormatException(
|
||||
'Angular 2 currently only supports map literal values for '
|
||||
'Directive#hostProperties.', '$hostPropertyValue' /* source */);
|
||||
}
|
||||
for (MapLiteralEntry entry in (hostPropertyValue as MapLiteral).entries) {
|
||||
var sKey =
|
||||
|
@ -161,11 +207,11 @@ class _DirectiveMetadataVisitor extends Object
|
|||
}
|
||||
|
||||
void _populateHostAttributes(Expression hostAttributeValue) {
|
||||
_checkMeta();
|
||||
if (hostAttributeValue is! MapLiteral) {
|
||||
logger.error('Angular 2 currently only supports map literal values for '
|
||||
'Directive#hostAttributes.'
|
||||
' Source: ${hostAttributeValue}');
|
||||
return;
|
||||
throw new FormatException(
|
||||
'Angular 2 currently only supports map literal values for '
|
||||
'Directive#hostAttributes.', '$hostAttributeValue' /* source */);
|
||||
}
|
||||
for (MapLiteralEntry entry in (hostAttributeValue as MapLiteral).entries) {
|
||||
var sKey =
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
library angular2.transform.common.registered_type;
|
||||
|
||||
import 'package:analyzer/analyzer.dart';
|
||||
import 'package:angular2/src/render/api.dart';
|
||||
import 'package:angular2/src/transform/common/directive_metadata_reader.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
|
||||
/// A call to `Reflector#registerType` generated by `DirectiveProcessor`.
|
||||
|
@ -16,6 +19,8 @@ class RegisteredType {
|
|||
/// The annotations registered.
|
||||
final Expression annotations;
|
||||
|
||||
DirectiveMetadata _directiveMetadata = null;
|
||||
|
||||
RegisteredType._(this.typeName, this.registerMethod, this.factoryFn,
|
||||
this.parameters, this.annotations);
|
||||
|
||||
|
@ -27,6 +32,20 @@ class RegisteredType {
|
|||
return new RegisteredType._(visitor.typeName, registerMethod,
|
||||
visitor.factoryFn, visitor.parameters, visitor.annotations);
|
||||
}
|
||||
|
||||
DirectiveMetadata get directiveMetadata {
|
||||
if (_directiveMetadata == null) {
|
||||
try {
|
||||
_directiveMetadata = readDirectiveMetadata(annotations);
|
||||
if (_directiveMetadata != null) {
|
||||
_directiveMetadata.id = '$typeName';
|
||||
}
|
||||
} on FormatException catch (ex) {
|
||||
logger.error(ex.message);
|
||||
}
|
||||
}
|
||||
return _directiveMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
class _ParseRegisterTypeVisitor extends Object
|
||||
|
|
|
@ -5,7 +5,6 @@ import 'dart:async';
|
|||
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/directive_metadata_reader.dart';
|
||||
import 'package:angular2/src/transform/common/logging.dart';
|
||||
import 'package:angular2/src/transform/common/names.dart';
|
||||
import 'package:angular2/src/transform/common/parser.dart';
|
||||
|
@ -58,9 +57,8 @@ Map<String, DirectiveMetadata> _metadataMapFromNgDeps(NgDeps ngDeps) {
|
|||
if (ngDeps == null || ngDeps.registeredTypes.isEmpty) return null;
|
||||
var retVal = <String, DirectiveMetadata>{};
|
||||
ngDeps.registeredTypes.forEach((rType) {
|
||||
var meta = readDirectiveMetadata(rType);
|
||||
if (meta != null) {
|
||||
retVal['${rType.typeName}'] = meta;
|
||||
if (rType.directiveMetadata != null) {
|
||||
retVal['${rType.typeName}'] = rType.directiveMetadata;
|
||||
}
|
||||
});
|
||||
return retVal;
|
||||
|
|
|
@ -24,7 +24,7 @@ void allTests() {
|
|||
|
||||
Future<DirectiveMetadata> readMetadata(inputPath) async {
|
||||
var ngDeps = await parser.parse(new AssetId('a', inputPath));
|
||||
return readDirectiveMetadata(ngDeps.registeredTypes.first);
|
||||
return ngDeps.registeredTypes.first.directiveMetadata;
|
||||
}
|
||||
|
||||
describe('readMetadata', () {
|
||||
|
@ -44,17 +44,17 @@ void allTests() {
|
|||
// Unset value defaults to `true`.
|
||||
it.moveNext();
|
||||
expect('${it.current.typeName}').toEqual('UnsetComp');
|
||||
var unsetComp = readDirectiveMetadata(it.current);
|
||||
var unsetComp = it.current.directiveMetadata;
|
||||
expect(unsetComp.compileChildren).toBeTrue();
|
||||
|
||||
it.moveNext();
|
||||
expect('${it.current.typeName}').toEqual('FalseComp');
|
||||
var falseComp = readDirectiveMetadata(it.current);
|
||||
var falseComp = it.current.directiveMetadata;
|
||||
expect(falseComp.compileChildren).toBeFalse();
|
||||
|
||||
it.moveNext();
|
||||
expect('${it.current.typeName}').toEqual('TrueComp');
|
||||
var trueComp = readDirectiveMetadata(it.current);
|
||||
var trueComp = it.current.directiveMetadata;
|
||||
expect(trueComp.compileChildren).toBeTrue();
|
||||
});
|
||||
|
||||
|
@ -85,8 +85,8 @@ void allTests() {
|
|||
var ngDeps = await parser.parse(new AssetId('a',
|
||||
'directive_metadata_extractor/'
|
||||
'directive_metadata_files/too_many_directives.ng_deps.dart'));
|
||||
expect(() => readDirectiveMetadata(ngDeps.registeredTypes.first))
|
||||
.toThrowWith(anInstanceOf: PrintLoggerError);
|
||||
expect(() => ngDeps.registeredTypes.first.directiveMetadata).toThrowWith(
|
||||
anInstanceOf: PrintLoggerError);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
library angular2.src.analysis.analyzer_plugin.src.tasks;
|
||||
|
||||
import 'package:analyzer/src/generated/ast.dart' hide Directive;
|
||||
import 'package:analyzer/src/generated/element.dart';
|
||||
import 'package:analyzer/src/generated/engine.dart';
|
||||
import 'package:analyzer/src/task/general.dart';
|
||||
import 'package:analyzer/task/dart.dart';
|
||||
import 'package:analyzer/task/model.dart';
|
||||
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||
import 'package:angular2/src/render/api.dart';
|
||||
import 'package:angular2/src/transform/common/directive_metadata_reader.dart';
|
||||
|
||||
/// The [DirectiveMetadata]s of a [LibrarySpecificUnit].
|
||||
final ListResultDescriptor<DirectiveMetadata> DIRECTIVES =
|
||||
|
@ -33,74 +32,15 @@ class BuildUnitDirectivesTask extends SourceBasedAnalysisTask {
|
|||
List<DirectiveMetadata> metaList = <DirectiveMetadata>[];
|
||||
for (CompilationUnitMember unitMember in unit.declarations) {
|
||||
if (unitMember is ClassDeclaration) {
|
||||
for (Annotation annotationNode in unitMember.metadata) {
|
||||
Directive directive = _createDirective(annotationNode);
|
||||
if (directive != null) {
|
||||
DirectiveMetadata meta = new DirectiveMetadata(
|
||||
type: _getDirectiveType(directive),
|
||||
selector: directive.selector);
|
||||
DirectiveMetadata meta = readDirectiveMetadata(unitMember.metadata);
|
||||
if (meta != null) {
|
||||
metaList.add(meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
outputs[DIRECTIVES] = metaList;
|
||||
}
|
||||
|
||||
/// Returns an Angular [Directive] that corresponds to the given [node].
|
||||
/// Returns `null` if not an Angular annotation.
|
||||
Directive _createDirective(Annotation node) {
|
||||
// TODO(scheglov) add support for all arguments
|
||||
if (_isAngularAnnotation(node, 'Component')) {
|
||||
String selector = _getNamedArgument(node, 'selector');
|
||||
return new Component(selector: selector);
|
||||
}
|
||||
if (_isAngularAnnotation(node, 'Directive')) {
|
||||
String selector = _getNamedArgument(node, 'selector');
|
||||
return new Directive(selector: selector);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
int _getDirectiveType(Directive directive) {
|
||||
if (directive is Component) {
|
||||
return DirectiveMetadata.COMPONENT_TYPE;
|
||||
}
|
||||
return DirectiveMetadata.DIRECTIVE_TYPE;
|
||||
}
|
||||
|
||||
/// Returns the value of an argument with the given [name].
|
||||
/// Returns `null` if not found or cannot be evaluated statically.
|
||||
Object _getNamedArgument(Annotation node, String name) {
|
||||
if (node.arguments != null) {
|
||||
List<Expression> arguments = node.arguments.arguments;
|
||||
for (Expression argument in arguments) {
|
||||
if (argument is NamedExpression &&
|
||||
argument.name != null &&
|
||||
argument.name.label != null &&
|
||||
argument.name.label.name == name) {
|
||||
Expression expression = argument.expression;
|
||||
if (expression is SimpleStringLiteral) {
|
||||
return expression.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Returns `true` is the given [node] is resolved to a creation of an Angular
|
||||
/// annotation class with the given [name].
|
||||
bool _isAngularAnnotation(Annotation node, String name) {
|
||||
if (node.element is ConstructorElement) {
|
||||
ClassElement clazz = node.element.enclosingElement;
|
||||
return clazz.library.name ==
|
||||
'angular2.src.core.annotations.annotations' &&
|
||||
clazz.name == name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static Map<String, TaskInput> buildInputs(LibrarySpecificUnit target) {
|
||||
return <String, TaskInput>{UNIT_INPUT: RESOLVED_UNIT.of(target)};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue