Skip to content

Commit 1a788e6

Browse files
author
Tim Blasi
committed
feat(dart/transform): Parse url values in Templates
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.
1 parent d822793 commit 1a788e6

File tree

5 files changed

+107
-66
lines changed

5 files changed

+107
-66
lines changed

modules/angular2/src/transform/directive_processor/visitors.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ class _CtorTransformVisitor extends ToSourceVisitor with VisitorMixin {
4242
ClassDeclaration clazz =
4343
node.getAncestor((node) => node is ClassDeclaration);
4444
_fieldNameToType.clear();
45-
clazz.members.where((member) => member is FieldDeclaration).forEach(
46-
(FieldDeclaration field) {
45+
clazz.members
46+
.where((member) => member is FieldDeclaration)
47+
.forEach((FieldDeclaration field) {
4748
var type = field.fields.type;
4849
if (type != null) {
4950
field.fields.variables.forEach((VariableDeclaration decl) {

modules/angular2/src/transform/template_compiler/generator.dart

Lines changed: 98 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import 'package:angular2/src/transform/common/logging.dart';
1717
import 'package:angular2/src/transform/common/names.dart';
1818
import 'package:angular2/src/transform/common/parser.dart';
1919
import 'package:barback/barback.dart';
20+
import 'package:code_transformers/assets.dart';
2021

2122
import 'recording_reflection_capabilities.dart';
2223

@@ -28,11 +29,12 @@ import 'recording_reflection_capabilities.dart';
2829
Future<String> processTemplates(AssetReader reader, AssetId entryPoint) async {
2930
var parser = new Parser(reader);
3031
NgDeps ngDeps = await parser.parse(entryPoint);
32+
var extractor = new _TemplateExtractor(reader, entryPoint);
3133

3234
var registrations = new StringBuffer();
33-
ngDeps.registeredTypes.forEach((rType) {
34-
_processRegisteredType(reader, rType).forEach((String templateText) {
35-
var values = _processTemplate(templateText);
35+
for (var rType in ngDeps.registeredTypes) {
36+
(await extractor.extractTemplates(rType))
37+
.forEach((RecordingReflectionCapabilities values) {
3638
var calls = _generateGetters('${rType.typeName}', values.getterNames);
3739
if (calls.isNotEmpty) {
3840
registrations.write('..${REGISTER_GETTERS_METHOD_NAME}'
@@ -49,7 +51,7 @@ Future<String> processTemplates(AssetReader reader, AssetId entryPoint) async {
4951
'({${calls.join(', ')}})');
5052
}
5153
});
52-
});
54+
}
5355

5456
var code = ngDeps.code;
5557
if (registrations.length == 0) return code;
@@ -60,82 +62,123 @@ Future<String> processTemplates(AssetReader reader, AssetId entryPoint) async {
6062
}
6163

6264
Iterable<String> _generateGetters(String typeName, List<String> getterNames) {
63-
return getterNames.map((prop) {
64-
// TODO(kegluneq): Include `typeName` where possible.
65-
return ''''$prop': (o) => o.$prop''';
66-
});
65+
// TODO(kegluneq): Include `typeName` where possible.
66+
return getterNames.map((prop) => '''
67+
'$prop': (o) => o.$prop
68+
''');
6769
}
6870

6971
Iterable<String> _generateSetters(String typeName, List<String> setterName) {
70-
return setterName.map((prop) {
71-
return ''''$prop': (o, v) => o.$prop = v''';
72-
});
72+
return setterName.map((prop) => '''
73+
'$prop': (o, v) => o.$prop = v
74+
''');
7375
}
7476

7577
Iterable<String> _generateMethods(String typeName, List<String> methodNames) {
76-
return methodNames.map((methodName) {
77-
return ''''$methodName': (o, List args) =>
78-
Function.apply(o.$methodName, args)''';
79-
});
78+
return methodNames.map((methodName) => '''
79+
'$methodName': (o, List args) => Function.apply(o.$methodName, args)
80+
''');
8081
}
8182

82-
RecordingReflectionCapabilities _processTemplate(String templateCode) {
83-
var recordingCapabilities = new RecordingReflectionCapabilities();
84-
var savedReflectionCapabilities = reflector.reflectionCapabilities;
85-
reflector.reflectionCapabilities = recordingCapabilities;
83+
/// Extracts `inline` and `url` values from `Template` annotations, reads
84+
/// template code if necessary, and determines what values will be
85+
/// reflectively accessed from that template.
86+
class _TemplateExtractor {
87+
final AssetReader _reader;
88+
final AssetId _entryPoint;
89+
final CompilePipeline _pipeline;
90+
final _TemplateExtractVisitor _visitor = new _TemplateExtractVisitor();
91+
92+
_TemplateExtractor(this._reader, this._entryPoint)
93+
: _pipeline = new CompilePipeline(_createCompileSteps());
94+
95+
static List<CompileStep> _createCompileSteps() {
96+
var parser = new ng.Parser(new ng.Lexer());
97+
// TODO(kegluneq): Add other compile steps from default_steps.dart.
98+
return [
99+
new ViewSplitter(parser),
100+
new PropertyBindingParser(parser),
101+
new TextInterpolationParser(parser)
102+
];
103+
}
86104

87-
var compilePipeline = new CompilePipeline(_createCompileSteps());
88-
var template = DOM.createTemplate(templateCode);
89-
// TODO(kegluneq): Need to parse this from a file when not inline.
90-
compilePipeline.process(template, templateCode);
105+
Future<List<RecordingReflectionCapabilities>> extractTemplates(
106+
RegisteredType t) async {
107+
return (await _processRegisteredType(t)).map(_processTemplate).toList();
108+
}
91109

92-
reflector.reflectionCapabilities = savedReflectionCapabilities;
93-
return recordingCapabilities;
94-
}
110+
RecordingReflectionCapabilities _processTemplate(String templateCode) {
111+
var recordingCapabilities = new RecordingReflectionCapabilities();
112+
var savedReflectionCapabilities = reflector.reflectionCapabilities;
113+
reflector.reflectionCapabilities = recordingCapabilities;
95114

96-
List<CompileStep> _createCompileSteps() {
97-
var parser = new ng.Parser(new ng.Lexer());
98-
// TODO(kegluneq): Add other compile steps from default_steps.dart.
99-
return [
100-
new ViewSplitter(parser),
101-
new PropertyBindingParser(parser),
102-
new TextInterpolationParser(parser)
103-
];
104-
}
115+
_pipeline.process(DOM.createTemplate(templateCode), templateCode);
116+
117+
reflector.reflectionCapabilities = savedReflectionCapabilities;
118+
return recordingCapabilities;
119+
}
120+
121+
Future<List<String>> _processRegisteredType(RegisteredType t) async {
122+
_visitor.reset();
123+
t.annotations.accept(_visitor);
124+
var toReturn = _visitor.inlineValues;
125+
for (var url in _visitor.urlValues) {
126+
var templateText = await _readUrlTemplate(url);
127+
if (templateText != null) {
128+
toReturn.add(templateText);
129+
}
130+
}
131+
return toReturn;
132+
}
105133

106-
List<String> _processRegisteredType(AssetReader reader, RegisteredType t) {
107-
var visitor = new _TemplateExtractVisitor(reader);
108-
t.annotations.accept(visitor);
109-
return visitor.templateText;
134+
// TODO(kegluneq): Rewrite these to `inline` where possible.
135+
// See [https://github.com/angular/angular/issues/1035].
136+
Future<String> _readUrlTemplate(String url) async {
137+
var assetId = uriToAssetId(_entryPoint, url, logger, null);
138+
var templateExists = await _reader.hasInput(assetId);
139+
if (!templateExists) {
140+
logger.error('Could not read template at uri $url from $_entryPoint');
141+
return null;
142+
}
143+
return await _reader.readAsString(assetId);
144+
}
110145
}
111146

112147
/// Visitor responsible for processing the `annotations` property of a
113148
/// [RegisterType] object and pulling out template text.
114149
class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> {
115-
final List<String> templateText = [];
116-
final AssetReader _reader;
150+
final List<String> inlineValues = [];
151+
final List<String> urlValues = [];
117152

118-
_TemplateExtractVisitor(this._reader);
153+
void reset() {
154+
inlineValues.clear();
155+
urlValues.clear();
156+
}
119157

120158
@override
121159
Object visitNamedExpression(NamedExpression node) {
122160
// TODO(kegluneq): Remove this limitation.
123-
if (node.name is Label && node.name.label is SimpleIdentifier) {
124-
var keyString = '${node.name.label}';
125-
if (keyString == 'inline') {
126-
if (node.expression is SimpleStringLiteral) {
127-
templateText.add(stringLiteralToString(node.expression));
128-
} else {
129-
logger.error(
130-
'Angular 2 currently only supports string literals in directives',
131-
' Source: ${node}');
132-
}
133-
}
134-
} else {
161+
if (node.name is! Label || node.name.label is! SimpleIdentifier) {
135162
logger.error(
136163
'Angular 2 currently only supports simple identifiers in directives',
137164
' Source: ${node}');
165+
return null;
166+
}
167+
var keyString = '${node.name.label}';
168+
if (keyString == 'inline' || keyString == 'url') {
169+
if (node.expression is! SimpleStringLiteral) {
170+
logger.error(
171+
'Angular 2 currently only supports string literals in directives',
172+
' Source: ${node}');
173+
return null;
174+
}
175+
var valueString = stringLiteralToString(node.expression);
176+
if (keyString == 'url') {
177+
urlValues.add(valueString);
178+
} else {
179+
inlineValues.add(valueString);
180+
}
138181
}
139-
return super.visitNamedExpression(node);
182+
return null;
140183
}
141184
}

modules/angular2/test/transform/integration/all_tests.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,8 @@ void allTests() {
116116
return value.endsWith('dart') ? formatter.format(code) : code;
117117
});
118118
});
119-
testPhases(config.name, [
120-
[transform]
121-
], config.assetPathToInputPath, config.assetPathToExpectedOutputPath, []);
119+
testPhases(config.name, [[transform]], config.assetPathToInputPath,
120+
config.assetPathToExpectedOutputPath, []);
122121
}
123122
}
124123

modules/angular2/test/transform/integration/list_of_types_files/expected/bar.ng_deps.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ void setupReflection(reflector) {
1212
..registerType(MyComponent, {
1313
'factory': (MyContext c) => new MyComponent(c),
1414
'parameters': const [const [MyContext]],
15-
'annotations': const [
16-
const Component(componentServices: const [MyContext])
17-
]
15+
'annotations':
16+
const [const Component(componentServices: const [MyContext])]
1817
});
1918
}

modules/angular2/test/transform/integration/two_deps_files/expected/bar.ng_deps.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ void setupReflection(reflector) {
1313
'factory':
1414
(prefix.MyContext c, String inValue) => new MyComponent(c, inValue),
1515
'parameters': const [const [prefix.MyContext], const [String]],
16-
'annotations': const [
17-
const Component(selector: prefix.preDefinedSelector)
18-
]
16+
'annotations':
17+
const [const Component(selector: prefix.preDefinedSelector)]
1918
});
2019
}

0 commit comments

Comments
 (0)