| OLD | NEW |
| (Empty) | |
| 1 library angular.source_metadata_extractor ; |
| 2 |
| 3 import 'package:analyzer/src/generated/ast.dart'; |
| 4 |
| 5 import 'source_crawler.dart'; |
| 6 import '../utils.dart'; |
| 7 import 'common.dart'; |
| 8 |
| 9 const String _COMPONENT = '-component'; |
| 10 const String _DIRECTIVE = '-directive'; |
| 11 String _ATTR_DIRECTIVE = '-attr' + _DIRECTIVE; |
| 12 RegExp _ATTR_SELECTOR_REGEXP = new RegExp(r'\[([^\]]+)\]'); |
| 13 const List<String> _specs = const ['=>!', '=>', '<=>', '@', '&']; |
| 14 const Map<String, String> _attrAnnotationsToSpec = const { |
| 15 'NgAttr': '@', |
| 16 'NgOneWay': '=>', |
| 17 'NgOneWayOneTime': '=>!', |
| 18 'NgTwoWay': '<=>', |
| 19 'NgCallback': '&' |
| 20 }; |
| 21 |
| 22 class SourceMetadataExtractor { |
| 23 SourceCrawler sourceCrawler; |
| 24 DirectiveMetadataCollectingVisitor metadataVisitor; |
| 25 |
| 26 SourceMetadataExtractor(this.sourceCrawler, [ this.metadataVisitor ]) { |
| 27 if (metadataVisitor == null) { |
| 28 metadataVisitor = new DirectiveMetadataCollectingVisitor(); |
| 29 } |
| 30 } |
| 31 |
| 32 List<DirectiveInfo> gatherDirectiveInfo(root) { |
| 33 sourceCrawler.crawl(root, metadataVisitor); |
| 34 |
| 35 List<DirectiveInfo> directives = <DirectiveInfo>[]; |
| 36 metadataVisitor.metadata.forEach((DirectiveMetadata meta) { |
| 37 DirectiveInfo dirInfo = new DirectiveInfo(); |
| 38 dirInfo.selector = meta.selector; |
| 39 dirInfo.template = meta.template; |
| 40 meta.attributeMappings.forEach((attrName, mappingSpec) { |
| 41 var spec = _specs |
| 42 .firstWhere((specPrefix) => mappingSpec.startsWith(specPrefix), |
| 43 orElse: () => throw '$mappingSpec no matching spec'); |
| 44 if (spec != '@') { |
| 45 dirInfo.expressionAttrs.add(snakecase(attrName)); |
| 46 } |
| 47 if (mappingSpec.length == 1) { // Shorthand. Remove. |
| 48 // TODO(pavelgj): Figure out if short-hand LHS should be expanded |
| 49 // and added to the expressions list. |
| 50 if (attrName != '.') { |
| 51 dirInfo.expressions.add(_maybeCamelCase(attrName)); |
| 52 } |
| 53 } else { |
| 54 mappingSpec = mappingSpec.substring(spec.length); |
| 55 if (mappingSpec.startsWith('.')) { |
| 56 mappingSpec = mappingSpec.substring(1); |
| 57 } |
| 58 dirInfo.expressions.add(mappingSpec); |
| 59 } |
| 60 }); |
| 61 |
| 62 meta.exportExpressionAttrs.forEach((attr) { |
| 63 attr = snakecase(attr); |
| 64 if (!dirInfo.expressionAttrs.contains(attr)) { |
| 65 dirInfo.expressionAttrs.add(attr); |
| 66 } |
| 67 }); |
| 68 |
| 69 meta.exportExpressions.forEach((expr) { |
| 70 if (!dirInfo.expressions.contains(expr)) { |
| 71 dirInfo.expressions.add(expr); |
| 72 } |
| 73 }); |
| 74 |
| 75 |
| 76 // No explicit selector specified on the directive, compute one. |
| 77 var className = snakecase(meta.className); |
| 78 if (dirInfo.selector == null) { |
| 79 if (meta.type == COMPONENT) { |
| 80 if (className.endsWith(_COMPONENT)) { |
| 81 dirInfo.selector = className. |
| 82 substring(0, className.length - _COMPONENT.length); |
| 83 } else { |
| 84 throw "Directive name '$className' must end with $_DIRECTIVE, " |
| 85 "$_ATTR_DIRECTIVE, $_COMPONENT or have a \$selector field."; |
| 86 } |
| 87 } else { |
| 88 if (className.endsWith(_ATTR_DIRECTIVE)) { |
| 89 var attrName = className. |
| 90 substring(0, className.length - _ATTR_DIRECTIVE.length); |
| 91 dirInfo.selector = '[$attrName]'; |
| 92 } else if (className.endsWith(_DIRECTIVE)) { |
| 93 dirInfo.selector = className. |
| 94 substring(0, className.length - _DIRECTIVE.length); |
| 95 } else { |
| 96 throw "Directive name '$className' must end with $_DIRECTIVE, " |
| 97 "$_ATTR_DIRECTIVE, $_COMPONENT or have a \$selector field."; |
| 98 } |
| 99 } |
| 100 } |
| 101 var reprocessedAttrs = <String>[]; |
| 102 dirInfo.expressionAttrs.forEach((String attr) { |
| 103 if (attr == '.') { |
| 104 var matches = _ATTR_SELECTOR_REGEXP.allMatches(dirInfo.selector); |
| 105 if (matches.length > 0) { |
| 106 reprocessedAttrs.add(matches.last.group(1)); |
| 107 } |
| 108 } else { |
| 109 reprocessedAttrs.add(attr); |
| 110 } |
| 111 }); |
| 112 dirInfo.expressionAttrs = reprocessedAttrs; |
| 113 directives.add(dirInfo); |
| 114 |
| 115 }); |
| 116 |
| 117 return directives; |
| 118 } |
| 119 } |
| 120 |
| 121 String _maybeCamelCase(String s) => (s.indexOf('-') > -1) ? camelcase(s) : s; |
| 122 |
| 123 class DirectiveMetadataCollectingVisitor { |
| 124 List<DirectiveMetadata> metadata = <DirectiveMetadata>[]; |
| 125 |
| 126 call(CompilationUnit cu) { |
| 127 cu.declarations.forEach((CompilationUnitMember declaration) { |
| 128 // We only care about classes. |
| 129 if (declaration is! ClassDeclaration) return; |
| 130 ClassDeclaration clazz = declaration; |
| 131 // Check class annotations for presense of NgComponent/NgDirective. |
| 132 DirectiveMetadata meta; |
| 133 clazz.metadata.forEach((Annotation ann) { |
| 134 if (ann.arguments == null) return; // Ignore non-class annotations. |
| 135 // TODO(pavelj): this is not a safe check for the type of the |
| 136 // annotations, but good enough for now. |
| 137 if (ann.name.name != 'NgComponent' |
| 138 && ann.name.name != 'NgDirective') return; |
| 139 |
| 140 bool isComponent = ann.name.name == 'NgComponent'; |
| 141 |
| 142 meta = new DirectiveMetadata() |
| 143 ..className = clazz.name.name |
| 144 ..type = isComponent ? COMPONENT : DIRECTIVE; |
| 145 metadata.add(meta); |
| 146 |
| 147 ann.arguments.arguments.forEach((Expression arg) { |
| 148 if (arg is NamedExpression) { |
| 149 NamedExpression namedArg = arg; |
| 150 var paramName = namedArg.name.label.name; |
| 151 if (paramName == 'selector') { |
| 152 meta.selector = assertString(namedArg.expression).stringValue; |
| 153 } |
| 154 if (paramName == 'template') { |
| 155 meta.template = assertString(namedArg.expression).stringValue; |
| 156 } |
| 157 if (paramName == 'map') { |
| 158 MapLiteral map = namedArg.expression; |
| 159 map.entries.forEach((MapLiteralEntry entry) { |
| 160 meta.attributeMappings[assertString(entry.key).stringValue] = |
| 161 assertString(entry.value).stringValue; |
| 162 }); |
| 163 } |
| 164 if (paramName == 'exportExpressions') { |
| 165 meta.exportExpressions = getStringValues(namedArg.expression); |
| 166 } |
| 167 if (paramName == 'exportExpressionAttrs') { |
| 168 meta.exportExpressionAttrs = getStringValues(namedArg.expression); |
| 169 } |
| 170 } |
| 171 }); |
| 172 }); |
| 173 |
| 174 // Check fields/getters/setter for presense of attr mapping annotations. |
| 175 if (meta != null) { |
| 176 clazz.members.forEach((ClassMember member) { |
| 177 if (member is FieldDeclaration || |
| 178 (member is MethodDeclaration && |
| 179 (member.isSetter || member.isGetter))) { |
| 180 member.metadata.forEach((Annotation ann) { |
| 181 if (_attrAnnotationsToSpec.containsKey(ann.name.name)) { |
| 182 String fieldName; |
| 183 if (member is FieldDeclaration) { |
| 184 fieldName = member.fields.variables.first.name.name; |
| 185 } else { // MethodDeclaration |
| 186 fieldName = (member as MethodDeclaration).name.name; |
| 187 } |
| 188 StringLiteral attNameLiteral = ann.arguments.arguments.first; |
| 189 if (meta.attributeMappings |
| 190 .containsKey(attNameLiteral.stringValue)) { |
| 191 throw 'Attribute mapping already defined for $fieldName'; |
| 192 } |
| 193 meta.attributeMappings[attNameLiteral.stringValue] = |
| 194 _attrAnnotationsToSpec[ann.name.name] + fieldName; |
| 195 } |
| 196 }); |
| 197 } |
| 198 }); |
| 199 } |
| 200 }); |
| 201 } |
| 202 } |
| 203 |
| 204 List<String> getStringValues(ListLiteral listLiteral) { |
| 205 List<String> res = <String>[]; |
| 206 for (Expression element in listLiteral.elements) { |
| 207 res.add(assertString(element).stringValue); |
| 208 } |
| 209 return res; |
| 210 } |
| 211 |
| 212 StringLiteral assertString(Expression key) { |
| 213 if (key is! StringLiteral) { |
| 214 throw 'must be a string literal: ${key.runtimeType}'; |
| 215 } |
| 216 return key; |
| 217 } |
| OLD | NEW |