From 1a788e6b0d4a29c3ac902594452971d97f85303e Mon Sep 17 00:00:00 2001 From: Tim Blasi Date: Thu, 19 Mar 2015 09:16:01 -0700 Subject: [PATCH] feat(dart/transform): Parse `url` values in `Template`s When a `Template` annotation declares a `url` value, parse it to generate `getter`s, `setter`s, and `method`s which will it needs to access reflectively. --- .../directive_processor/visitors.dart | 5 +- .../template_compiler/generator.dart | 153 +++++++++++------- .../test/transform/integration/all_tests.dart | 5 +- .../expected/bar.ng_deps.dart | 5 +- .../two_deps_files/expected/bar.ng_deps.dart | 5 +- 5 files changed, 107 insertions(+), 66 deletions(-) diff --git a/modules/angular2/src/transform/directive_processor/visitors.dart b/modules/angular2/src/transform/directive_processor/visitors.dart index 82953468a5..0eb81ff08b 100644 --- a/modules/angular2/src/transform/directive_processor/visitors.dart +++ b/modules/angular2/src/transform/directive_processor/visitors.dart @@ -42,8 +42,9 @@ class _CtorTransformVisitor extends ToSourceVisitor with VisitorMixin { ClassDeclaration clazz = node.getAncestor((node) => node is ClassDeclaration); _fieldNameToType.clear(); - clazz.members.where((member) => member is FieldDeclaration).forEach( - (FieldDeclaration field) { + clazz.members + .where((member) => member is FieldDeclaration) + .forEach((FieldDeclaration field) { var type = field.fields.type; if (type != null) { field.fields.variables.forEach((VariableDeclaration decl) { diff --git a/modules/angular2/src/transform/template_compiler/generator.dart b/modules/angular2/src/transform/template_compiler/generator.dart index c7773424e2..9e0daf5b56 100644 --- a/modules/angular2/src/transform/template_compiler/generator.dart +++ b/modules/angular2/src/transform/template_compiler/generator.dart @@ -17,6 +17,7 @@ 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'; @@ -28,11 +29,12 @@ import 'recording_reflection_capabilities.dart'; Future processTemplates(AssetReader reader, AssetId entryPoint) async { var parser = new Parser(reader); NgDeps ngDeps = await parser.parse(entryPoint); + var extractor = new _TemplateExtractor(reader, entryPoint); var registrations = new StringBuffer(); - ngDeps.registeredTypes.forEach((rType) { - _processRegisteredType(reader, rType).forEach((String templateText) { - var values = _processTemplate(templateText); + for (var rType in ngDeps.registeredTypes) { + (await extractor.extractTemplates(rType)) + .forEach((RecordingReflectionCapabilities values) { var calls = _generateGetters('${rType.typeName}', values.getterNames); if (calls.isNotEmpty) { registrations.write('..${REGISTER_GETTERS_METHOD_NAME}' @@ -49,7 +51,7 @@ Future processTemplates(AssetReader reader, AssetId entryPoint) async { '({${calls.join(', ')}})'); } }); - }); + } var code = ngDeps.code; if (registrations.length == 0) return code; @@ -60,82 +62,123 @@ Future processTemplates(AssetReader reader, AssetId entryPoint) async { } Iterable _generateGetters(String typeName, List getterNames) { - return getterNames.map((prop) { - // TODO(kegluneq): Include `typeName` where possible. - return ''''$prop': (o) => o.$prop'''; - }); + // TODO(kegluneq): Include `typeName` where possible. + return getterNames.map((prop) => ''' + '$prop': (o) => o.$prop + '''); } Iterable _generateSetters(String typeName, List setterName) { - return setterName.map((prop) { - return ''''$prop': (o, v) => o.$prop = v'''; - }); + return setterName.map((prop) => ''' + '$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)'''; - }); + return methodNames.map((methodName) => ''' + '$methodName': (o, List args) => Function.apply(o.$methodName, args) + '''); } -RecordingReflectionCapabilities _processTemplate(String templateCode) { - var recordingCapabilities = new RecordingReflectionCapabilities(); - var savedReflectionCapabilities = reflector.reflectionCapabilities; - reflector.reflectionCapabilities = recordingCapabilities; +/// Extracts `inline` and `url` values from `Template` annotations, reads +/// template code if necessary, and determines what values will be +/// reflectively accessed from that template. +class _TemplateExtractor { + final AssetReader _reader; + final AssetId _entryPoint; + final CompilePipeline _pipeline; + final _TemplateExtractVisitor _visitor = new _TemplateExtractVisitor(); - 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); + _TemplateExtractor(this._reader, this._entryPoint) + : _pipeline = new CompilePipeline(_createCompileSteps()); - reflector.reflectionCapabilities = savedReflectionCapabilities; - return recordingCapabilities; -} + static List _createCompileSteps() { + var parser = new ng.Parser(new ng.Lexer()); + // TODO(kegluneq): Add other compile steps from default_steps.dart. + return [ + new ViewSplitter(parser), + new PropertyBindingParser(parser), + new TextInterpolationParser(parser) + ]; + } -List _createCompileSteps() { - var parser = new ng.Parser(new ng.Lexer()); - // TODO(kegluneq): Add other compile steps from default_steps.dart. - return [ - new ViewSplitter(parser), - new PropertyBindingParser(parser), - new TextInterpolationParser(parser) - ]; -} + Future> extractTemplates( + RegisteredType t) async { + return (await _processRegisteredType(t)).map(_processTemplate).toList(); + } -List _processRegisteredType(AssetReader reader, RegisteredType t) { - var visitor = new _TemplateExtractVisitor(reader); - t.annotations.accept(visitor); - return visitor.templateText; + RecordingReflectionCapabilities _processTemplate(String templateCode) { + var recordingCapabilities = new RecordingReflectionCapabilities(); + var savedReflectionCapabilities = reflector.reflectionCapabilities; + reflector.reflectionCapabilities = recordingCapabilities; + + _pipeline.process(DOM.createTemplate(templateCode), templateCode); + + reflector.reflectionCapabilities = savedReflectionCapabilities; + return recordingCapabilities; + } + + Future> _processRegisteredType(RegisteredType t) async { + _visitor.reset(); + t.annotations.accept(_visitor); + var toReturn = _visitor.inlineValues; + for (var url in _visitor.urlValues) { + var templateText = await _readUrlTemplate(url); + if (templateText != null) { + toReturn.add(templateText); + } + } + return toReturn; + } + + // TODO(kegluneq): Rewrite these to `inline` where possible. + // See [https://github.com/angular/angular/issues/1035]. + Future _readUrlTemplate(String url) async { + var assetId = uriToAssetId(_entryPoint, url, logger, null); + var templateExists = await _reader.hasInput(assetId); + if (!templateExists) { + logger.error('Could not read template at uri $url from $_entryPoint'); + return null; + } + return await _reader.readAsString(assetId); + } } /// 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; + final List inlineValues = []; + final List urlValues = []; - _TemplateExtractVisitor(this._reader); + void reset() { + inlineValues.clear(); + urlValues.clear(); + } @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 { + if (node.name is! Label || node.name.label is! SimpleIdentifier) { logger.error( 'Angular 2 currently only supports simple identifiers in directives', ' Source: ${node}'); + return null; } - return super.visitNamedExpression(node); + var keyString = '${node.name.label}'; + if (keyString == 'inline' || keyString == 'url') { + if (node.expression is! SimpleStringLiteral) { + logger.error( + 'Angular 2 currently only supports string literals in directives', + ' Source: ${node}'); + return null; + } + var valueString = stringLiteralToString(node.expression); + if (keyString == 'url') { + urlValues.add(valueString); + } else { + inlineValues.add(valueString); + } + } + return null; } } diff --git a/modules/angular2/test/transform/integration/all_tests.dart b/modules/angular2/test/transform/integration/all_tests.dart index 30f88a8add..ef3b85e09d 100644 --- a/modules/angular2/test/transform/integration/all_tests.dart +++ b/modules/angular2/test/transform/integration/all_tests.dart @@ -116,9 +116,8 @@ void allTests() { return value.endsWith('dart') ? formatter.format(code) : code; }); }); - testPhases(config.name, [ - [transform] - ], config.assetPathToInputPath, config.assetPathToExpectedOutputPath, []); + testPhases(config.name, [[transform]], config.assetPathToInputPath, + config.assetPathToExpectedOutputPath, []); } } diff --git a/modules/angular2/test/transform/integration/list_of_types_files/expected/bar.ng_deps.dart b/modules/angular2/test/transform/integration/list_of_types_files/expected/bar.ng_deps.dart index a8c1ca0721..20f3095fe4 100644 --- a/modules/angular2/test/transform/integration/list_of_types_files/expected/bar.ng_deps.dart +++ b/modules/angular2/test/transform/integration/list_of_types_files/expected/bar.ng_deps.dart @@ -12,8 +12,7 @@ void setupReflection(reflector) { ..registerType(MyComponent, { 'factory': (MyContext c) => new MyComponent(c), 'parameters': const [const [MyContext]], - 'annotations': const [ - const Component(componentServices: const [MyContext]) - ] + 'annotations': + const [const Component(componentServices: const [MyContext])] }); } diff --git a/modules/angular2/test/transform/integration/two_deps_files/expected/bar.ng_deps.dart b/modules/angular2/test/transform/integration/two_deps_files/expected/bar.ng_deps.dart index 8025b9ddd7..7143310e5f 100644 --- a/modules/angular2/test/transform/integration/two_deps_files/expected/bar.ng_deps.dart +++ b/modules/angular2/test/transform/integration/two_deps_files/expected/bar.ng_deps.dart @@ -13,8 +13,7 @@ void setupReflection(reflector) { 'factory': (prefix.MyContext c, String inValue) => new MyComponent(c, inValue), 'parameters': const [const [prefix.MyContext], const [String]], - 'annotations': const [ - const Component(selector: prefix.preDefinedSelector) - ] + 'annotations': + const [const Component(selector: prefix.preDefinedSelector)] }); }