OLD | NEW |
(Empty) | |
| 1 library angular.tools.transformer.expression_generator; |
| 2 |
| 3 import 'dart:async'; |
| 4 import 'package:analyzer/src/generated/element.dart'; |
| 5 import 'package:angular/core/parser/parser.dart'; |
| 6 import 'package:angular/tools/html_extractor.dart'; |
| 7 import 'package:angular/tools/parser_getter_setter/generator.dart'; |
| 8 import 'package:angular/tools/source_crawler.dart'; |
| 9 import 'package:angular/tools/source_metadata_extractor.dart'; |
| 10 import 'package:angular/tools/transformer/options.dart'; |
| 11 import 'package:angular/tools/transformer/referenced_uris.dart'; |
| 12 import 'package:barback/barback.dart'; |
| 13 import 'package:code_transformers/resolver.dart'; |
| 14 import 'package:di/di.dart'; |
| 15 import 'package:di/dynamic_injector.dart'; |
| 16 import 'package:path/path.dart' as path; |
| 17 |
| 18 /** |
| 19 * Transformer which gathers all expressions from the HTML source files and |
| 20 * Dart source files of an application and packages them for static evaluation. |
| 21 * |
| 22 * This will also modify the main Dart source file to import the generated |
| 23 * expressions and modify all references to NG_EXPRESSION_MODULE to refer to |
| 24 * the generated expressions. |
| 25 */ |
| 26 class ExpressionGenerator extends Transformer with ResolverTransformer { |
| 27 final TransformOptions options; |
| 28 |
| 29 ExpressionGenerator(this.options, Resolvers resolvers) { |
| 30 this.resolvers = resolvers; |
| 31 } |
| 32 |
| 33 Future applyResolver(Transform transform, Resolver resolver) { |
| 34 var asset = transform.primaryInput; |
| 35 var outputBuffer = new StringBuffer(); |
| 36 |
| 37 _writeStaticExpressionHeader(asset.id, outputBuffer); |
| 38 |
| 39 var sourceMetadataExtractor = new SourceMetadataExtractor(); |
| 40 var directives = |
| 41 sourceMetadataExtractor.gatherDirectiveInfo(null, |
| 42 new _LibrarySourceCrawler(resolver.libraries)); |
| 43 |
| 44 var htmlExtractor = new HtmlExpressionExtractor(directives); |
| 45 return _getHtmlSources(transform, resolver) |
| 46 .forEach(htmlExtractor.parseHtml) |
| 47 .then((_) { |
| 48 var module = new Module() |
| 49 ..type(Parser, implementedBy: DynamicParser) |
| 50 ..type(ParserBackend, implementedBy: DartGetterSetterGen); |
| 51 var injector = |
| 52 new DynamicInjector(modules: [module], allowImplicitInjection: true); |
| 53 |
| 54 injector.get(_ParserGetterSetter).generateParser( |
| 55 htmlExtractor.expressions.toList(), outputBuffer); |
| 56 |
| 57 var id = transform.primaryInput.id; |
| 58 var outputFilename = '${path.url.basenameWithoutExtension(id.path)}' |
| 59 '_static_expressions.dart'; |
| 60 var outputPath = path.url.join(path.url.dirname(id.path), outputFilename); |
| 61 var outputId = new AssetId(id.package, outputPath); |
| 62 transform.addOutput( |
| 63 new Asset.fromString(outputId, outputBuffer.toString())); |
| 64 |
| 65 transform.addOutput(asset); |
| 66 }); |
| 67 } |
| 68 |
| 69 /** |
| 70 * Gets a stream consisting of the contents of all HTML source files to be |
| 71 * scoured for expressions. |
| 72 */ |
| 73 Stream<String> _getHtmlSources(Transform transform, Resolver resolver) { |
| 74 var id = transform.primaryInput.id; |
| 75 |
| 76 var controller = new StreamController<String>(); |
| 77 var assets = options.htmlFiles |
| 78 .map((path) => _uriToAssetId(path, transform)) |
| 79 .where((id) => id != null) |
| 80 .toList(); |
| 81 |
| 82 // Get all of the contents of templates in @Component(templateUrl:'...') |
| 83 gatherReferencedUris(transform, resolver, options, |
| 84 templatesOnly: true).then((templates) { |
| 85 templates.values.forEach(controller.add); |
| 86 }).then((_) { |
| 87 // Add any HTML files referencing this Dart file. |
| 88 return _findHtmlEntry(transform); |
| 89 }).then((htmlRefId) { |
| 90 if (htmlRefId != null) { |
| 91 assets.add(htmlRefId); |
| 92 } |
| 93 Future.wait( |
| 94 // Add any manually specified HTML files. |
| 95 assets.map((id) => transform.readInputAsString(id)) |
| 96 .map((future) => |
| 97 future.then(controller.add).catchError((e) { |
| 98 transform.logger.warning('Unable to find $id from html_files ' |
| 99 'in pubspec.yaml.'); |
| 100 })) |
| 101 ).then((_) { |
| 102 controller.close(); |
| 103 }); |
| 104 }); |
| 105 |
| 106 return controller.stream; |
| 107 } |
| 108 |
| 109 AssetId _uriToAssetId(String uri, Transform transform) { |
| 110 if (path.url.isAbsolute(uri)) { |
| 111 var parts = path.url.split(uri); |
| 112 if (parts[1] == 'packages') { |
| 113 var pkgPath = path.url.join('lib', path.url.joinAll(parts.skip(3))); |
| 114 return new AssetId(parts[2], pkgPath); |
| 115 } |
| 116 transform.logger.warning('Cannot cache non-package absolute URIs. $uri'); |
| 117 return null; |
| 118 } |
| 119 return new AssetId(transform.primaryInput.id.package, uri); |
| 120 } |
| 121 |
| 122 /// Finds any HTML files referencing the primary input of the transform. |
| 123 Future<AssetId> _findHtmlEntry(Transform transform) { |
| 124 var id = transform.primaryInput.id; |
| 125 // Magic file generated by HtmlDartReferencesGenerator |
| 126 var htmlRefId = new AssetId(id.package, id.path + '.html_reference'); |
| 127 |
| 128 return transform.readInputAsString(htmlRefId).then((path) { |
| 129 return new AssetId(id.package, path); |
| 130 }, onError: (e, s) => null); // swallow not-found errors. |
| 131 } |
| 132 } |
| 133 |
| 134 void _writeStaticExpressionHeader(AssetId id, StringSink sink) { |
| 135 var libPath = path.withoutExtension(id.path).replaceAll('/', '.'); |
| 136 sink.write(''' |
| 137 library ${id.package}.$libPath.generated_expressions; |
| 138 |
| 139 import 'package:angular/change_detection/change_detection.dart'; |
| 140 |
| 141 '''); |
| 142 } |
| 143 |
| 144 class _LibrarySourceCrawler implements SourceCrawler { |
| 145 final Iterable<LibraryElement> libraries; |
| 146 _LibrarySourceCrawler(this.libraries); |
| 147 |
| 148 void crawl(String entryPoint, CompilationUnitVisitor visitor) { |
| 149 libraries.expand((lib) => lib.units) |
| 150 .map((compilationUnitElement) => compilationUnitElement.node) |
| 151 .forEach(visitor); |
| 152 } |
| 153 } |
| 154 |
| 155 class _ParserGetterSetter { |
| 156 final Parser parser; |
| 157 final ParserBackend backend; |
| 158 _ParserGetterSetter(this.parser, this.backend); |
| 159 |
| 160 generateParser(List<String> exprs, StringSink sink) { |
| 161 exprs.forEach((expr) { |
| 162 try { |
| 163 parser(expr); |
| 164 } catch (e) { |
| 165 // Ignore exceptions. |
| 166 } |
| 167 }); |
| 168 |
| 169 DartGetterSetterGen backend = this.backend; |
| 170 sink.write(generateClosures(backend.properties, backend.calls, backend.symbo
ls)); |
| 171 } |
| 172 |
| 173 String generateClosures(Set<String> properties, |
| 174 Set<String> calls, |
| 175 Set<String> symbols) { |
| 176 var getters = new Set.from(properties)..addAll(calls); |
| 177 return ''' |
| 178 final Map<String, FieldGetter> getters = ${generateGetterMap(getters)}; |
| 179 final Map<String, FieldSetter> setters = ${generateSetterMap(properties)}; |
| 180 final Map<String, Symbol> symbols = ${generateSymbolMap(symbols)}; |
| 181 '''; |
| 182 } |
| 183 |
| 184 generateGetterMap(Iterable<String> keys) { |
| 185 var lines = keys.map((key) => ' r"${key}": (o) => o.$key'); |
| 186 return '{\n${lines.join(",\n")}\n}'; |
| 187 } |
| 188 |
| 189 generateSetterMap(Iterable<String> keys) { |
| 190 var lines = keys.map((key) => ' r"${key}": (o, v) => o.$key = v'); |
| 191 return '{\n${lines.join(",\n")}\n}'; |
| 192 } |
| 193 |
| 194 generateSymbolMap(Set<String> symbols) { |
| 195 var lines = symbols.map((key) => ' r"${key}": #$key'); |
| 196 return '{\n${lines.join(",\n")}\n}'; |
| 197 } |
| 198 } |
| 199 |
OLD | NEW |