| 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 | 4 |
| 5 import 'package:angular/tools/source_crawler.dart'; | 5 import 'package:angular/tools/source_crawler.dart'; |
| 6 import 'package:angular/tools/common.dart'; | 6 import 'package:angular/tools/common.dart'; |
| 7 import 'package:angular/utils.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 { |
| 15 'NgAttr': '@', | 14 'NgAttr': '@', |
| 16 'NgOneWay': '=>', | 15 'NgOneWay': '=>', |
| 17 'NgOneWayOneTime': '=>!', | 16 'NgOneWayOneTime': '=>!', |
| 18 'NgTwoWay': '<=>', | 17 'NgTwoWay': '<=>', |
| 19 'NgCallback': '&' | 18 'NgCallback': '&' |
| 20 }; | 19 }; |
| 21 | 20 |
| 22 class SourceMetadataExtractor { | 21 class SourceMetadataExtractor { |
| 23 SourceCrawler sourceCrawler; | |
| 24 DirectiveMetadataCollectingVisitor metadataVisitor; | 22 DirectiveMetadataCollectingVisitor metadataVisitor; |
| 25 | 23 |
| 26 SourceMetadataExtractor(this.sourceCrawler, [ this.metadataVisitor ]) { | 24 SourceMetadataExtractor([ this.metadataVisitor ]) { |
| 27 if (metadataVisitor == null) { | 25 if (metadataVisitor == null) { |
| 28 metadataVisitor = new DirectiveMetadataCollectingVisitor(); | 26 metadataVisitor = new DirectiveMetadataCollectingVisitor(); |
| 29 } | 27 } |
| 30 } | 28 } |
| 31 | 29 |
| 32 List<DirectiveInfo> gatherDirectiveInfo(root) { | 30 List<DirectiveInfo> gatherDirectiveInfo(root, SourceCrawler sourceCrawler) { |
| 33 sourceCrawler.crawl(root, metadataVisitor); | 31 sourceCrawler.crawl(root, metadataVisitor); |
| 34 | 32 |
| 35 List<DirectiveInfo> directives = <DirectiveInfo>[]; | 33 List<DirectiveInfo> directives = <DirectiveInfo>[]; |
| 36 metadataVisitor.metadata.forEach((DirectiveMetadata meta) { | 34 metadataVisitor.metadata.forEach((DirectiveMetadata meta) { |
| 37 DirectiveInfo dirInfo = new DirectiveInfo(); | 35 DirectiveInfo dirInfo = new DirectiveInfo(); |
| 38 dirInfo.selector = meta.selector; | 36 dirInfo.selector = meta.selector; |
| 39 dirInfo.template = meta.template; | 37 dirInfo.template = meta.template; |
| 40 meta.attributeMappings.forEach((attrName, mappingSpec) { | 38 meta.attributeMappings.forEach((attrName, mappingSpec) { |
| 41 var spec = _specs | 39 var spec = _specs |
| 42 .firstWhere((specPrefix) => mappingSpec.startsWith(specPrefix), | 40 .firstWhere((specPrefix) => mappingSpec.startsWith(specPrefix), |
| 43 orElse: () => throw '$mappingSpec no matching spec'); | 41 orElse: () => throw '$mappingSpec no matching spec'); |
| 44 if (spec != '@') { | 42 if (spec != '@') { |
| 45 dirInfo.expressionAttrs.add(snakecase(attrName)); | 43 dirInfo.expressionAttrs.add(attrName); |
| 46 } | 44 } |
| 47 if (mappingSpec.length == 1) { // Shorthand. Remove. | 45 if (mappingSpec.length == 1) { // Shorthand. Remove. |
| 48 // TODO(pavelgj): Figure out if short-hand LHS should be expanded | 46 // TODO(pavelgj): Figure out if short-hand LHS should be expanded |
| 49 // and added to the expressions list. | 47 // and added to the expressions list. |
| 50 if (attrName != '.') { | 48 if (attrName != '.') { |
| 51 dirInfo.expressions.add(_maybeCamelCase(attrName)); | 49 dirInfo.expressions.add(attrName); |
| 52 } | 50 } |
| 53 } else { | 51 } else { |
| 54 mappingSpec = mappingSpec.substring(spec.length); | 52 mappingSpec = mappingSpec.substring(spec.length); |
| 55 if (mappingSpec.startsWith('.')) { | 53 if (mappingSpec.startsWith('.')) { |
| 56 mappingSpec = mappingSpec.substring(1); | 54 mappingSpec = mappingSpec.substring(1); |
| 57 } | 55 } |
| 58 dirInfo.expressions.add(mappingSpec); | 56 dirInfo.expressions.add(mappingSpec); |
| 59 } | 57 } |
| 60 }); | 58 }); |
| 61 | 59 |
| 62 meta.exportExpressionAttrs.forEach((attr) { | 60 meta.exportExpressionAttrs.forEach((attr) { |
| 63 attr = snakecase(attr); | |
| 64 if (!dirInfo.expressionAttrs.contains(attr)) { | 61 if (!dirInfo.expressionAttrs.contains(attr)) { |
| 65 dirInfo.expressionAttrs.add(attr); | 62 dirInfo.expressionAttrs.add(attr); |
| 66 } | 63 } |
| 67 }); | 64 }); |
| 68 | 65 |
| 69 meta.exportExpressions.forEach((expr) { | 66 meta.exportExpressions.forEach((expr) { |
| 70 if (!dirInfo.expressions.contains(expr)) { | 67 if (!dirInfo.expressions.contains(expr)) { |
| 71 dirInfo.expressions.add(expr); | 68 dirInfo.expressions.add(expr); |
| 72 } | 69 } |
| 73 }); | 70 }); |
| 74 | 71 |
| 75 | 72 |
| 76 // No explicit selector specified on the directive, compute one. | 73 // No explicit selector specified on the directive, compute one. |
| 77 var className = snakecase(meta.className); | 74 var className = meta.className; |
| 78 if (dirInfo.selector == null) { | 75 if (dirInfo.selector == null) { |
| 79 if (meta.type == COMPONENT) { | 76 if (meta.type == COMPONENT) { |
| 80 if (className.endsWith(_COMPONENT)) { | 77 if (className.endsWith(_COMPONENT)) { |
| 81 dirInfo.selector = className. | 78 dirInfo.selector = className. |
| 82 substring(0, className.length - _COMPONENT.length); | 79 substring(0, className.length - _COMPONENT.length); |
| 83 } else { | 80 } else { |
| 84 throw "Directive name '$className' must end with $_DIRECTIVE, " | 81 throw "Directive name '$className' must end with $_DIRECTIVE, " |
| 85 "$_ATTR_DIRECTIVE, $_COMPONENT or have a \$selector field."; | 82 "$_ATTR_DIRECTIVE, $_COMPONENT or have a \$selector field."; |
| 86 } | 83 } |
| 87 } else { | 84 } else { |
| 88 if (className.endsWith(_ATTR_DIRECTIVE)) { | 85 if (className.endsWith(_ATTR_DIRECTIVE)) { |
| 89 var attrName = className. | 86 var attrName = className. |
| 90 substring(0, className.length - _ATTR_DIRECTIVE.length); | 87 substring(0, className.length - _ATTR_DIRECTIVE.length); |
| 91 dirInfo.selector = '[$attrName]'; | 88 dirInfo.selector = '[$attrName]'; |
| 92 } else if (className.endsWith(_DIRECTIVE)) { | 89 } else if (className.endsWith(_DIRECTIVE)) { |
| 93 dirInfo.selector = className. | 90 dirInfo.selector = className. |
| 94 substring(0, className.length - _DIRECTIVE.length); | 91 substring(0, className.length - _DIRECTIVE.length); |
| 95 } else { | 92 } else { |
| 96 throw "Directive name '$className' must end with $_DIRECTIVE, " | 93 throw "Directive name '$className' must have a \$selector field."; |
| 97 "$_ATTR_DIRECTIVE, $_COMPONENT or have a \$selector field."; | |
| 98 } | 94 } |
| 99 } | 95 } |
| 100 } | 96 } |
| 101 var reprocessedAttrs = <String>[]; | 97 var reprocessedAttrs = <String>[]; |
| 102 dirInfo.expressionAttrs.forEach((String attr) { | 98 dirInfo.expressionAttrs.forEach((String attr) { |
| 103 if (attr == '.') { | 99 if (attr == '.') { |
| 104 var matches = _ATTR_SELECTOR_REGEXP.allMatches(dirInfo.selector); | 100 var matches = _ATTR_SELECTOR_REGEXP.allMatches(dirInfo.selector); |
| 105 if (matches.length > 0) { | 101 if (matches.length > 0) { |
| 106 reprocessedAttrs.add(matches.last.group(1)); | 102 reprocessedAttrs.add(matches.last.group(1)); |
| 107 } | 103 } |
| 108 } else { | 104 } else { |
| 109 reprocessedAttrs.add(attr); | 105 reprocessedAttrs.add(attr); |
| 110 } | 106 } |
| 111 }); | 107 }); |
| 112 dirInfo.expressionAttrs = reprocessedAttrs; | 108 dirInfo.expressionAttrs = reprocessedAttrs; |
| 113 directives.add(dirInfo); | 109 directives.add(dirInfo); |
| 114 | 110 |
| 115 }); | 111 }); |
| 116 | 112 |
| 117 return directives; | 113 return directives; |
| 118 } | 114 } |
| 119 } | 115 } |
| 120 | 116 |
| 121 String _maybeCamelCase(String s) => (s.indexOf('-') > -1) ? camelcase(s) : s; | |
| 122 | |
| 123 class DirectiveMetadataCollectingVisitor { | 117 class DirectiveMetadataCollectingVisitor { |
| 124 List<DirectiveMetadata> metadata = <DirectiveMetadata>[]; | 118 List<DirectiveMetadata> metadata = <DirectiveMetadata>[]; |
| 125 | 119 |
| 126 call(CompilationUnit cu) { | 120 call(CompilationUnit cu) { |
| 127 cu.declarations.forEach((CompilationUnitMember declaration) { | 121 cu.declarations.forEach((CompilationUnitMember declaration) { |
| 128 // We only care about classes. | 122 // We only care about classes. |
| 129 if (declaration is! ClassDeclaration) return; | 123 if (declaration is! ClassDeclaration) return; |
| 130 ClassDeclaration clazz = declaration; | 124 ClassDeclaration clazz = declaration; |
| 131 // Check class annotations for presense of NgComponent/NgDirective. | 125 // Check class annotations for presense of NgComponent/NgDirective. |
| 132 DirectiveMetadata meta; | 126 DirectiveMetadata meta; |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 208 } | 202 } |
| 209 return res; | 203 return res; |
| 210 } | 204 } |
| 211 | 205 |
| 212 StringLiteral assertString(Expression key) { | 206 StringLiteral assertString(Expression key) { |
| 213 if (key is! StringLiteral) { | 207 if (key is! StringLiteral) { |
| 214 throw 'must be a string literal: ${key.runtimeType}'; | 208 throw 'must be a string literal: ${key.runtimeType}'; |
| 215 } | 209 } |
| 216 return key; | 210 return key; |
| 217 } | 211 } |
| OLD | NEW |