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:
@ -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
|
||||
|
@ -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)
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 '
|
||||
|
Reference in New Issue
Block a user