| OLD | NEW |
| 1 library angular.source_metadata_extractor ; | 1 library angular.source_metadata_extractor ; |
| 2 | 2 |
| 3 import 'package:analyzer/src/generated/ast.dart'; | 3 import 'package:analyzer/src/generated/ast.dart'; |
| 4 import 'package:analyzer/src/generated/element.dart'; |
| 4 | 5 |
| 5 import 'package:angular/tools/source_crawler.dart'; | 6 import 'package:angular/tools/source_crawler.dart'; |
| 6 import 'package:angular/tools/common.dart'; | 7 import 'package:angular/tools/common.dart'; |
| 7 | 8 |
| 8 const String _COMPONENT = '-component'; | 9 const String _COMPONENT = '-component'; |
| 9 const String _DIRECTIVE = '-directive'; | 10 const String _DIRECTIVE = '-directive'; |
| 10 String _ATTR_DIRECTIVE = '-attr' + _DIRECTIVE; | 11 String _ATTR_DIRECTIVE = '-attr' + _DIRECTIVE; |
| 11 RegExp _ATTR_SELECTOR_REGEXP = new RegExp(r'\[([^\]]+)\]'); | 12 RegExp _ATTR_SELECTOR_REGEXP = new RegExp(r'\[([^\]]+)\]'); |
| 12 const List<String> _specs = const ['=>!', '=>', '<=>', '@', '&']; | 13 const List<String> _specs = const ['=>!', '=>', '<=>', '@', '&']; |
| 13 const Map<String, String> _attrAnnotationsToSpec = const { | 14 const Map<String, String> _attrAnnotationsToSpec = const { |
| (...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 91 substring(0, className.length - _DIRECTIVE.length); | 92 substring(0, className.length - _DIRECTIVE.length); |
| 92 } else { | 93 } else { |
| 93 throw "Directive name '$className' must have a \$selector field."; | 94 throw "Directive name '$className' must have a \$selector field."; |
| 94 } | 95 } |
| 95 } | 96 } |
| 96 } | 97 } |
| 97 var reprocessedAttrs = <String>[]; | 98 var reprocessedAttrs = <String>[]; |
| 98 dirInfo.expressionAttrs.forEach((String attr) { | 99 dirInfo.expressionAttrs.forEach((String attr) { |
| 99 if (attr == '.') { | 100 if (attr == '.') { |
| 100 var matches = _ATTR_SELECTOR_REGEXP.allMatches(dirInfo.selector); | 101 var matches = _ATTR_SELECTOR_REGEXP.allMatches(dirInfo.selector); |
| 101 if (matches.length > 0) { | 102 if (matches.isNotEmpty) { |
| 102 reprocessedAttrs.add(matches.last.group(1)); | 103 reprocessedAttrs.add(matches.last.group(1)); |
| 103 } | 104 } |
| 104 } else { | 105 } else { |
| 105 reprocessedAttrs.add(attr); | 106 reprocessedAttrs.add(attr); |
| 106 } | 107 } |
| 107 }); | 108 }); |
| 108 dirInfo.expressionAttrs = reprocessedAttrs; | 109 dirInfo.expressionAttrs = reprocessedAttrs; |
| 109 directives.add(dirInfo); | 110 directives.add(dirInfo); |
| 111 }); |
| 110 | 112 |
| 111 }); | 113 directives.addAll(metadataVisitor.templates.map( |
| 114 (tmpl) => new DirectiveInfo()..template = tmpl)); |
| 112 | 115 |
| 113 return directives; | 116 return directives; |
| 114 } | 117 } |
| 115 } | 118 } |
| 116 | 119 |
| 117 class DirectiveMetadataCollectingVisitor { | 120 class DirectiveMetadataCollectingAstVisitor extends RecursiveAstVisitor { |
| 118 List<DirectiveMetadata> metadata = <DirectiveMetadata>[]; | 121 final List<DirectiveMetadata> metadata; |
| 122 final List<String> templates; |
| 119 | 123 |
| 120 call(CompilationUnit cu) { | 124 DirectiveMetadataCollectingAstVisitor(this.metadata, this.templates); |
| 121 cu.declarations.forEach((CompilationUnitMember declaration) { | |
| 122 // We only care about classes. | |
| 123 if (declaration is! ClassDeclaration) return; | |
| 124 ClassDeclaration clazz = declaration; | |
| 125 // Check class annotations for presense of NgComponent/NgDirective. | |
| 126 DirectiveMetadata meta; | |
| 127 clazz.metadata.forEach((Annotation ann) { | |
| 128 if (ann.arguments == null) return; // Ignore non-class annotations. | |
| 129 // TODO(pavelj): this is not a safe check for the type of the | |
| 130 // annotations, but good enough for now. | |
| 131 if (ann.name.name != 'NgComponent' | |
| 132 && ann.name.name != 'NgDirective') return; | |
| 133 | 125 |
| 134 bool isComponent = ann.name.name == 'NgComponent'; | 126 visitMethodInvocation(MethodInvocation node) { |
| 127 if (node.methodName.name == 'ngRoute') { |
| 128 NamedExpression viewHtmlExpression = |
| 129 node.argumentList.arguments |
| 130 .firstWhere((e) => e is NamedExpression && |
| 131 e.name.label.name == 'viewHtml', orElse: () => null); |
| 132 if (viewHtmlExpression != null) { |
| 133 if (viewHtmlExpression.expression is! StringLiteral) { |
| 134 throw 'viewHtml must be a string literal'; |
| 135 } |
| 136 templates.add( |
| 137 (viewHtmlExpression.expression as StringLiteral).stringValue); |
| 138 } |
| 139 } |
| 140 super.visitMethodInvocation(node); |
| 141 } |
| 135 | 142 |
| 136 meta = new DirectiveMetadata() | 143 visitClassDeclaration(ClassDeclaration clazz) { |
| 137 ..className = clazz.name.name | 144 // Check class annotations for presense of Component/Decorator. |
| 138 ..type = isComponent ? COMPONENT : DIRECTIVE; | 145 clazz.metadata.forEach((Annotation ann) { |
| 139 metadata.add(meta); | 146 if (ann.arguments == null) return; // Ignore non-class annotations. |
| 147 // TODO(pavelj): this is not a safe check for the type of the |
| 148 // annotations, but good enough for now. |
| 149 if (ann.name.name != 'Component' |
| 150 && ann.name.name != 'Decorator') return; |
| 140 | 151 |
| 141 ann.arguments.arguments.forEach((Expression arg) { | 152 bool isComponent = ann.name.name == 'Component'; |
| 142 if (arg is NamedExpression) { | 153 |
| 143 NamedExpression namedArg = arg; | 154 var meta = new DirectiveMetadata() |
| 144 var paramName = namedArg.name.label.name; | 155 ..className = clazz.name.name |
| 145 if (paramName == 'selector') { | 156 ..type = isComponent ? COMPONENT : DIRECTIVE; |
| 146 meta.selector = assertString(namedArg.expression).stringValue; | 157 metadata.add(meta); |
| 147 } | 158 |
| 148 if (paramName == 'template') { | 159 ann.arguments.arguments.forEach((Expression arg) { |
| 149 meta.template = assertString(namedArg.expression).stringValue; | 160 if (arg is NamedExpression) { |
| 150 } | 161 NamedExpression namedArg = arg; |
| 151 if (paramName == 'map') { | 162 var paramName = namedArg.name.label.name; |
| 152 MapLiteral map = namedArg.expression; | 163 if (paramName == 'selector') { |
| 153 map.entries.forEach((MapLiteralEntry entry) { | 164 meta.selector = assertString(namedArg.expression).stringValue; |
| 154 meta.attributeMappings[assertString(entry.key).stringValue] = | |
| 155 assertString(entry.value).stringValue; | |
| 156 }); | |
| 157 } | |
| 158 if (paramName == 'exportExpressions') { | |
| 159 meta.exportExpressions = getStringValues(namedArg.expression); | |
| 160 } | |
| 161 if (paramName == 'exportExpressionAttrs') { | |
| 162 meta.exportExpressionAttrs = getStringValues(namedArg.expression); | |
| 163 } | |
| 164 } | 165 } |
| 165 }); | 166 if (paramName == 'template') { |
| 167 meta.template = assertString(namedArg.expression).stringValue; |
| 168 } |
| 169 if (paramName == 'map') { |
| 170 MapLiteral map = namedArg.expression; |
| 171 map.entries.forEach((MapLiteralEntry entry) { |
| 172 meta.attributeMappings[assertString(entry.key).stringValue] = |
| 173 assertString(entry.value).stringValue; |
| 174 }); |
| 175 } |
| 176 if (paramName == 'exportExpressions') { |
| 177 meta.exportExpressions = getStringValues(namedArg.expression); |
| 178 } |
| 179 if (paramName == 'exportExpressionAttrs') { |
| 180 meta.exportExpressionAttrs = getStringValues(namedArg.expression); |
| 181 } |
| 182 } |
| 166 }); | 183 }); |
| 167 | 184 |
| 168 // Check fields/getters/setter for presense of attr mapping annotations. | 185 if (meta != null) _walkSuperclassChain(clazz, meta, _extractMappingsFromCl
ass); |
| 169 if (meta != null) { | 186 }); |
| 170 clazz.members.forEach((ClassMember member) { | 187 |
| 171 if (member is FieldDeclaration || | 188 return super.visitClassDeclaration(clazz); |
| 172 (member is MethodDeclaration && | 189 } |
| 173 (member.isSetter || member.isGetter))) { | 190 |
| 174 member.metadata.forEach((Annotation ann) { | 191 _walkSuperclassChain(ClassDeclaration clazz, DirectiveMetadata meta, |
| 175 if (_attrAnnotationsToSpec.containsKey(ann.name.name)) { | 192 metadataExtractor(ClassDeclaration clazz, DirectiveMetada
ta meta)) { |
| 176 String fieldName; | 193 while (clazz != null) { |
| 177 if (member is FieldDeclaration) { | 194 metadataExtractor(clazz, meta); |
| 178 fieldName = member.fields.variables.first.name.name; | 195 if (clazz.element != null && clazz.element.supertype != null) { |
| 179 } else { // MethodDeclaration | 196 clazz = clazz.element.supertype.element.node; |
| 180 fieldName = (member as MethodDeclaration).name.name; | 197 } else { |
| 181 } | 198 clazz = null; |
| 182 StringLiteral attNameLiteral = ann.arguments.arguments.first; | 199 } |
| 183 if (meta.attributeMappings | 200 } |
| 184 .containsKey(attNameLiteral.stringValue)) { | 201 } |
| 185 throw 'Attribute mapping already defined for $fieldName'; | 202 |
| 186 } | 203 _extractMappingsFromClass(ClassDeclaration clazz, DirectiveMetadata meta) { |
| 187 meta.attributeMappings[attNameLiteral.stringValue] = | 204 // Check fields/getters/setter for presence of attr mapping annotations. |
| 188 _attrAnnotationsToSpec[ann.name.name] + fieldName; | 205 clazz.members.forEach((ClassMember member) { |
| 189 } | 206 if (member is FieldDeclaration || |
| 190 }); | 207 (member is MethodDeclaration && |
| 208 (member.isSetter || member.isGetter))) { |
| 209 member.metadata.forEach((Annotation ann) { |
| 210 if (_attrAnnotationsToSpec.containsKey(ann.name.name)) { |
| 211 String fieldName; |
| 212 if (member is FieldDeclaration) { |
| 213 fieldName = member.fields.variables.first.name.name; |
| 214 } else { // MethodDeclaration |
| 215 fieldName = (member as MethodDeclaration).name.name; |
| 216 } |
| 217 StringLiteral attNameLiteral = ann.arguments.arguments.first; |
| 218 if (meta.attributeMappings |
| 219 .containsKey(attNameLiteral.stringValue)) { |
| 220 throw 'Attribute mapping already defined for ' |
| 221 '${clazz.name}.$fieldName'; |
| 222 } |
| 223 meta.attributeMappings[attNameLiteral.stringValue] = |
| 224 _attrAnnotationsToSpec[ann.name.name] + fieldName; |
| 191 } | 225 } |
| 192 }); | 226 }); |
| 193 } | 227 } |
| 194 }); | 228 }); |
| 195 } | 229 } |
| 196 } | 230 } |
| 197 | 231 |
| 232 class DirectiveMetadataCollectingVisitor { |
| 233 List<DirectiveMetadata> metadata = <DirectiveMetadata>[]; |
| 234 List<String> templates = <String>[]; |
| 235 |
| 236 call(CompilationUnit cu) { |
| 237 cu.accept(new DirectiveMetadataCollectingAstVisitor(metadata, templates)); |
| 238 } |
| 239 } |
| 240 |
| 198 List<String> getStringValues(ListLiteral listLiteral) { | 241 List<String> getStringValues(ListLiteral listLiteral) { |
| 199 List<String> res = <String>[]; | 242 List<String> res = <String>[]; |
| 200 for (Expression element in listLiteral.elements) { | 243 for (Expression element in listLiteral.elements) { |
| 201 res.add(assertString(element).stringValue); | 244 res.add(assertString(element).stringValue); |
| 202 } | 245 } |
| 203 return res; | 246 return res; |
| 204 } | 247 } |
| 205 | 248 |
| 206 StringLiteral assertString(Expression key) { | 249 StringLiteral assertString(Expression key) { |
| 207 if (key is! StringLiteral) { | 250 if (key is! StringLiteral) { |
| 208 throw 'must be a string literal: ${key.runtimeType}'; | 251 throw 'must be a string literal: ${key.runtimeType}'; |
| 209 } | 252 } |
| 210 return key; | 253 return key; |
| 211 } | 254 } |
| OLD | NEW |