From b3fa1fa4fa4e8bd7f15b8ef201b6917f302e4818 Mon Sep 17 00:00:00 2001 From: Tim Blasi Date: Fri, 13 Mar 2015 13:24:56 -0700 Subject: [PATCH] feat(dart/transform): Add simple ParseTemplates step Adds a step that parses `inline` Template values to generate getters and setters. --- .../transform/template_parser/generator.dart | 133 ++++++++++++++++++ .../recording_reflection_capabilities.dart | 39 +++++ .../template_parser/transformer.dart | 65 +++++++++ .../angular2/src/transform/transformer.dart | 4 +- .../transform/template_parser/all_tests.dart | 30 ++++ .../basic_files/expected/hello.ngDeps.dart | 22 +++ .../basic_files/hello.ngDeps.dart | 20 +++ .../test/transform/transform.server.spec.dart | 2 + 8 files changed, 314 insertions(+), 1 deletion(-) create mode 100644 modules/angular2/src/transform/template_parser/generator.dart create mode 100644 modules/angular2/src/transform/template_parser/recording_reflection_capabilities.dart create mode 100644 modules/angular2/src/transform/template_parser/transformer.dart create mode 100644 modules/angular2/test/transform/template_parser/all_tests.dart create mode 100644 modules/angular2/test/transform/template_parser/basic_files/expected/hello.ngDeps.dart create mode 100644 modules/angular2/test/transform/template_parser/basic_files/hello.ngDeps.dart diff --git a/modules/angular2/src/transform/template_parser/generator.dart b/modules/angular2/src/transform/template_parser/generator.dart new file mode 100644 index 0000000000..eb768975c8 --- /dev/null +++ b/modules/angular2/src/transform/template_parser/generator.dart @@ -0,0 +1,133 @@ +library angular2.src.transform.template_parser.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'; + +Future processTemplates(AssetReader reader, AssetId entryPoint) async { + var parser = new Parser(reader); + NgDeps ngDeps = await parser.parse(entryPoint); + + var registrations = new StringBuffer(); + ngDeps.registeredTypes.forEach((rType) { + _processRegisteredType(reader, rType).forEach((String templateText) { + var values = _processTemplate(templateText); + var calls = _generateGetters('${rType.typeName}', values.getterNames); + if (calls.isNotEmpty) { + registrations.write('..registerGetters({${calls.join(', ')}})'); + } + calls = _generateSetters('${rType.typeName}', values.setterNames); + if (calls.isNotEmpty) { + registrations.write('..registerSetters({${calls.join(', ')}})'); + } + }); + }); + + String code = ngDeps.code; + if (registrations.length == 0) return code; + var codeInjectIdx = ngDeps.registeredTypes.last.registerMethod.end; + return '${code.substring(0, codeInjectIdx)}' + '${registrations}' + '${code.substring(codeInjectIdx)}'; +} + +RecordingReflectionCapabilities _processTemplate(String templateCode) { + var recordingCapabilities = new RecordingReflectionCapabilities(); + reflector.reflectionCapabilities = recordingCapabilities; + + 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); + + 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() { + var parser = new ng.Parser(new ng.Lexer()); + 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) + ]; +} + +List _processRegisteredType(AssetReader reader, RegisteredType t) { + var visitor = new _TemplateExtractVisitor(reader); + t.annotations.accept(visitor); + return visitor.templateText; +} + +class _TemplateExtractVisitor extends Object with RecursiveAstVisitor { + final List templateText = []; + final AssetReader _reader; + + _TemplateExtractVisitor(this._reader); + + @override + Object visitNamedExpression(NamedExpression node) { + // TODO(kegluneq): Remove this limitation. + if (node.name is Label && node.name.label is SimpleIdentifier) { + var keyString = '${node.name.label}'; + if (keyString == 'inline') { + if (node.expression is SimpleStringLiteral) { + templateText.add(stringLiteralToString(node.expression)); + } else { + logger.error( + 'Angular 2 currently only supports string literals in directives', + ' Source: ${node}'); + } + } + } else { + logger.error( + 'Angular 2 currently only supports simple identifiers in directives', + ' Source: ${node}'); + } + return super.visitNamedExpression(node); + } +} diff --git a/modules/angular2/src/transform/template_parser/recording_reflection_capabilities.dart b/modules/angular2/src/transform/template_parser/recording_reflection_capabilities.dart new file mode 100644 index 0000000000..4dccd492e9 --- /dev/null +++ b/modules/angular2/src/transform/template_parser/recording_reflection_capabilities.dart @@ -0,0 +1,39 @@ +library angular2.src.transform.template_parser.recording_reflection_capabilities; + +import 'package:angular2/src/reflection/reflection_capabilities.dart'; +import 'package:angular2/src/reflection/types.dart'; + +class RecordingReflectionCapabilities implements ReflectionCapabilities { + 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'); + + List annotations(typeOrFunc) => _notImplemented('annotations'); + + static GetterFn _nullGetter = (Object p) => null; + static SetterFn _nullSetter = (Object p, v) => null; + static MethodFn _nullMethod = (Object p, List a) => null; + + GetterFn getter(String name) { + getterNames.add(name); + return _nullGetter; + } + + SetterFn setter(String name) { + setterNames.add(name); + return _nullSetter; + } + + MethodFn method(String name) { + methodNames.add(name); + return _nullMethod; + } +} diff --git a/modules/angular2/src/transform/template_parser/transformer.dart b/modules/angular2/src/transform/template_parser/transformer.dart new file mode 100644 index 0000000000..d9c1cdfb3d --- /dev/null +++ b/modules/angular2/src/transform/template_parser/transformer.dart @@ -0,0 +1,65 @@ +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 30a774b8af..a96dc71095 100644 --- a/modules/angular2/src/transform/transformer.dart +++ b/modules/angular2/src/transform/transformer.dart @@ -7,6 +7,7 @@ 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 'common/formatter.dart' as formatter; import 'common/options.dart'; @@ -18,7 +19,8 @@ class AngularTransformerGroup extends TransformerGroup { AngularTransformerGroup(TransformerOptions options) : super([ [new DirectiveProcessor(options)], [new DirectiveLinker(options)], - [new BindGenerator(options), new ReflectionRemover(options)] + [new BindGenerator(options), new ReflectionRemover(options)], + [new TemplateParser(options)] ]) { formatter.init(new DartFormatter()); } diff --git a/modules/angular2/test/transform/template_parser/all_tests.dart b/modules/angular2/test/transform/template_parser/all_tests.dart new file mode 100644 index 0000000000..3a108b2dfc --- /dev/null +++ b/modules/angular2/test/transform/template_parser/all_tests.dart @@ -0,0 +1,30 @@ +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/template_parser/basic_files/expected/hello.ngDeps.dart b/modules/angular2/test/transform/template_parser/basic_files/expected/hello.ngDeps.dart new file mode 100644 index 0000000000..f5350f7119 --- /dev/null +++ b/modules/angular2/test/transform/template_parser/basic_files/expected/hello.ngDeps.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: '{{greeting}}') + ] + }) + ..registerGetters({'greeting': (o) => o.greeting}) + ..registerSetters({'greeting': (o, String v) => o.greeting = v}); +} diff --git a/modules/angular2/test/transform/template_parser/basic_files/hello.ngDeps.dart b/modules/angular2/test/transform/template_parser/basic_files/hello.ngDeps.dart new file mode 100644 index 0000000000..325570824c --- /dev/null +++ b/modules/angular2/test/transform/template_parser/basic_files/hello.ngDeps.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: '{{greeting}}') + ] + }); +} diff --git a/modules/angular2/test/transform/transform.server.spec.dart b/modules/angular2/test/transform/transform.server.spec.dart index 214f7f1a54..0296ad80ec 100644 --- a/modules/angular2/test/transform/transform.server.spec.dart +++ b/modules/angular2/test/transform/transform.server.spec.dart @@ -7,6 +7,7 @@ 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; main() { useVMConfiguration(); @@ -14,4 +15,5 @@ main() { group('Directive Processor', directiveProcessor.allTests); group('Reflection Remover', reflectionRemover.allTests); group('Transformer Pipeline', integration.allTests); + group('Template Parser', templateParser.allTests); }