diff --git a/modules_dart/transform/lib/src/transform/common/code/ng_deps_code.dart b/modules_dart/transform/lib/src/transform/common/code/ng_deps_code.dart index f0e2a98b6d..8d5c48fc68 100644 --- a/modules_dart/transform/lib/src/transform/common/code/ng_deps_code.dart +++ b/modules_dart/transform/lib/src/transform/common/code/ng_deps_code.dart @@ -12,20 +12,23 @@ import 'annotation_code.dart'; import 'import_export_code.dart'; import 'reflection_info_code.dart'; import 'parameter_code.dart'; +import 'queries_code.dart'; /// Visitor responsible for parsing source Dart files (that is, not /// `.ng_deps.dart` files) into [NgDepsModel] objects. class NgDepsVisitor extends RecursiveAstVisitor { final AssetId processedFile; - final ImportVisitor _importVisitor = new ImportVisitor(); - final ExportVisitor _exportVisitor = new ExportVisitor(); + final _importVisitor = new ImportVisitor(); + final _exportVisitor = new ExportVisitor(); final ReflectionInfoVisitor _reflectableVisitor; + final QueriesVisitor _queriesVisitor; bool _isPart = false; NgDepsModel _model = null; NgDepsVisitor(AssetId processedFile, AnnotationMatcher annotationMatcher) : this.processedFile = processedFile, + _queriesVisitor = new QueriesVisitor(processedFile, annotationMatcher), _reflectableVisitor = new ReflectionInfoVisitor(processedFile, annotationMatcher); @@ -48,6 +51,14 @@ class NgDepsVisitor extends RecursiveAstVisitor { var reflectableModel = _reflectableVisitor.visitClassDeclaration(node); if (reflectableModel != null) { model.reflectables.add(reflectableModel); + var queryFields = _queriesVisitor.visitClassDeclaration(node); + if (queryFields != null) { + for (var queryField in queryFields) { + if (!model.setters.contains(queryField)) { + model.setters.add(queryField); + } + } + } } return null; } @@ -155,9 +166,39 @@ abstract class NgDepsWriterMixin ..writeln('void ${SETUP_METHOD_NAME}() {') ..writeln('if (_visited) return; _visited = true;'); - if (model.reflectables != null && model.reflectables.isNotEmpty) { + final needsReceiver = (model.reflectables != null && + model.reflectables.isNotEmpty) || + (model.getters != null && model.getters.isNotEmpty) || + (model.setters != null && model.setters.isNotEmpty) || + (model.methods != null && model.methods.isNotEmpty); + + if (needsReceiver) { buffer.writeln('$REFLECTOR_PREFIX.$REFLECTOR_VAR_NAME'); + } + + if (model.reflectables != null && model.reflectables.isNotEmpty) { model.reflectables.forEach(writeRegistration); + } + + if (model.getters != null && model.getters.isNotEmpty) { + buffer.writeln('..registerGetters({' + '${model.getters.map((g) => "'$g': (o) => o.$g").join(', ')}' + '})'); + } + + if (model.setters != null && model.setters.isNotEmpty) { + buffer.writeln('..registerSetters({' + '${model.setters.map((g) => "'$g': (o, v) => o.$g = v").join(', ')}' + '})'); + } + + if (model.methods != null && model.methods.isNotEmpty) { + buffer.writeln('..registerMethods({' + '${model.methods.map((g) => "'$g': (o, args) => o.$g.apply(args)").join(', ')}' + '})'); + } + + if (needsReceiver) { buffer.writeln(';'); } diff --git a/modules_dart/transform/lib/src/transform/common/code/queries_code.dart b/modules_dart/transform/lib/src/transform/common/code/queries_code.dart new file mode 100644 index 0000000000..86b943c8fe --- /dev/null +++ b/modules_dart/transform/lib/src/transform/common/code/queries_code.dart @@ -0,0 +1,136 @@ +library angular2.transform.common.code.queries_code; + +import 'package:analyzer/analyzer.dart'; +import 'package:analyzer/src/generated/ast.dart'; +import 'package:angular2/src/transform/common/annotation_matcher.dart'; +import 'package:angular2/src/transform/common/dumb_eval.dart'; +import 'package:barback/barback.dart'; + +/// Visitor responsbile for processing a [ClassDeclaration] and extracting any +/// query fields it defines. +class QueriesVisitor extends RecursiveAstVisitor> { + final _QueriesAnnotationVisitor _annotationVisitor; + final _propertyVisitor = new _QueriesPropertyVisitor(); + + QueriesVisitor(AssetId assetId, AnnotationMatcher annotationMatcher) + : _annotationVisitor = + new _QueriesAnnotationVisitor(assetId, annotationMatcher); + + @override + Iterable visitClassDeclaration(ClassDeclaration node) { + final queryFields = new Set(); + if (node.metadata != null) { + for (var annotation in node.metadata) { + var annotationQueryFields = + _annotationVisitor.visitAnnotation(annotation); + if (annotationQueryFields != null) { + queryFields.addAll(annotationQueryFields); + } + } + } + + // Record annotations attached to properties. + if (node.members != null) { + for (var member in node.members) { + var queryProp = member.accept(_propertyVisitor); + if (queryProp != null) { + queryFields.add(queryProp); + } + } + } + + return queryFields.isNotEmpty ? queryFields : null; + } +} + +/// Visitor responsbile for processing properties and getters on a +/// [ClassDeclaration] and extracting any query fields it contains. +class _QueriesPropertyVisitor extends SimpleAstVisitor { + @override + String visitFieldDeclaration(FieldDeclaration node) { + for (var variable in node.fields.variables) { + for (var meta in node.metadata) { + if (_isQueryAnnotation(meta)) { + return '${variable.name}'; + } + } + } + return null; + } + + @override + String visitMethodDeclaration(MethodDeclaration node) { + if (node.isGetter || node.isSetter) { + for (var meta in node.metadata) { + if (_isQueryAnnotation(meta)) { + return '${node.name}'; + } + } + } + return null; + } + + bool _isQueryAnnotation(Annotation node) { + // TODO(kegluenq): Use ClassMatcherBase to ensure this is a match. + var id = node.name; + final name = id is PrefixedIdentifier ? '${id.identifier}' : '$id'; + switch (name) { + case "ContentChild": + case "ViewChild": + case "ContentChildren": + case "ViewChildren": + return true; + default: + return false; + } + } +} + +/// Visitor responsible for processing the [Annotation]s on a [ClassDeclaration] +/// and extracting any query fields it contains. +class _QueriesAnnotationVisitor extends SimpleAstVisitor> { + /// The file we are processing. + final AssetId assetId; + + /// Responsible for testing whether [Annotation]s are those recognized by + /// Angular 2, for example `@Component`. + final AnnotationMatcher _annotationMatcher; + + /// All currently found query fields. + Set _queryFields = null; + + _QueriesAnnotationVisitor(this.assetId, this._annotationMatcher); + + @override + Iterable visitAnnotation(Annotation node) { + var queryFields = null; + if (_annotationMatcher.isView(node, assetId) || + _annotationMatcher.isComponent(node, assetId)) { + queryFields = _queryFields = new Set(); + if (node.arguments != null && node.arguments.arguments != null) { + node.arguments.arguments.accept(this); + } + _queryFields = null; + } + return queryFields; + } + + @override + Iterable visitNamedExpression(NamedExpression node) { + if ('${node.name.label}' == "queries") { + if (node.expression is! MapLiteral) { + throw new FormatException( + 'Expected a map value for "queries", but got ${node.expression}', + node.toSource()); + } + final queries = node.expression as MapLiteral; + for (var entry in queries.entries) { + var queryField = dumbEval(entry.key); + if (queryField != NOT_A_CONSTANT) { + _queryFields.add(queryField.toString()); + } + } + } + return null; + } +} diff --git a/modules_dart/transform/lib/src/transform/common/model/ng_deps_model.pb.dart b/modules_dart/transform/lib/src/transform/common/model/ng_deps_model.pb.dart index 528761a4e0..7ba9b86ef1 100644 --- a/modules_dart/transform/lib/src/transform/common/model/ng_deps_model.pb.dart +++ b/modules_dart/transform/lib/src/transform/common/model/ng_deps_model.pb.dart @@ -17,7 +17,10 @@ class NgDepsModel extends GeneratedMessage { ExportModel.create) ..pp(5, 'reflectables', PbFieldType.PM, ReflectionInfoModel.$checkItem, ReflectionInfoModel.create) - ..a(6, 'sourceFile', PbFieldType.OS); + ..a(6, 'sourceFile', PbFieldType.OS) + ..p(7, 'getters', PbFieldType.PS) + ..p(8, 'setters', PbFieldType.PS) + ..p(9, 'methods', PbFieldType.PS); NgDepsModel() : super(); NgDepsModel.fromBuffer(List i, @@ -63,6 +66,12 @@ class NgDepsModel extends GeneratedMessage { bool hasSourceFile() => $_has(5, 6); void clearSourceFile() => clearField(6); + + List get getters => $_get(6, 7, null); + + List get setters => $_get(7, 8, null); + + List get methods => $_get(8, 9, null); } class _ReadonlyNgDepsModel extends NgDepsModel with ReadonlyMessageMixin {} @@ -94,12 +103,15 @@ const NgDepsModel$json = const { '6': '.angular2.src.transform.common.model.proto.ReflectionInfoModel' }, const {'1': 'source_file', '3': 6, '4': 1, '5': 9}, + const {'1': 'getters', '3': 7, '4': 3, '5': 9}, + const {'1': 'setters', '3': 8, '4': 3, '5': 9}, + const {'1': 'methods', '3': 9, '4': 3, '5': 9}, ], }; /** * Generated with: - * ng_deps_model.proto (83fe43a087fdd0a7ebee360cd6b669570df4d216) + * ng_deps_model.proto (ab93a7f3c411be8a6dafe914f8aae56027e1bac6) * libprotoc 2.6.1 * dart-protoc-plugin (af5fc2bf1de367a434c3b1847ab260510878ffc0) */ diff --git a/modules_dart/transform/lib/src/transform/common/model/ng_deps_model.proto b/modules_dart/transform/lib/src/transform/common/model/ng_deps_model.proto index 0efe0daaec..c408492dc3 100644 --- a/modules_dart/transform/lib/src/transform/common/model/ng_deps_model.proto +++ b/modules_dart/transform/lib/src/transform/common/model/ng_deps_model.proto @@ -20,4 +20,16 @@ message NgDepsModel { // The basename of the file from which the ng_deps were generated. // Example: component.dart optional string source_file = 6; + + // The names of getters that should be registered in the Angular 2 reflection + // framework. + repeated string getters = 7; + + // The names of setters that should be registered in the Angular 2 reflection + // framework. + repeated string setters = 8; + + // The names of methods that should be registered in the Angular 2 reflection + // framework. + repeated string methods = 9; }