From 08b56e1c532d23120cdcc86f2fd95de29271bc91 Mon Sep 17 00:00:00 2001 From: Tim Blasi Date: Fri, 13 Mar 2015 13:55:49 -0700 Subject: [PATCH] feat(dart/transform): Add simple ParseTemplates step Generate methods in the ParseTemplates step. Add a test for inline template method generation. --- .../angular2/src/transform/common/names.dart | 2 + .../generator.dart | 78 ++++++++++--------- .../recording_reflection_capabilities.dart | 16 ++-- .../template_compiler/transformer.dart | 46 +++++++++++ .../template_parser/transformer.dart | 65 ---------------- .../angular2/src/transform/transformer.dart | 10 +-- .../test/transform/integration/all_tests.dart | 3 + .../template_compiler/all_tests.dart | 39 ++++++++++ .../expected/hello.ng_deps.dart} | 2 +- .../hello.ng_deps.dart} | 0 .../expected/hello.ng_deps.dart | 22 ++++++ .../inline_method_files/hello.ng_deps.dart | 20 +++++ .../transform/template_parser/all_tests.dart | 30 ------- .../test/transform/transform.server.spec.dart | 4 +- 14 files changed, 194 insertions(+), 143 deletions(-) rename modules/angular2/src/transform/{template_parser => template_compiler}/generator.dart (67%) rename modules/angular2/src/transform/{template_parser => template_compiler}/recording_reflection_capabilities.dart (72%) create mode 100644 modules/angular2/src/transform/template_compiler/transformer.dart delete mode 100644 modules/angular2/src/transform/template_parser/transformer.dart create mode 100644 modules/angular2/test/transform/template_compiler/all_tests.dart rename modules/angular2/test/transform/{template_parser/basic_files/expected/hello.ngDeps.dart => template_compiler/inline_expression_files/expected/hello.ng_deps.dart} (89%) rename modules/angular2/test/transform/{template_parser/basic_files/hello.ngDeps.dart => template_compiler/inline_expression_files/hello.ng_deps.dart} (100%) create mode 100644 modules/angular2/test/transform/template_compiler/inline_method_files/expected/hello.ng_deps.dart create mode 100644 modules/angular2/test/transform/template_compiler/inline_method_files/hello.ng_deps.dart delete mode 100644 modules/angular2/test/transform/template_parser/all_tests.dart diff --git a/modules/angular2/src/transform/common/names.dart b/modules/angular2/src/transform/common/names.dart index c0592d157f..8dae729293 100644 --- a/modules/angular2/src/transform/common/names.dart +++ b/modules/angular2/src/transform/common/names.dart @@ -4,4 +4,6 @@ const SETUP_METHOD_NAME = 'setupReflection'; const REFLECTOR_VAR_NAME = 'reflector'; const DEPS_EXTENSION = '.ng_deps.dart'; const REGISTER_TYPE_METHOD_NAME = 'registerType'; +const REGISTER_GETTERS_METHOD_NAME = 'registerGetters'; const REGISTER_SETTERS_METHOD_NAME = 'registerSetters'; +const REGISTER_METHODS_METHOD_NAME = 'registerMethods'; diff --git a/modules/angular2/src/transform/template_parser/generator.dart b/modules/angular2/src/transform/template_compiler/generator.dart similarity index 67% rename from modules/angular2/src/transform/template_parser/generator.dart rename to modules/angular2/src/transform/template_compiler/generator.dart index eb768975c8..1cedd3514a 100644 --- a/modules/angular2/src/transform/template_parser/generator.dart +++ b/modules/angular2/src/transform/template_compiler/generator.dart @@ -1,29 +1,30 @@ -library angular2.src.transform.template_parser.generator; +library angular2.src.transform.template_compiler.generator; import 'dart:async'; import 'package:analyzer/analyzer.dart'; -import 'package:angular2/src/change_detection/parser/ast.dart'; import 'package:angular2/src/change_detection/parser/lexer.dart' as ng; import 'package:angular2/src/change_detection/parser/parser.dart' as ng; -import 'package:angular2/src/core/compiler/pipeline/compile_element.dart'; import 'package:angular2/src/core/compiler/pipeline/compile_pipeline.dart'; import 'package:angular2/src/core/compiler/pipeline/compile_step.dart'; import 'package:angular2/src/core/compiler/pipeline/property_binding_parser.dart'; import 'package:angular2/src/core/compiler/pipeline/text_interpolation_parser.dart'; import 'package:angular2/src/core/compiler/pipeline/view_splitter.dart'; import 'package:angular2/src/dom/dom_adapter.dart'; -import 'package:angular2/src/dom/html5lib_adapter.dart'; import 'package:angular2/src/reflection/reflection.dart'; import 'package:angular2/src/transform/common/asset_reader.dart'; import 'package:angular2/src/transform/common/logging.dart'; import 'package:angular2/src/transform/common/names.dart'; import 'package:angular2/src/transform/common/parser.dart'; import 'package:barback/barback.dart'; -import 'package:code_transformers/assets.dart'; import 'recording_reflection_capabilities.dart'; +/// Reads the `.ng_deps.dart` file represented by `entryPoint` and parses any +/// Angular 2 `Template` annotations it declares to generate `getter`s, +/// `setter`s, and `method`s that would otherwise be reflectively accessed. +/// +/// This method assumes a [DomAdapter] has been registered. Future processTemplates(AssetReader reader, AssetId entryPoint) async { var parser = new Parser(reader); NgDeps ngDeps = await parser.parse(entryPoint); @@ -34,16 +35,23 @@ Future processTemplates(AssetReader reader, AssetId entryPoint) async { var values = _processTemplate(templateText); var calls = _generateGetters('${rType.typeName}', values.getterNames); if (calls.isNotEmpty) { - registrations.write('..registerGetters({${calls.join(', ')}})'); + registrations.write('..${REGISTER_GETTERS_METHOD_NAME}' + '({${calls.join(', ')}})'); } calls = _generateSetters('${rType.typeName}', values.setterNames); if (calls.isNotEmpty) { - registrations.write('..registerSetters({${calls.join(', ')}})'); + registrations.write('..${REGISTER_SETTERS_METHOD_NAME}' + '({${calls.join(', ')}})'); + } + calls = _generateMethods('${rType.typeName}', values.methodNames); + if (calls.isNotEmpty) { + registrations.write('..${REGISTER_METHODS_METHOD_NAME}' + '({${calls.join(', ')}})'); } }); }); - String code = ngDeps.code; + var code = ngDeps.code; if (registrations.length == 0) return code; var codeInjectIdx = ngDeps.registeredTypes.last.registerMethod.end; return '${code.substring(0, codeInjectIdx)}' @@ -51,49 +59,47 @@ Future processTemplates(AssetReader reader, AssetId entryPoint) async { '${code.substring(codeInjectIdx)}'; } +Iterable _generateGetters(String typeName, List getterNames) { + return getterNames.map((prop) { + // TODO(kegluneq): Include `typeName` where possible. + return ''''$prop': (o) => o.$prop'''; + }); +} + +Iterable _generateSetters(String typeName, List setterName) { + return setterName.map((prop) { + return ''''$prop': (o, v) => o.$prop = v'''; + }); +} + +Iterable _generateMethods(String typeName, List methodNames) { + return methodNames.map((methodName) { + return ''''$methodName': (o, List args) => + Function.apply(o.$methodName, args)'''; + }); +} + RecordingReflectionCapabilities _processTemplate(String templateCode) { var recordingCapabilities = new RecordingReflectionCapabilities(); + var savedReflectionCapabilities = reflector.reflectionCapabilities; reflector.reflectionCapabilities = recordingCapabilities; - var compilePipeline = new CompilePipeline(createCompileSteps()); + var compilePipeline = new CompilePipeline(_createCompileSteps()); var template = DOM.createTemplate(templateCode); // TODO(kegluneq): Need to parse this from a file when not inline. compilePipeline.process(template, templateCode); + reflector.reflectionCapabilities = savedReflectionCapabilities; return recordingCapabilities; } -List _generateGetters(String typeName, List getterNames) { - var getters = []; - getterNames.forEach((prop) { - // TODO(kegluneq): Include `typeName` where possible. - getters.add('\'$prop\': (o) => o.$prop'); - }); - return getters; -} - -List _generateSetters(String typeName, List setterName) { - var setters = []; - setterName.forEach((prop) { - // TODO(kegluneq): Include `typeName` where possible. - setters.add('\'$prop\': (o, String v) => o.$prop = v'); - }); - return setters; -} - -List createCompileSteps() { +List _createCompileSteps() { var parser = new ng.Parser(new ng.Lexer()); + // TODO(kegluneq): Add other compile steps from default_steps.dart. return [ new ViewSplitter(parser), -// cssProcessor.getCompileStep( -// compiledComponent, shadowDomStrategy, templateUrl), new PropertyBindingParser(parser), -// new DirectiveParser(directives), new TextInterpolationParser(parser) -// new ElementBindingMarker(), -// new ProtoViewBuilder(changeDetection, shadowDomStrategy), -// new ProtoElementInjectorBuilder(), -// new ElementBinderBuilder(parser) ]; } @@ -103,6 +109,8 @@ List _processRegisteredType(AssetReader reader, RegisteredType t) { return visitor.templateText; } +/// Visitor responsible for processing the `annotations` property of a +/// [RegisterType] object and pulling out template text. class _TemplateExtractVisitor extends Object with RecursiveAstVisitor { final List templateText = []; final AssetReader _reader; diff --git a/modules/angular2/src/transform/template_parser/recording_reflection_capabilities.dart b/modules/angular2/src/transform/template_compiler/recording_reflection_capabilities.dart similarity index 72% rename from modules/angular2/src/transform/template_parser/recording_reflection_capabilities.dart rename to modules/angular2/src/transform/template_compiler/recording_reflection_capabilities.dart index 4dccd492e9..c7ca67b29b 100644 --- a/modules/angular2/src/transform/template_parser/recording_reflection_capabilities.dart +++ b/modules/angular2/src/transform/template_compiler/recording_reflection_capabilities.dart @@ -1,17 +1,23 @@ -library angular2.src.transform.template_parser.recording_reflection_capabilities; +library angular2.src.transform.template_compiler.recording_reflection_capabilities; import 'package:angular2/src/reflection/reflection_capabilities.dart'; import 'package:angular2/src/reflection/types.dart'; +/// ReflectionCapabilities object that records requests for `getter`s, +/// `setter`s, and `method`s so these can be code generated rather than +/// reflectively accessed at runtime. class RecordingReflectionCapabilities implements ReflectionCapabilities { + /// The names of all requested `getter`s. + final List getterNames = []; + /// The names of all requested `setter`s. + final List setterNames = []; + /// The names of all requested `method`s. + final List methodNames = []; + void _notImplemented(String name) { throw 'Not implemented: $name'; } - final List getterNames = []; - final List setterNames = []; - final List methodNames = []; - Function factory(Type type) => _notImplemented('factory'); List parameters(typeOrFunc) => _notImplemented('parameters'); diff --git a/modules/angular2/src/transform/template_compiler/transformer.dart b/modules/angular2/src/transform/template_compiler/transformer.dart new file mode 100644 index 0000000000..1d15914164 --- /dev/null +++ b/modules/angular2/src/transform/template_compiler/transformer.dart @@ -0,0 +1,46 @@ +library angular2.src.transform.template_compiler.transformer; + +import 'dart:async'; + +import 'package:angular2/src/dom/html5lib_adapter.dart'; +import 'package:angular2/src/transform/common/asset_reader.dart'; +import 'package:angular2/src/transform/common/formatter.dart'; +import 'package:angular2/src/transform/common/logging.dart' as log; +import 'package:angular2/src/transform/common/names.dart'; +import 'package:angular2/src/transform/common/options.dart'; +import 'package:barback/barback.dart'; + +import 'generator.dart'; + +/// [Transformer] responsible for detecting and processing Angular 2 templates. +/// +/// [TemplateComplier] uses the Angular 2 `Compiler` to process the templates, +/// extracting information about what reflection is necessary to render and +/// use that template. It then generates code in place of those reflective +/// accesses. +class TemplateComplier extends Transformer { + final TransformerOptions options; + + TemplateComplier(this.options); + + @override + bool isPrimary(AssetId id) => id.path.endsWith(DEPS_EXTENSION); + + @override + Future apply(Transform transform) async { + log.init(transform); + + try { + Html5LibDomAdapter.makeCurrent(); + var id = transform.primaryInput.id; + var reader = new AssetReader.fromTransform(transform); + var transformedCode = + formatter.format(await processTemplates(reader, id)); + transform.addOutput(new Asset.fromString(id, transformedCode)); + } catch (ex, stackTrace) { + log.logger.error('Parsing ng templates failed.\n' + 'Exception: $ex\n' + 'Stack Trace: $stackTrace'); + } + } +} diff --git a/modules/angular2/src/transform/template_parser/transformer.dart b/modules/angular2/src/transform/template_parser/transformer.dart deleted file mode 100644 index d9c1cdfb3d..0000000000 --- a/modules/angular2/src/transform/template_parser/transformer.dart +++ /dev/null @@ -1,65 +0,0 @@ -library angular2.src.transform.template_parser.transformer; - -import 'dart:async'; -import 'package:angular2/src/change_detection/parser/ast.dart'; -import 'package:angular2/src/change_detection/parser/lexer.dart'; -import 'package:angular2/src/change_detection/parser/parser.dart'; -import 'package:angular2/src/core/compiler/pipeline/compile_element.dart'; -import 'package:angular2/src/core/compiler/pipeline/compile_pipeline.dart'; -import 'package:angular2/src/core/compiler/pipeline/compile_step.dart'; -import 'package:angular2/src/core/compiler/pipeline/property_binding_parser.dart'; -import 'package:angular2/src/core/compiler/pipeline/text_interpolation_parser.dart'; -import 'package:angular2/src/core/compiler/pipeline/view_splitter.dart'; -import 'package:angular2/src/dom/dom_adapter.dart'; -import 'package:angular2/src/dom/html5lib_adapter.dart'; -import 'package:angular2/src/reflection/reflection.dart'; -import 'package:angular2/src/reflection/reflection_capabilities.dart'; -import 'package:angular2/src/transform/common/asset_reader.dart'; -import 'package:angular2/src/transform/common/formatter.dart'; -import 'package:angular2/src/transform/common/logging.dart' as log; -import 'package:angular2/src/transform/common/names.dart'; -import 'package:angular2/src/transform/common/options.dart'; -import 'package:barback/barback.dart'; -import 'package:html5lib/dom.dart' as html; -import 'package:html5lib/parser.dart' as html; -import 'package:path/path.dart' as path; - -import 'generator.dart'; - -class TemplateParser extends Transformer { - final TransformerOptions options; - - TemplateParser(this.options); - - @override - bool isPrimary(AssetId id) => id.path.endsWith(DEPS_EXTENSION); - - @override - Future apply(Transform transform) async { - log.init(transform); - - Html5LibDomAdapter.makeCurrent(); - - try { - -// var doc = html.parse(inlineTemplate); -// parseTemplate(doc); - - var id = transform.primaryInput.id; - var reader = new AssetReader.fromTransform(transform); - var transformedCode = await processTemplates(reader, id); - transform.addOutput(new Asset.fromString(id, transformedCode)); - } catch (ex, stackTrace) { - log.logger.error('Parsing ng templates failed.\n' - 'Exception: $ex\n' - 'Stack Trace: $stackTrace'); - } - } -} - -const inlineTemplate = - '''
{{greeting}} world!
- -'''; diff --git a/modules/angular2/src/transform/transformer.dart b/modules/angular2/src/transform/transformer.dart index a96dc71095..986d3f6f5a 100644 --- a/modules/angular2/src/transform/transformer.dart +++ b/modules/angular2/src/transform/transformer.dart @@ -7,20 +7,20 @@ import 'directive_linker/transformer.dart'; import 'directive_processor/transformer.dart'; import 'bind_generator/transformer.dart'; import 'reflection_remover/transformer.dart'; -import 'template_parser/transformer.dart'; +import 'template_compiler/transformer.dart'; import 'common/formatter.dart' as formatter; import 'common/options.dart'; export 'common/options.dart'; -/// Removes the mirror-based initialization logic and replaces it with static -/// logic. +/// Replaces Angular 2 mirror use with generated code. class AngularTransformerGroup extends TransformerGroup { AngularTransformerGroup(TransformerOptions options) : super([ [new DirectiveProcessor(options)], [new DirectiveLinker(options)], - [new BindGenerator(options), new ReflectionRemover(options)], - [new TemplateParser(options)] + [new BindGenerator(options)], + [new TemplateComplier(options)], + [new ReflectionRemover(options)] ]) { formatter.init(new DartFormatter()); } diff --git a/modules/angular2/test/transform/integration/all_tests.dart b/modules/angular2/test/transform/integration/all_tests.dart index b3d0d3b45e..7d891cc115 100644 --- a/modules/angular2/test/transform/integration/all_tests.dart +++ b/modules/angular2/test/transform/integration/all_tests.dart @@ -1,6 +1,7 @@ library angular2.test.transform.integration; import 'dart:io'; +import 'package:angular2/src/dom/html5lib_adapter.dart'; import 'package:angular2/transformer.dart'; import 'package:code_transformers/tests.dart'; import 'package:dart_style/dart_style.dart'; @@ -24,6 +25,8 @@ class IntegrationTestConfig { } void allTests() { + Html5LibDomAdapter.makeCurrent(); + /* * Each test has its own directory for inputs & an `expected` directory for * expected outputs. diff --git a/modules/angular2/test/transform/template_compiler/all_tests.dart b/modules/angular2/test/transform/template_compiler/all_tests.dart new file mode 100644 index 0000000000..ee2c397814 --- /dev/null +++ b/modules/angular2/test/transform/template_compiler/all_tests.dart @@ -0,0 +1,39 @@ +library angular2.test.transform.directive_processor.all_tests; + +import 'package:barback/barback.dart'; +import 'package:angular2/src/dom/html5lib_adapter.dart'; +import 'package:angular2/src/transform/common/asset_reader.dart'; +import 'package:angular2/src/transform/common/formatter.dart'; +import 'package:angular2/src/transform/template_compiler/generator.dart'; +import 'package:dart_style/dart_style.dart'; +import 'package:unittest/unittest.dart'; + +import '../common/read_file.dart'; + +var formatter = new DartFormatter(); + +void allTests() { + Html5LibDomAdapter.makeCurrent(); + AssetReader reader = new TestAssetReader(); + + test('should parse simple expressions in inline templates.', () async { + var inputPath = + 'template_compiler/inline_expression_files/hello.ng_deps.dart'; + var expected = readFile( + 'template_compiler/inline_expression_files/expected/hello.ng_deps.dart'); + var output = await processTemplates(reader, new AssetId('a', inputPath)); + output = formatter.format(output); + expected = formatter.format(expected); + expect(output, equals(expected)); + }); + + test('should parse simple methods in inline templates.', () async { + var inputPath = 'template_compiler/inline_method_files/hello.ng_deps.dart'; + var expected = readFile( + 'template_compiler/inline_method_files/expected/hello.ng_deps.dart'); + var output = await processTemplates(reader, new AssetId('a', inputPath)); + output = formatter.format(output); + expected = formatter.format(expected); + expect(output, equals(expected)); + }); +} diff --git a/modules/angular2/test/transform/template_parser/basic_files/expected/hello.ngDeps.dart b/modules/angular2/test/transform/template_compiler/inline_expression_files/expected/hello.ng_deps.dart similarity index 89% rename from modules/angular2/test/transform/template_parser/basic_files/expected/hello.ngDeps.dart rename to modules/angular2/test/transform/template_compiler/inline_expression_files/expected/hello.ng_deps.dart index f5350f7119..b4436f0947 100644 --- a/modules/angular2/test/transform/template_parser/basic_files/expected/hello.ngDeps.dart +++ b/modules/angular2/test/transform/template_compiler/inline_expression_files/expected/hello.ng_deps.dart @@ -18,5 +18,5 @@ void setupReflection(reflector) { ] }) ..registerGetters({'greeting': (o) => o.greeting}) - ..registerSetters({'greeting': (o, String v) => o.greeting = v}); + ..registerSetters({'greeting': (o, v) => o.greeting = v}); } diff --git a/modules/angular2/test/transform/template_parser/basic_files/hello.ngDeps.dart b/modules/angular2/test/transform/template_compiler/inline_expression_files/hello.ng_deps.dart similarity index 100% rename from modules/angular2/test/transform/template_parser/basic_files/hello.ngDeps.dart rename to modules/angular2/test/transform/template_compiler/inline_expression_files/hello.ng_deps.dart diff --git a/modules/angular2/test/transform/template_compiler/inline_method_files/expected/hello.ng_deps.dart b/modules/angular2/test/transform/template_compiler/inline_method_files/expected/hello.ng_deps.dart new file mode 100644 index 0000000000..bdf9c7bb54 --- /dev/null +++ b/modules/angular2/test/transform/template_compiler/inline_method_files/expected/hello.ng_deps.dart @@ -0,0 +1,22 @@ +library examples.src.hello_world.index_common_dart; + +import 'hello.dart'; +import 'package:angular2/angular2.dart' + show bootstrap, Component, Decorator, Template, NgElement; + +bool _visited = false; +void setupReflection(reflector) { + if (_visited) return; + _visited = true; + reflector + ..registerType(HelloCmp, { + 'factory': () => new HelloCmp(), + 'parameters': const [const []], + 'annotations': const [ + const Component(selector: 'hello-app'), + const Template(inline: '') + ] + }) + ..registerMethods( + {'action': (o, List args) => Function.apply(o.action, args)}); +} diff --git a/modules/angular2/test/transform/template_compiler/inline_method_files/hello.ng_deps.dart b/modules/angular2/test/transform/template_compiler/inline_method_files/hello.ng_deps.dart new file mode 100644 index 0000000000..acfaece10e --- /dev/null +++ b/modules/angular2/test/transform/template_compiler/inline_method_files/hello.ng_deps.dart @@ -0,0 +1,20 @@ +library examples.src.hello_world.index_common_dart; + +import 'hello.dart'; +import 'package:angular2/angular2.dart' + show bootstrap, Component, Decorator, Template, NgElement; + +bool _visited = false; +void setupReflection(reflector) { + if (_visited) return; + _visited = true; + reflector + ..registerType(HelloCmp, { + 'factory': () => new HelloCmp(), + 'parameters': const [const []], + 'annotations': const [ + const Component(selector: 'hello-app'), + const Template(inline: '') + ] + }); +} diff --git a/modules/angular2/test/transform/template_parser/all_tests.dart b/modules/angular2/test/transform/template_parser/all_tests.dart deleted file mode 100644 index 3a108b2dfc..0000000000 --- a/modules/angular2/test/transform/template_parser/all_tests.dart +++ /dev/null @@ -1,30 +0,0 @@ -library angular2.test.transform.directive_processor.all_tests; - -import 'dart:io'; -import 'package:barback/barback.dart'; -import 'package:angular2/src/transform/common/asset_reader.dart'; -import 'package:angular2/src/transform/common/formatter.dart'; -import 'package:angular2/src/transform/template_parser/generator.dart'; -import 'package:code_transformers/tests.dart'; -import 'package:dart_style/dart_style.dart'; -import 'package:path/path.dart' as path; -import 'package:unittest/unittest.dart'; -import 'package:unittest/vm_config.dart'; - -import '../common/read_file.dart'; - -var formatter = new DartFormatter(); - -void allTests() { - AssetReader reader = new TestAssetReader(); - - test('should parse simple inline templates.', () async { - var inputPath = 'template_parser/basic_files/hello.ngDeps.dart'; - var expected = - readFile('template_parser/basic_files/expected/hello.ngDeps.dart'); - var output = await processTemplates(reader, new AssetId('a', inputPath)); - output = formatter.format(output); - expected = formatter.format(expected); - expect(output, equals(expected)); - }); -} diff --git a/modules/angular2/test/transform/transform.server.spec.dart b/modules/angular2/test/transform/transform.server.spec.dart index 0296ad80ec..89436f6d1c 100644 --- a/modules/angular2/test/transform/transform.server.spec.dart +++ b/modules/angular2/test/transform/transform.server.spec.dart @@ -7,13 +7,13 @@ import 'bind_generator/all_tests.dart' as bindGenerator; import 'directive_processor/all_tests.dart' as directiveProcessor; import 'integration/all_tests.dart' as integration; import 'reflection_remover/all_tests.dart' as reflectionRemover; -import 'template_parser/all_tests.dart' as templateParser; +import 'template_compiler/all_tests.dart' as templateCompiler; main() { useVMConfiguration(); group('Bind Generator', bindGenerator.allTests); group('Directive Processor', directiveProcessor.allTests); group('Reflection Remover', reflectionRemover.allTests); + group('Template Compiler', templateCompiler.allTests); group('Transformer Pipeline', integration.allTests); - group('Template Parser', templateParser.allTests); }