|
| 1 | +library angular2.transform.common.code.queries_code; |
| 2 | + |
| 3 | +import 'package:analyzer/analyzer.dart'; |
| 4 | +import 'package:analyzer/src/generated/ast.dart'; |
| 5 | +import 'package:angular2/src/transform/common/annotation_matcher.dart'; |
| 6 | +import 'package:angular2/src/transform/common/dumb_eval.dart'; |
| 7 | +import 'package:barback/barback.dart'; |
| 8 | + |
| 9 | +/// Visitor responsbile for processing a [ClassDeclaration] and extracting any |
| 10 | +/// query fields it defines. |
| 11 | +class QueriesVisitor extends RecursiveAstVisitor<Iterable<String>> { |
| 12 | + final _QueriesAnnotationVisitor _annotationVisitor; |
| 13 | + final _propertyVisitor = new _QueriesPropertyVisitor(); |
| 14 | + |
| 15 | + QueriesVisitor(AssetId assetId, AnnotationMatcher annotationMatcher) |
| 16 | + : _annotationVisitor = |
| 17 | + new _QueriesAnnotationVisitor(assetId, annotationMatcher); |
| 18 | + |
| 19 | + @override |
| 20 | + Iterable<String> visitClassDeclaration(ClassDeclaration node) { |
| 21 | + final queryFields = new Set<String>(); |
| 22 | + if (node.metadata != null) { |
| 23 | + for (var annotation in node.metadata) { |
| 24 | + var annotationQueryFields = |
| 25 | + _annotationVisitor.visitAnnotation(annotation); |
| 26 | + if (annotationQueryFields != null) { |
| 27 | + queryFields.addAll(annotationQueryFields); |
| 28 | + } |
| 29 | + } |
| 30 | + } |
| 31 | + |
| 32 | + // Record annotations attached to properties. |
| 33 | + if (node.members != null) { |
| 34 | + for (var member in node.members) { |
| 35 | + var queryProp = member.accept(_propertyVisitor); |
| 36 | + if (queryProp != null) { |
| 37 | + queryFields.add(queryProp); |
| 38 | + } |
| 39 | + } |
| 40 | + } |
| 41 | + |
| 42 | + return queryFields.isNotEmpty ? queryFields : null; |
| 43 | + } |
| 44 | +} |
| 45 | + |
| 46 | +/// Visitor responsbile for processing properties and getters on a |
| 47 | +/// [ClassDeclaration] and extracting any query fields it contains. |
| 48 | +class _QueriesPropertyVisitor extends SimpleAstVisitor<String> { |
| 49 | + @override |
| 50 | + String visitFieldDeclaration(FieldDeclaration node) { |
| 51 | + for (var variable in node.fields.variables) { |
| 52 | + for (var meta in node.metadata) { |
| 53 | + if (_isQueryAnnotation(meta)) { |
| 54 | + return '${variable.name}'; |
| 55 | + } |
| 56 | + } |
| 57 | + } |
| 58 | + return null; |
| 59 | + } |
| 60 | + |
| 61 | + @override |
| 62 | + String visitMethodDeclaration(MethodDeclaration node) { |
| 63 | + if (node.isGetter || node.isSetter) { |
| 64 | + for (var meta in node.metadata) { |
| 65 | + if (_isQueryAnnotation(meta)) { |
| 66 | + return '${node.name}'; |
| 67 | + } |
| 68 | + } |
| 69 | + } |
| 70 | + return null; |
| 71 | + } |
| 72 | + |
| 73 | + bool _isQueryAnnotation(Annotation node) { |
| 74 | + // TODO(kegluenq): Use ClassMatcherBase to ensure this is a match. |
| 75 | + var id = node.name; |
| 76 | + final name = id is PrefixedIdentifier ? '${id.identifier}' : '$id'; |
| 77 | + switch (name) { |
| 78 | + case "ContentChild": |
| 79 | + case "ViewChild": |
| 80 | + case "ContentChildren": |
| 81 | + case "ViewChildren": |
| 82 | + return true; |
| 83 | + default: |
| 84 | + return false; |
| 85 | + } |
| 86 | + } |
| 87 | +} |
| 88 | + |
| 89 | +/// Visitor responsible for processing the [Annotation]s on a [ClassDeclaration] |
| 90 | +/// and extracting any query fields it contains. |
| 91 | +class _QueriesAnnotationVisitor extends SimpleAstVisitor<Iterable<String>> { |
| 92 | + /// The file we are processing. |
| 93 | + final AssetId assetId; |
| 94 | + |
| 95 | + /// Responsible for testing whether [Annotation]s are those recognized by |
| 96 | + /// Angular 2, for example `@Component`. |
| 97 | + final AnnotationMatcher _annotationMatcher; |
| 98 | + |
| 99 | + /// All currently found query fields. |
| 100 | + Set<String> _queryFields = null; |
| 101 | + |
| 102 | + _QueriesAnnotationVisitor(this.assetId, this._annotationMatcher); |
| 103 | + |
| 104 | + @override |
| 105 | + Iterable<String> visitAnnotation(Annotation node) { |
| 106 | + var queryFields = null; |
| 107 | + if (_annotationMatcher.isView(node, assetId) || |
| 108 | + _annotationMatcher.isComponent(node, assetId)) { |
| 109 | + queryFields = _queryFields = new Set<String>(); |
| 110 | + if (node.arguments != null && node.arguments.arguments != null) { |
| 111 | + node.arguments.arguments.accept(this); |
| 112 | + } |
| 113 | + _queryFields = null; |
| 114 | + } |
| 115 | + return queryFields; |
| 116 | + } |
| 117 | + |
| 118 | + @override |
| 119 | + Iterable<String> visitNamedExpression(NamedExpression node) { |
| 120 | + if ('${node.name.label}' == "queries") { |
| 121 | + if (node.expression is! MapLiteral) { |
| 122 | + throw new FormatException( |
| 123 | + 'Expected a map value for "queries", but got ${node.expression}', |
| 124 | + node.toSource()); |
| 125 | + } |
| 126 | + final queries = node.expression as MapLiteral; |
| 127 | + for (var entry in queries.entries) { |
| 128 | + var queryField = dumbEval(entry.key); |
| 129 | + if (queryField != NOT_A_CONSTANT) { |
| 130 | + _queryFields.add(queryField.toString()); |
| 131 | + } |
| 132 | + } |
| 133 | + } |
| 134 | + return null; |
| 135 | + } |
| 136 | +} |
0 commit comments