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