feat(dart/transform): Add simple ParseTemplates step

Generate methods in the ParseTemplates step.
Add a test for inline template method generation.
This commit is contained in:
Tim Blasi 2015-03-13 13:55:49 -07:00
parent b3fa1fa4fa
commit 08b56e1c53
14 changed files with 194 additions and 143 deletions

View File

@ -4,4 +4,6 @@ const SETUP_METHOD_NAME = 'setupReflection';
const REFLECTOR_VAR_NAME = 'reflector'; const REFLECTOR_VAR_NAME = 'reflector';
const DEPS_EXTENSION = '.ng_deps.dart'; const DEPS_EXTENSION = '.ng_deps.dart';
const REGISTER_TYPE_METHOD_NAME = 'registerType'; const REGISTER_TYPE_METHOD_NAME = 'registerType';
const REGISTER_GETTERS_METHOD_NAME = 'registerGetters';
const REGISTER_SETTERS_METHOD_NAME = 'registerSetters'; const REGISTER_SETTERS_METHOD_NAME = 'registerSetters';
const REGISTER_METHODS_METHOD_NAME = 'registerMethods';

View File

@ -1,29 +1,30 @@
library angular2.src.transform.template_parser.generator; library angular2.src.transform.template_compiler.generator;
import 'dart:async'; import 'dart:async';
import 'package:analyzer/analyzer.dart'; 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/lexer.dart' as ng;
import 'package:angular2/src/change_detection/parser/parser.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_pipeline.dart';
import 'package:angular2/src/core/compiler/pipeline/compile_step.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/property_binding_parser.dart';
import 'package:angular2/src/core/compiler/pipeline/text_interpolation_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/core/compiler/pipeline/view_splitter.dart';
import 'package:angular2/src/dom/dom_adapter.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.dart';
import 'package:angular2/src/transform/common/asset_reader.dart'; import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart'; import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart'; import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/parser.dart'; import 'package:angular2/src/transform/common/parser.dart';
import 'package:barback/barback.dart'; import 'package:barback/barback.dart';
import 'package:code_transformers/assets.dart';
import 'recording_reflection_capabilities.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<String> processTemplates(AssetReader reader, AssetId entryPoint) async { Future<String> processTemplates(AssetReader reader, AssetId entryPoint) async {
var parser = new Parser(reader); var parser = new Parser(reader);
NgDeps ngDeps = await parser.parse(entryPoint); NgDeps ngDeps = await parser.parse(entryPoint);
@ -34,16 +35,23 @@ Future<String> processTemplates(AssetReader reader, AssetId entryPoint) async {
var values = _processTemplate(templateText); var values = _processTemplate(templateText);
var calls = _generateGetters('${rType.typeName}', values.getterNames); var calls = _generateGetters('${rType.typeName}', values.getterNames);
if (calls.isNotEmpty) { if (calls.isNotEmpty) {
registrations.write('..registerGetters({${calls.join(', ')}})'); registrations.write('..${REGISTER_GETTERS_METHOD_NAME}'
'({${calls.join(', ')}})');
} }
calls = _generateSetters('${rType.typeName}', values.setterNames); calls = _generateSetters('${rType.typeName}', values.setterNames);
if (calls.isNotEmpty) { 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; if (registrations.length == 0) return code;
var codeInjectIdx = ngDeps.registeredTypes.last.registerMethod.end; var codeInjectIdx = ngDeps.registeredTypes.last.registerMethod.end;
return '${code.substring(0, codeInjectIdx)}' return '${code.substring(0, codeInjectIdx)}'
@ -51,49 +59,47 @@ Future<String> processTemplates(AssetReader reader, AssetId entryPoint) async {
'${code.substring(codeInjectIdx)}'; '${code.substring(codeInjectIdx)}';
} }
Iterable<String> _generateGetters(String typeName, List<String> getterNames) {
return getterNames.map((prop) {
// TODO(kegluneq): Include `typeName` where possible.
return ''''$prop': (o) => o.$prop''';
});
}
Iterable<String> _generateSetters(String typeName, List<String> setterName) {
return setterName.map((prop) {
return ''''$prop': (o, v) => o.$prop = v''';
});
}
Iterable<String> _generateMethods(String typeName, List<String> methodNames) {
return methodNames.map((methodName) {
return ''''$methodName': (o, List args) =>
Function.apply(o.$methodName, args)''';
});
}
RecordingReflectionCapabilities _processTemplate(String templateCode) { RecordingReflectionCapabilities _processTemplate(String templateCode) {
var recordingCapabilities = new RecordingReflectionCapabilities(); var recordingCapabilities = new RecordingReflectionCapabilities();
var savedReflectionCapabilities = reflector.reflectionCapabilities;
reflector.reflectionCapabilities = recordingCapabilities; reflector.reflectionCapabilities = recordingCapabilities;
var compilePipeline = new CompilePipeline(createCompileSteps()); var compilePipeline = new CompilePipeline(_createCompileSteps());
var template = DOM.createTemplate(templateCode); var template = DOM.createTemplate(templateCode);
// TODO(kegluneq): Need to parse this from a file when not inline. // TODO(kegluneq): Need to parse this from a file when not inline.
compilePipeline.process(template, templateCode); compilePipeline.process(template, templateCode);
reflector.reflectionCapabilities = savedReflectionCapabilities;
return recordingCapabilities; return recordingCapabilities;
} }
List<String> _generateGetters(String typeName, List<String> getterNames) { List<CompileStep> _createCompileSteps() {
var getters = [];
getterNames.forEach((prop) {
// TODO(kegluneq): Include `typeName` where possible.
getters.add('\'$prop\': (o) => o.$prop');
});
return getters;
}
List<String> _generateSetters(String typeName, List<String> setterName) {
var setters = [];
setterName.forEach((prop) {
// TODO(kegluneq): Include `typeName` where possible.
setters.add('\'$prop\': (o, String v) => o.$prop = v');
});
return setters;
}
List<CompileStep> createCompileSteps() {
var parser = new ng.Parser(new ng.Lexer()); var parser = new ng.Parser(new ng.Lexer());
// TODO(kegluneq): Add other compile steps from default_steps.dart.
return [ return [
new ViewSplitter(parser), new ViewSplitter(parser),
// cssProcessor.getCompileStep(
// compiledComponent, shadowDomStrategy, templateUrl),
new PropertyBindingParser(parser), new PropertyBindingParser(parser),
// new DirectiveParser(directives),
new TextInterpolationParser(parser) new TextInterpolationParser(parser)
// new ElementBindingMarker(),
// new ProtoViewBuilder(changeDetection, shadowDomStrategy),
// new ProtoElementInjectorBuilder(),
// new ElementBinderBuilder(parser)
]; ];
} }
@ -103,6 +109,8 @@ List<String> _processRegisteredType(AssetReader reader, RegisteredType t) {
return visitor.templateText; 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<Object> { class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> {
final List<String> templateText = []; final List<String> templateText = [];
final AssetReader _reader; final AssetReader _reader;

View File

@ -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/reflection_capabilities.dart';
import 'package:angular2/src/reflection/types.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 { class RecordingReflectionCapabilities implements ReflectionCapabilities {
/// The names of all requested `getter`s.
final List<String> getterNames = [];
/// The names of all requested `setter`s.
final List<String> setterNames = [];
/// The names of all requested `method`s.
final List<String> methodNames = [];
void _notImplemented(String name) { void _notImplemented(String name) {
throw 'Not implemented: $name'; throw 'Not implemented: $name';
} }
final List<String> getterNames = [];
final List<String> setterNames = [];
final List<String> methodNames = [];
Function factory(Type type) => _notImplemented('factory'); Function factory(Type type) => _notImplemented('factory');
List<List> parameters(typeOrFunc) => _notImplemented('parameters'); List<List> parameters(typeOrFunc) => _notImplemented('parameters');

View File

@ -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');
}
}
}

View File

@ -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 =
'''<div class=\"greeting\">{{greeting}} <span red>world</span>!</div>
<button class=\"changeButton\" (click)=\"changeGreeting()\">
change greeting
</button>
''';

View File

@ -7,20 +7,20 @@ import 'directive_linker/transformer.dart';
import 'directive_processor/transformer.dart'; import 'directive_processor/transformer.dart';
import 'bind_generator/transformer.dart'; import 'bind_generator/transformer.dart';
import 'reflection_remover/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/formatter.dart' as formatter;
import 'common/options.dart'; import 'common/options.dart';
export 'common/options.dart'; export 'common/options.dart';
/// Removes the mirror-based initialization logic and replaces it with static /// Replaces Angular 2 mirror use with generated code.
/// logic.
class AngularTransformerGroup extends TransformerGroup { class AngularTransformerGroup extends TransformerGroup {
AngularTransformerGroup(TransformerOptions options) : super([ AngularTransformerGroup(TransformerOptions options) : super([
[new DirectiveProcessor(options)], [new DirectiveProcessor(options)],
[new DirectiveLinker(options)], [new DirectiveLinker(options)],
[new BindGenerator(options), new ReflectionRemover(options)], [new BindGenerator(options)],
[new TemplateParser(options)] [new TemplateComplier(options)],
[new ReflectionRemover(options)]
]) { ]) {
formatter.init(new DartFormatter()); formatter.init(new DartFormatter());
} }

View File

@ -1,6 +1,7 @@
library angular2.test.transform.integration; library angular2.test.transform.integration;
import 'dart:io'; import 'dart:io';
import 'package:angular2/src/dom/html5lib_adapter.dart';
import 'package:angular2/transformer.dart'; import 'package:angular2/transformer.dart';
import 'package:code_transformers/tests.dart'; import 'package:code_transformers/tests.dart';
import 'package:dart_style/dart_style.dart'; import 'package:dart_style/dart_style.dart';
@ -24,6 +25,8 @@ class IntegrationTestConfig {
} }
void allTests() { void allTests() {
Html5LibDomAdapter.makeCurrent();
/* /*
* Each test has its own directory for inputs & an `expected` directory for * Each test has its own directory for inputs & an `expected` directory for
* expected outputs. * expected outputs.

View File

@ -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));
});
}

View File

@ -18,5 +18,5 @@ void setupReflection(reflector) {
] ]
}) })
..registerGetters({'greeting': (o) => o.greeting}) ..registerGetters({'greeting': (o) => o.greeting})
..registerSetters({'greeting': (o, String v) => o.greeting = v}); ..registerSetters({'greeting': (o, v) => o.greeting = v});
} }

View File

@ -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: '<button (click)=\"action()\">go</button>')
]
})
..registerMethods(
{'action': (o, List args) => Function.apply(o.action, args)});
}

View File

@ -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: '<button (click)=\"action()\">go</button>')
]
});
}

View File

@ -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));
});
}

View File

@ -7,13 +7,13 @@ import 'bind_generator/all_tests.dart' as bindGenerator;
import 'directive_processor/all_tests.dart' as directiveProcessor; import 'directive_processor/all_tests.dart' as directiveProcessor;
import 'integration/all_tests.dart' as integration; import 'integration/all_tests.dart' as integration;
import 'reflection_remover/all_tests.dart' as reflectionRemover; 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() { main() {
useVMConfiguration(); useVMConfiguration();
group('Bind Generator', bindGenerator.allTests); group('Bind Generator', bindGenerator.allTests);
group('Directive Processor', directiveProcessor.allTests); group('Directive Processor', directiveProcessor.allTests);
group('Reflection Remover', reflectionRemover.allTests); group('Reflection Remover', reflectionRemover.allTests);
group('Template Compiler', templateCompiler.allTests);
group('Transformer Pipeline', integration.allTests); group('Transformer Pipeline', integration.allTests);
group('Template Parser', templateParser.allTests);
} }