feat(dart/transform): Add simple ParseTemplates step
Adds a step that parses `inline` Template values to generate getters and setters.
This commit is contained in:
parent
5d502d4093
commit
b3fa1fa4fa
133
modules/angular2/src/transform/template_parser/generator.dart
Normal file
133
modules/angular2/src/transform/template_parser/generator.dart
Normal file
@ -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<String> 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<String> _generateGetters(String typeName, List<String> getterNames) {
|
||||||
|
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());
|
||||||
|
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<String> _processRegisteredType(AssetReader reader, RegisteredType t) {
|
||||||
|
var visitor = new _TemplateExtractVisitor(reader);
|
||||||
|
t.annotations.accept(visitor);
|
||||||
|
return visitor.templateText;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> {
|
||||||
|
final List<String> 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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<String> getterNames = [];
|
||||||
|
final List<String> setterNames = [];
|
||||||
|
final List<String> methodNames = [];
|
||||||
|
|
||||||
|
Function factory(Type type) => _notImplemented('factory');
|
||||||
|
|
||||||
|
List<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;
|
||||||
|
}
|
||||||
|
}
|
@ -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 =
|
||||||
|
'''<div class=\"greeting\">{{greeting}} <span red>world</span>!</div>
|
||||||
|
<button class=\"changeButton\" (click)=\"changeGreeting()\">
|
||||||
|
change greeting
|
||||||
|
</button>
|
||||||
|
''';
|
@ -7,6 +7,7 @@ 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 'common/formatter.dart' as formatter;
|
import 'common/formatter.dart' as formatter;
|
||||||
import 'common/options.dart';
|
import 'common/options.dart';
|
||||||
|
|
||||||
@ -18,7 +19,8 @@ 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 ReflectionRemover(options)],
|
||||||
|
[new TemplateParser(options)]
|
||||||
]) {
|
]) {
|
||||||
formatter.init(new DartFormatter());
|
formatter.init(new DartFormatter());
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
|
});
|
||||||
|
}
|
@ -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});
|
||||||
|
}
|
@ -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}}')
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
@ -7,6 +7,7 @@ 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;
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
useVMConfiguration();
|
useVMConfiguration();
|
||||||
@ -14,4 +15,5 @@ main() {
|
|||||||
group('Directive Processor', directiveProcessor.allTests);
|
group('Directive Processor', directiveProcessor.allTests);
|
||||||
group('Reflection Remover', reflectionRemover.allTests);
|
group('Reflection Remover', reflectionRemover.allTests);
|
||||||
group('Transformer Pipeline', integration.allTests);
|
group('Transformer Pipeline', integration.allTests);
|
||||||
|
group('Template Parser', templateParser.allTests);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user