Index: third_party/pkg/angular/lib/tools/transformer/expression_generator.dart |
diff --git a/third_party/pkg/angular/lib/tools/transformer/expression_generator.dart b/third_party/pkg/angular/lib/tools/transformer/expression_generator.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..911aa1abfa23278fd57427411129d1ca53fa3a63 |
--- /dev/null |
+++ b/third_party/pkg/angular/lib/tools/transformer/expression_generator.dart |
@@ -0,0 +1,199 @@ |
+library angular.tools.transformer.expression_generator; |
+ |
+import 'dart:async'; |
+import 'package:analyzer/src/generated/element.dart'; |
+import 'package:angular/core/parser/parser.dart'; |
+import 'package:angular/tools/html_extractor.dart'; |
+import 'package:angular/tools/parser_getter_setter/generator.dart'; |
+import 'package:angular/tools/source_crawler.dart'; |
+import 'package:angular/tools/source_metadata_extractor.dart'; |
+import 'package:angular/tools/transformer/options.dart'; |
+import 'package:angular/tools/transformer/referenced_uris.dart'; |
+import 'package:barback/barback.dart'; |
+import 'package:code_transformers/resolver.dart'; |
+import 'package:di/di.dart'; |
+import 'package:di/dynamic_injector.dart'; |
+import 'package:path/path.dart' as path; |
+ |
+/** |
+ * Transformer which gathers all expressions from the HTML source files and |
+ * Dart source files of an application and packages them for static evaluation. |
+ * |
+ * This will also modify the main Dart source file to import the generated |
+ * expressions and modify all references to NG_EXPRESSION_MODULE to refer to |
+ * the generated expressions. |
+ */ |
+class ExpressionGenerator extends Transformer with ResolverTransformer { |
+ final TransformOptions options; |
+ |
+ ExpressionGenerator(this.options, Resolvers resolvers) { |
+ this.resolvers = resolvers; |
+ } |
+ |
+ Future applyResolver(Transform transform, Resolver resolver) { |
+ var asset = transform.primaryInput; |
+ var outputBuffer = new StringBuffer(); |
+ |
+ _writeStaticExpressionHeader(asset.id, outputBuffer); |
+ |
+ var sourceMetadataExtractor = new SourceMetadataExtractor(); |
+ var directives = |
+ sourceMetadataExtractor.gatherDirectiveInfo(null, |
+ new _LibrarySourceCrawler(resolver.libraries)); |
+ |
+ var htmlExtractor = new HtmlExpressionExtractor(directives); |
+ return _getHtmlSources(transform, resolver) |
+ .forEach(htmlExtractor.parseHtml) |
+ .then((_) { |
+ var module = new Module() |
+ ..type(Parser, implementedBy: DynamicParser) |
+ ..type(ParserBackend, implementedBy: DartGetterSetterGen); |
+ var injector = |
+ new DynamicInjector(modules: [module], allowImplicitInjection: true); |
+ |
+ injector.get(_ParserGetterSetter).generateParser( |
+ htmlExtractor.expressions.toList(), outputBuffer); |
+ |
+ var id = transform.primaryInput.id; |
+ var outputFilename = '${path.url.basenameWithoutExtension(id.path)}' |
+ '_static_expressions.dart'; |
+ var outputPath = path.url.join(path.url.dirname(id.path), outputFilename); |
+ var outputId = new AssetId(id.package, outputPath); |
+ transform.addOutput( |
+ new Asset.fromString(outputId, outputBuffer.toString())); |
+ |
+ transform.addOutput(asset); |
+ }); |
+ } |
+ |
+ /** |
+ * Gets a stream consisting of the contents of all HTML source files to be |
+ * scoured for expressions. |
+ */ |
+ Stream<String> _getHtmlSources(Transform transform, Resolver resolver) { |
+ var id = transform.primaryInput.id; |
+ |
+ var controller = new StreamController<String>(); |
+ var assets = options.htmlFiles |
+ .map((path) => _uriToAssetId(path, transform)) |
+ .where((id) => id != null) |
+ .toList(); |
+ |
+ // Get all of the contents of templates in @Component(templateUrl:'...') |
+ gatherReferencedUris(transform, resolver, options, |
+ templatesOnly: true).then((templates) { |
+ templates.values.forEach(controller.add); |
+ }).then((_) { |
+ // Add any HTML files referencing this Dart file. |
+ return _findHtmlEntry(transform); |
+ }).then((htmlRefId) { |
+ if (htmlRefId != null) { |
+ assets.add(htmlRefId); |
+ } |
+ Future.wait( |
+ // Add any manually specified HTML files. |
+ assets.map((id) => transform.readInputAsString(id)) |
+ .map((future) => |
+ future.then(controller.add).catchError((e) { |
+ transform.logger.warning('Unable to find $id from html_files ' |
+ 'in pubspec.yaml.'); |
+ })) |
+ ).then((_) { |
+ controller.close(); |
+ }); |
+ }); |
+ |
+ return controller.stream; |
+ } |
+ |
+ AssetId _uriToAssetId(String uri, Transform transform) { |
+ if (path.url.isAbsolute(uri)) { |
+ var parts = path.url.split(uri); |
+ if (parts[1] == 'packages') { |
+ var pkgPath = path.url.join('lib', path.url.joinAll(parts.skip(3))); |
+ return new AssetId(parts[2], pkgPath); |
+ } |
+ transform.logger.warning('Cannot cache non-package absolute URIs. $uri'); |
+ return null; |
+ } |
+ return new AssetId(transform.primaryInput.id.package, uri); |
+ } |
+ |
+ /// Finds any HTML files referencing the primary input of the transform. |
+ Future<AssetId> _findHtmlEntry(Transform transform) { |
+ var id = transform.primaryInput.id; |
+ // Magic file generated by HtmlDartReferencesGenerator |
+ var htmlRefId = new AssetId(id.package, id.path + '.html_reference'); |
+ |
+ return transform.readInputAsString(htmlRefId).then((path) { |
+ return new AssetId(id.package, path); |
+ }, onError: (e, s) => null); // swallow not-found errors. |
+ } |
+} |
+ |
+void _writeStaticExpressionHeader(AssetId id, StringSink sink) { |
+ var libPath = path.withoutExtension(id.path).replaceAll('/', '.'); |
+ sink.write(''' |
+library ${id.package}.$libPath.generated_expressions; |
+ |
+import 'package:angular/change_detection/change_detection.dart'; |
+ |
+'''); |
+} |
+ |
+class _LibrarySourceCrawler implements SourceCrawler { |
+ final Iterable<LibraryElement> libraries; |
+ _LibrarySourceCrawler(this.libraries); |
+ |
+ void crawl(String entryPoint, CompilationUnitVisitor visitor) { |
+ libraries.expand((lib) => lib.units) |
+ .map((compilationUnitElement) => compilationUnitElement.node) |
+ .forEach(visitor); |
+ } |
+} |
+ |
+class _ParserGetterSetter { |
+ final Parser parser; |
+ final ParserBackend backend; |
+ _ParserGetterSetter(this.parser, this.backend); |
+ |
+ generateParser(List<String> exprs, StringSink sink) { |
+ exprs.forEach((expr) { |
+ try { |
+ parser(expr); |
+ } catch (e) { |
+ // Ignore exceptions. |
+ } |
+ }); |
+ |
+ DartGetterSetterGen backend = this.backend; |
+ sink.write(generateClosures(backend.properties, backend.calls, backend.symbols)); |
+ } |
+ |
+ String generateClosures(Set<String> properties, |
+ Set<String> calls, |
+ Set<String> symbols) { |
+ var getters = new Set.from(properties)..addAll(calls); |
+ return ''' |
+final Map<String, FieldGetter> getters = ${generateGetterMap(getters)}; |
+final Map<String, FieldSetter> setters = ${generateSetterMap(properties)}; |
+final Map<String, Symbol> symbols = ${generateSymbolMap(symbols)}; |
+'''; |
+ } |
+ |
+ generateGetterMap(Iterable<String> keys) { |
+ var lines = keys.map((key) => ' r"${key}": (o) => o.$key'); |
+ return '{\n${lines.join(",\n")}\n}'; |
+ } |
+ |
+ generateSetterMap(Iterable<String> keys) { |
+ var lines = keys.map((key) => ' r"${key}": (o, v) => o.$key = v'); |
+ return '{\n${lines.join(",\n")}\n}'; |
+ } |
+ |
+ generateSymbolMap(Set<String> symbols) { |
+ var lines = symbols.map((key) => ' r"${key}": #$key'); |
+ return '{\n${lines.join(",\n")}\n}'; |
+ } |
+} |
+ |