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