Skip to content

Commit b3fa1fa

Browse files
author
Tim Blasi
committed
feat(dart/transform): Add simple ParseTemplates step
Adds a step that parses `inline` Template values to generate getters and setters.
1 parent 5d502d4 commit b3fa1fa

File tree

8 files changed

+314
-1
lines changed

8 files changed

+314
-1
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
library angular2.src.transform.template_parser.generator;
2+
3+
import 'dart:async';
4+
5+
import 'package:analyzer/analyzer.dart';
6+
import 'package:angular2/src/change_detection/parser/ast.dart';
7+
import 'package:angular2/src/change_detection/parser/lexer.dart' as ng;
8+
import 'package:angular2/src/change_detection/parser/parser.dart' as ng;
9+
import 'package:angular2/src/core/compiler/pipeline/compile_element.dart';
10+
import 'package:angular2/src/core/compiler/pipeline/compile_pipeline.dart';
11+
import 'package:angular2/src/core/compiler/pipeline/compile_step.dart';
12+
import 'package:angular2/src/core/compiler/pipeline/property_binding_parser.dart';
13+
import 'package:angular2/src/core/compiler/pipeline/text_interpolation_parser.dart';
14+
import 'package:angular2/src/core/compiler/pipeline/view_splitter.dart';
15+
import 'package:angular2/src/dom/dom_adapter.dart';
16+
import 'package:angular2/src/dom/html5lib_adapter.dart';
17+
import 'package:angular2/src/reflection/reflection.dart';
18+
import 'package:angular2/src/transform/common/asset_reader.dart';
19+
import 'package:angular2/src/transform/common/logging.dart';
20+
import 'package:angular2/src/transform/common/names.dart';
21+
import 'package:angular2/src/transform/common/parser.dart';
22+
import 'package:barback/barback.dart';
23+
import 'package:code_transformers/assets.dart';
24+
25+
import 'recording_reflection_capabilities.dart';
26+
27+
Future<String> processTemplates(AssetReader reader, AssetId entryPoint) async {
28+
var parser = new Parser(reader);
29+
NgDeps ngDeps = await parser.parse(entryPoint);
30+
31+
var registrations = new StringBuffer();
32+
ngDeps.registeredTypes.forEach((rType) {
33+
_processRegisteredType(reader, rType).forEach((String templateText) {
34+
var values = _processTemplate(templateText);
35+
var calls = _generateGetters('${rType.typeName}', values.getterNames);
36+
if (calls.isNotEmpty) {
37+
registrations.write('..registerGetters({${calls.join(', ')}})');
38+
}
39+
calls = _generateSetters('${rType.typeName}', values.setterNames);
40+
if (calls.isNotEmpty) {
41+
registrations.write('..registerSetters({${calls.join(', ')}})');
42+
}
43+
});
44+
});
45+
46+
String code = ngDeps.code;
47+
if (registrations.length == 0) return code;
48+
var codeInjectIdx = ngDeps.registeredTypes.last.registerMethod.end;
49+
return '${code.substring(0, codeInjectIdx)}'
50+
'${registrations}'
51+
'${code.substring(codeInjectIdx)}';
52+
}
53+
54+
RecordingReflectionCapabilities _processTemplate(String templateCode) {
55+
var recordingCapabilities = new RecordingReflectionCapabilities();
56+
reflector.reflectionCapabilities = recordingCapabilities;
57+
58+
var compilePipeline = new CompilePipeline(createCompileSteps());
59+
var template = DOM.createTemplate(templateCode);
60+
// TODO(kegluneq): Need to parse this from a file when not inline.
61+
compilePipeline.process(template, templateCode);
62+
63+
return recordingCapabilities;
64+
}
65+
66+
List<String> _generateGetters(String typeName, List<String> getterNames) {
67+
var getters = [];
68+
getterNames.forEach((prop) {
69+
// TODO(kegluneq): Include `typeName` where possible.
70+
getters.add('\'$prop\': (o) => o.$prop');
71+
});
72+
return getters;
73+
}
74+
75+
List<String> _generateSetters(String typeName, List<String> setterName) {
76+
var setters = [];
77+
setterName.forEach((prop) {
78+
// TODO(kegluneq): Include `typeName` where possible.
79+
setters.add('\'$prop\': (o, String v) => o.$prop = v');
80+
});
81+
return setters;
82+
}
83+
84+
List<CompileStep> createCompileSteps() {
85+
var parser = new ng.Parser(new ng.Lexer());
86+
return [
87+
new ViewSplitter(parser),
88+
// cssProcessor.getCompileStep(
89+
// compiledComponent, shadowDomStrategy, templateUrl),
90+
new PropertyBindingParser(parser),
91+
// new DirectiveParser(directives),
92+
new TextInterpolationParser(parser)
93+
// new ElementBindingMarker(),
94+
// new ProtoViewBuilder(changeDetection, shadowDomStrategy),
95+
// new ProtoElementInjectorBuilder(),
96+
// new ElementBinderBuilder(parser)
97+
];
98+
}
99+
100+
List<String> _processRegisteredType(AssetReader reader, RegisteredType t) {
101+
var visitor = new _TemplateExtractVisitor(reader);
102+
t.annotations.accept(visitor);
103+
return visitor.templateText;
104+
}
105+
106+
class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> {
107+
final List<String> templateText = [];
108+
final AssetReader _reader;
109+
110+
_TemplateExtractVisitor(this._reader);
111+
112+
@override
113+
Object visitNamedExpression(NamedExpression node) {
114+
// TODO(kegluneq): Remove this limitation.
115+
if (node.name is Label && node.name.label is SimpleIdentifier) {
116+
var keyString = '${node.name.label}';
117+
if (keyString == 'inline') {
118+
if (node.expression is SimpleStringLiteral) {
119+
templateText.add(stringLiteralToString(node.expression));
120+
} else {
121+
logger.error(
122+
'Angular 2 currently only supports string literals in directives',
123+
' Source: ${node}');
124+
}
125+
}
126+
} else {
127+
logger.error(
128+
'Angular 2 currently only supports simple identifiers in directives',
129+
' Source: ${node}');
130+
}
131+
return super.visitNamedExpression(node);
132+
}
133+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
library angular2.src.transform.template_parser.recording_reflection_capabilities;
2+
3+
import 'package:angular2/src/reflection/reflection_capabilities.dart';
4+
import 'package:angular2/src/reflection/types.dart';
5+
6+
class RecordingReflectionCapabilities implements ReflectionCapabilities {
7+
void _notImplemented(String name) {
8+
throw 'Not implemented: $name';
9+
}
10+
11+
final List<String> getterNames = [];
12+
final List<String> setterNames = [];
13+
final List<String> methodNames = [];
14+
15+
Function factory(Type type) => _notImplemented('factory');
16+
17+
List<List> parameters(typeOrFunc) => _notImplemented('parameters');
18+
19+
List annotations(typeOrFunc) => _notImplemented('annotations');
20+
21+
static GetterFn _nullGetter = (Object p) => null;
22+
static SetterFn _nullSetter = (Object p, v) => null;
23+
static MethodFn _nullMethod = (Object p, List a) => null;
24+
25+
GetterFn getter(String name) {
26+
getterNames.add(name);
27+
return _nullGetter;
28+
}
29+
30+
SetterFn setter(String name) {
31+
setterNames.add(name);
32+
return _nullSetter;
33+
}
34+
35+
MethodFn method(String name) {
36+
methodNames.add(name);
37+
return _nullMethod;
38+
}
39+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
library angular2.src.transform.template_parser.transformer;
2+
3+
import 'dart:async';
4+
import 'package:angular2/src/change_detection/parser/ast.dart';
5+
import 'package:angular2/src/change_detection/parser/lexer.dart';
6+
import 'package:angular2/src/change_detection/parser/parser.dart';
7+
import 'package:angular2/src/core/compiler/pipeline/compile_element.dart';
8+
import 'package:angular2/src/core/compiler/pipeline/compile_pipeline.dart';
9+
import 'package:angular2/src/core/compiler/pipeline/compile_step.dart';
10+
import 'package:angular2/src/core/compiler/pipeline/property_binding_parser.dart';
11+
import 'package:angular2/src/core/compiler/pipeline/text_interpolation_parser.dart';
12+
import 'package:angular2/src/core/compiler/pipeline/view_splitter.dart';
13+
import 'package:angular2/src/dom/dom_adapter.dart';
14+
import 'package:angular2/src/dom/html5lib_adapter.dart';
15+
import 'package:angular2/src/reflection/reflection.dart';
16+
import 'package:angular2/src/reflection/reflection_capabilities.dart';
17+
import 'package:angular2/src/transform/common/asset_reader.dart';
18+
import 'package:angular2/src/transform/common/formatter.dart';
19+
import 'package:angular2/src/transform/common/logging.dart' as log;
20+
import 'package:angular2/src/transform/common/names.dart';
21+
import 'package:angular2/src/transform/common/options.dart';
22+
import 'package:barback/barback.dart';
23+
import 'package:html5lib/dom.dart' as html;
24+
import 'package:html5lib/parser.dart' as html;
25+
import 'package:path/path.dart' as path;
26+
27+
import 'generator.dart';
28+
29+
class TemplateParser extends Transformer {
30+
final TransformerOptions options;
31+
32+
TemplateParser(this.options);
33+
34+
@override
35+
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_EXTENSION);
36+
37+
@override
38+
Future apply(Transform transform) async {
39+
log.init(transform);
40+
41+
Html5LibDomAdapter.makeCurrent();
42+
43+
try {
44+
45+
// var doc = html.parse(inlineTemplate);
46+
// parseTemplate(doc);
47+
48+
var id = transform.primaryInput.id;
49+
var reader = new AssetReader.fromTransform(transform);
50+
var transformedCode = await processTemplates(reader, id);
51+
transform.addOutput(new Asset.fromString(id, transformedCode));
52+
} catch (ex, stackTrace) {
53+
log.logger.error('Parsing ng templates failed.\n'
54+
'Exception: $ex\n'
55+
'Stack Trace: $stackTrace');
56+
}
57+
}
58+
}
59+
60+
const inlineTemplate =
61+
'''<div class=\"greeting\">{{greeting}} <span red>world</span>!</div>
62+
<button class=\"changeButton\" (click)=\"changeGreeting()\">
63+
change greeting
64+
</button>
65+
''';

modules/angular2/src/transform/transformer.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'directive_linker/transformer.dart';
77
import 'directive_processor/transformer.dart';
88
import 'bind_generator/transformer.dart';
99
import 'reflection_remover/transformer.dart';
10+
import 'template_parser/transformer.dart';
1011
import 'common/formatter.dart' as formatter;
1112
import 'common/options.dart';
1213

@@ -18,7 +19,8 @@ class AngularTransformerGroup extends TransformerGroup {
1819
AngularTransformerGroup(TransformerOptions options) : super([
1920
[new DirectiveProcessor(options)],
2021
[new DirectiveLinker(options)],
21-
[new BindGenerator(options), new ReflectionRemover(options)]
22+
[new BindGenerator(options), new ReflectionRemover(options)],
23+
[new TemplateParser(options)]
2224
]) {
2325
formatter.init(new DartFormatter());
2426
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
library angular2.test.transform.directive_processor.all_tests;
2+
3+
import 'dart:io';
4+
import 'package:barback/barback.dart';
5+
import 'package:angular2/src/transform/common/asset_reader.dart';
6+
import 'package:angular2/src/transform/common/formatter.dart';
7+
import 'package:angular2/src/transform/template_parser/generator.dart';
8+
import 'package:code_transformers/tests.dart';
9+
import 'package:dart_style/dart_style.dart';
10+
import 'package:path/path.dart' as path;
11+
import 'package:unittest/unittest.dart';
12+
import 'package:unittest/vm_config.dart';
13+
14+
import '../common/read_file.dart';
15+
16+
var formatter = new DartFormatter();
17+
18+
void allTests() {
19+
AssetReader reader = new TestAssetReader();
20+
21+
test('should parse simple inline templates.', () async {
22+
var inputPath = 'template_parser/basic_files/hello.ngDeps.dart';
23+
var expected =
24+
readFile('template_parser/basic_files/expected/hello.ngDeps.dart');
25+
var output = await processTemplates(reader, new AssetId('a', inputPath));
26+
output = formatter.format(output);
27+
expected = formatter.format(expected);
28+
expect(output, equals(expected));
29+
});
30+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
library examples.src.hello_world.index_common_dart;
2+
3+
import 'hello.dart';
4+
import 'package:angular2/angular2.dart'
5+
show bootstrap, Component, Decorator, Template, NgElement;
6+
7+
bool _visited = false;
8+
void setupReflection(reflector) {
9+
if (_visited) return;
10+
_visited = true;
11+
reflector
12+
..registerType(HelloCmp, {
13+
'factory': () => new HelloCmp(),
14+
'parameters': const [const []],
15+
'annotations': const [
16+
const Component(selector: 'hello-app'),
17+
const Template(inline: '{{greeting}}')
18+
]
19+
})
20+
..registerGetters({'greeting': (o) => o.greeting})
21+
..registerSetters({'greeting': (o, String v) => o.greeting = v});
22+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
library examples.src.hello_world.index_common_dart;
2+
3+
import 'hello.dart';
4+
import 'package:angular2/angular2.dart'
5+
show bootstrap, Component, Decorator, Template, NgElement;
6+
7+
bool _visited = false;
8+
void setupReflection(reflector) {
9+
if (_visited) return;
10+
_visited = true;
11+
reflector
12+
..registerType(HelloCmp, {
13+
'factory': () => new HelloCmp(),
14+
'parameters': const [const []],
15+
'annotations': const [
16+
const Component(selector: 'hello-app'),
17+
const Template(inline: '{{greeting}}')
18+
]
19+
});
20+
}

modules/angular2/test/transform/transform.server.spec.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import 'bind_generator/all_tests.dart' as bindGenerator;
77
import 'directive_processor/all_tests.dart' as directiveProcessor;
88
import 'integration/all_tests.dart' as integration;
99
import 'reflection_remover/all_tests.dart' as reflectionRemover;
10+
import 'template_parser/all_tests.dart' as templateParser;
1011

1112
main() {
1213
useVMConfiguration();
1314
group('Bind Generator', bindGenerator.allTests);
1415
group('Directive Processor', directiveProcessor.allTests);
1516
group('Reflection Remover', reflectionRemover.allTests);
1617
group('Transformer Pipeline', integration.allTests);
18+
group('Template Parser', templateParser.allTests);
1719
}

0 commit comments

Comments
 (0)