feat(dart/transform): Parse directives dependencies from the Dart ast

Previously, we parsed dependencies out of a the stringified value of
`directives`, which is brittle and error-prone.

Move this parsing into `DirectiveProcessor` where we have the full Dart
ast to help.
This commit is contained in:
Tim Blasi
2015-10-15 14:21:20 -07:00
parent ca5e31bc77
commit 26044026c9
9 changed files with 221 additions and 135 deletions

View File

@ -83,6 +83,10 @@ class ReflectionInfoVisitor extends RecursiveAstVisitor<ReflectionInfoModel> {
if (node.metadata != null) {
node.metadata.forEach((node) {
final extractedDirectives = _extractDirectives(node);
if (extractedDirectives != null) {
model.directives.addAll(extractedDirectives);
}
model.annotations.add(_annotationVisitor.visitAnnotation(node));
});
}
@ -137,6 +141,36 @@ class ReflectionInfoVisitor extends RecursiveAstVisitor<ReflectionInfoModel> {
}
}
Iterable<PrefixedDirective> _extractDirectives(Annotation node) {
var shouldProcess = _annotationMatcher.isComponent(node, assetId);
shouldProcess = shouldProcess || _annotationMatcher.isView(node, assetId);
if (node.arguments == null && node.arguments.arguments == null) return null;
final directivesNode = node.arguments.arguments.firstWhere((arg) {
return arg is NamedExpression && '${arg.name.label}' == 'directives';
}, orElse: () => null);
if (directivesNode == null) return null;
if (directivesNode.expression is! ListLiteral) {
logger.warning('Angular 2 expects a list literal for `directives` '
'but found a ${directivesNode.expression.runtimeType}');
return null;
}
final directives = <PrefixedDirective>[];
for (var dep in (directivesNode.expression as ListLiteral).elements) {
if (dep is PrefixedIdentifier) {
directives.add(new PrefixedDirective()
..prefix = '${dep.prefix}'
..name = '${dep.identifier}');
} else if (dep is Identifier) {
directives.add(new PrefixedDirective()..name = '${dep}');
} else {
logger.warning('Found unexpected value $dep in `directives`.');
}
}
return directives;
}
@override
ReflectionInfoModel visitFunctionDeclaration(FunctionDeclaration node) {
if (!node.metadata

View File

@ -52,6 +52,55 @@ class PropertyMetadataModel extends GeneratedMessage {
class _ReadonlyPropertyMetadataModel extends PropertyMetadataModel
with ReadonlyMessageMixin {}
class PrefixedDirective extends GeneratedMessage {
static final BuilderInfo _i = new BuilderInfo('PrefixedDirective')
..a(1, 'prefix', PbFieldType.OS)
..a(2, 'name', PbFieldType.OS)
..hasRequiredFields = false;
PrefixedDirective() : super();
PrefixedDirective.fromBuffer(List<int> i,
[ExtensionRegistry r = ExtensionRegistry.EMPTY])
: super.fromBuffer(i, r);
PrefixedDirective.fromJson(String i,
[ExtensionRegistry r = ExtensionRegistry.EMPTY])
: super.fromJson(i, r);
PrefixedDirective clone() => new PrefixedDirective()..mergeFromMessage(this);
BuilderInfo get info_ => _i;
static PrefixedDirective create() => new PrefixedDirective();
static PbList<PrefixedDirective> createRepeated() =>
new PbList<PrefixedDirective>();
static PrefixedDirective getDefault() {
if (_defaultInstance == null) _defaultInstance =
new _ReadonlyPrefixedDirective();
return _defaultInstance;
}
static PrefixedDirective _defaultInstance;
static void $checkItem(PrefixedDirective v) {
if (v is! PrefixedDirective) checkItemFailed(v, 'PrefixedDirective');
}
String get prefix => $_get(0, 1, '');
void set prefix(String v) {
$_setString(0, 1, v);
}
bool hasPrefix() => $_has(0, 1);
void clearPrefix() => clearField(1);
String get name => $_get(1, 2, '');
void set name(String v) {
$_setString(1, 2, v);
}
bool hasName() => $_has(1, 2);
void clearName() => clearField(2);
}
class _ReadonlyPrefixedDirective extends PrefixedDirective
with ReadonlyMessageMixin {}
class ReflectionInfoModel extends GeneratedMessage {
static final BuilderInfo _i = new BuilderInfo('ReflectionInfoModel')
..a(1, 'name', PbFieldType.QS)
@ -63,7 +112,9 @@ class ReflectionInfoModel extends GeneratedMessage {
ParameterModel.create)
..p(6, 'interfaces', PbFieldType.PS)
..pp(7, 'propertyMetadata', PbFieldType.PM,
PropertyMetadataModel.$checkItem, PropertyMetadataModel.create);
PropertyMetadataModel.$checkItem, PropertyMetadataModel.create)
..pp(8, 'directives', PbFieldType.PM, PrefixedDirective.$checkItem,
PrefixedDirective.create);
ReflectionInfoModel() : super();
ReflectionInfoModel.fromBuffer(List<int> i,
@ -120,6 +171,8 @@ class ReflectionInfoModel extends GeneratedMessage {
List<String> get interfaces => $_get(5, 6, null);
List<PropertyMetadataModel> get propertyMetadata => $_get(6, 7, null);
List<PrefixedDirective> get directives => $_get(7, 8, null);
}
class _ReadonlyReflectionInfoModel extends ReflectionInfoModel
@ -139,6 +192,14 @@ const PropertyMetadataModel$json = const {
],
};
const PrefixedDirective$json = const {
'1': 'PrefixedDirective',
'2': const [
const {'1': 'prefix', '3': 1, '4': 1, '5': 9},
const {'1': 'name', '3': 2, '4': 1, '5': 9},
],
};
const ReflectionInfoModel$json = const {
'1': 'ReflectionInfoModel',
'2': const [
@ -167,12 +228,19 @@ const ReflectionInfoModel$json = const {
'5': 11,
'6': '.angular2.src.transform.common.model.proto.PropertyMetadataModel'
},
const {
'1': 'directives',
'3': 8,
'4': 3,
'5': 11,
'6': '.angular2.src.transform.common.model.proto.PrefixedDirective'
},
],
};
/**
* Generated with:
* reflection_info_model.proto (71d723738054f1276f792a2672a956ef9be94a4c)
* reflection_info_model.proto (e81bf93b6872b2bd5fabc6625be2560bacc3d186)
* libprotoc 2.6.1
* dart-protoc-plugin (af5fc2bf1de367a434c3b1847ab260510878ffc0)
*/

View File

@ -13,6 +13,15 @@ message PropertyMetadataModel {
repeated AnnotationModel annotations = 2;
}
message PrefixedDirective {
// The prefix used to reference this Directive, if any.
optional string prefix = 1;
// The name of the Directive or directive alias.
// See https://goo.gl/d8XPt0 for info on directive aliases.
optional string name = 2;
}
message ReflectionInfoModel {
// The (potentially prefixed) name of this Injectable.
// This can be a `Type` or a function name.
@ -32,4 +41,8 @@ message ReflectionInfoModel {
// Entries for all properties with associated metadata.
repeated PropertyMetadataModel propertyMetadata = 7;
// Directive dependencies parsed from the @View or @Component `directives`
// parameter.
repeated PrefixedDirective directives = 8;
}

View File

@ -38,58 +38,6 @@ class CompileDataResults {
this.ngMeta, this.viewDefinitions, this.directiveMetadatas);
}
class _PrefixedDirectiveName {
final String prefix;
final String directiveName;
_PrefixedDirectiveName(String prefix, this.directiveName)
: this.prefix = prefix == null ? '' : prefix;
@override
String toString() {
if (prefix.isEmpty) {
return directiveName;
} else {
return '${prefix}.${directiveName}';
}
}
}
String _directivesValue(ReflectionInfoModel model, bool test(AnnotationModel)) {
final viewAnnotation = model.annotations.firstWhere(test, orElse: () => null);
if (viewAnnotation == null) return null;
final directivesParam = viewAnnotation.namedParameters
.firstWhere((p) => p.name == 'directives', orElse: () => null);
return directivesParam != null ? directivesParam.value : null;
}
// TODO(kegluneq): Parse this value when creating [NgDepsModel]?
/// Find the `directives` parameter on the @View annotation and make sure that
/// all necessary [CompileDirectiveMetadata] objects are available.
Iterable<_PrefixedDirectiveName> _getDirectiveDeps(ReflectionInfoModel model) {
var directives = _directivesValue(model, (m) => m.isView);
if (directives == null) {
directives = _directivesValue(model, (m) => m.isComponent);
}
if (directives == null) return const [];
directives = directives.trim();
for (var toTrim in ['const [', '[']) {
if (directives.startsWith(toTrim) && directives.endsWith(']')) {
directives =
directives.substring(toTrim.length, directives.length - 1).trim();
}
}
if (directives.length == 0) return const [];
return directives.split(',').map((p) {
var parts = p.trim().split('.');
if (parts.length == 1) {
return new _PrefixedDirectiveName(null, parts[0]);
} else {
return new _PrefixedDirectiveName(parts[0], parts[1]);
}
});
}
/// Creates [ViewDefinition] objects for all `View` `Directive`s defined in
/// `entryPoint`.
class _CompileDataCreator {
@ -127,7 +75,7 @@ class _CompileDataCreator {
if (compileDirectiveMetadata.template != null) {
final compileDatum = new NormalizedComponentWithViewDirectives(
compileDirectiveMetadata, <CompileDirectiveMetadata>[]);
for (var dep in _getDirectiveDeps(reflectable)) {
for (var dep in reflectable.directives) {
if (!ngMetaMap.containsKey(dep.prefix)) {
logger.warning(
'Missing prefix "${dep.prefix}" '
@ -137,11 +85,10 @@ class _CompileDataCreator {
}
final depNgMeta = ngMetaMap[dep.prefix];
if (depNgMeta.types.containsKey(dep.directiveName)) {
compileDatum.directives.add(depNgMeta.types[dep.directiveName]);
} else if (depNgMeta.aliases.containsKey(dep.directiveName)) {
compileDatum.directives
.addAll(depNgMeta.flatten(dep.directiveName));
if (depNgMeta.types.containsKey(dep.name)) {
compileDatum.directives.add(depNgMeta.types[dep.name]);
} else if (depNgMeta.aliases.containsKey(dep.name)) {
compileDatum.directives.addAll(depNgMeta.flatten(dep.name));
} else {
logger.warning('Could not find Directive entry for $dep. '
'Please be aware that Dart transformers have limited support for '