Skip to content

Commit 5d2af54

Browse files
author
Tim Blasi
committed
feat(dart/transform): Improve constant evaluation
Use `package:analyzer`'s `ConstantEvaluator` to read from the AST. This cleanly builds values for us from adjacent strings, interpolations, etc.
1 parent a9be2eb commit 5d2af54

11 files changed

Lines changed: 114 additions & 82 deletions

File tree

modules/angular2/src/transform/bind_generator/visitor.dart

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,20 @@ import 'package:angular2/src/transform/common/logging.dart';
88
/// values found.
99
class ExtractSettersVisitor extends Object with RecursiveAstVisitor<Object> {
1010
final Map<String, String> bindMappings = {};
11-
12-
void _extractFromMapLiteral(MapLiteral map) {
13-
map.entries.forEach((entry) {
14-
// TODO(kegluneq): Remove this restriction
15-
if (entry.key is SimpleStringLiteral &&
16-
entry.value is SimpleStringLiteral) {
17-
bindMappings[stringLiteralToString(entry.key)] =
18-
stringLiteralToString(entry.value);
19-
} else {
20-
logger.error('`properties` currently only supports string literals '
21-
'(${entry})');
22-
}
23-
});
24-
}
11+
final ConstantEvaluator _evaluator = new ConstantEvaluator();
2512

2613
@override
2714
Object visitNamedExpression(NamedExpression node) {
2815
if ('${node.name.label}' == 'properties') {
29-
// TODO(kegluneq): Remove this restriction.
30-
if (node.expression is MapLiteral) {
31-
_extractFromMapLiteral(node.expression);
16+
var evaluated = node.expression.accept(_evaluator);
17+
if (evaluated is Map) {
18+
evaluated.forEach((key, value) {
19+
if (value != null) {
20+
bindMappings[key] = '$value';
21+
}
22+
});
3223
} else {
33-
logger.error('`properties` currently only supports map literals');
24+
logger.error('`properties` currently only supports Map values');
3425
}
3526
return null;
3627
}

modules/angular2/src/transform/common/directive_metadata_reader.dart

Lines changed: 33 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ num _getDirectiveType(String annotationName, Element element) {
4949
class _DirectiveMetadataVisitor extends Object
5050
with RecursiveAstVisitor<Object> {
5151
DirectiveMetadata meta;
52+
final ConstantEvaluator _evaluator = new ConstantEvaluator();
5253

5354
void _createEmptyMetadata(num type) {
5455
assert(type >= 0);
@@ -130,13 +131,12 @@ class _DirectiveMetadataVisitor extends Object
130131
}
131132

132133
String _expressionToString(Expression node, String nodeDescription) {
133-
// TODO(kegluneq): Accept more options.
134-
if (node is! SimpleStringLiteral) {
135-
throw new FormatException(
136-
'Angular 2 currently only supports string literals '
134+
var value = node.accept(_evaluator);
135+
if (value is! String) {
136+
throw new FormatException('Angular 2 could not understand the value '
137137
'in $nodeDescription.', '$node' /* source */);
138138
}
139-
return stringLiteralToString(node);
139+
return value;
140140
}
141141

142142
void _populateSelector(Expression selectorValue) {
@@ -153,72 +153,52 @@ class _DirectiveMetadataVisitor extends Object
153153

154154
void _populateCompileChildren(Expression compileChildrenValue) {
155155
_checkMeta();
156-
if (compileChildrenValue is! BooleanLiteral) {
156+
var evaluated = compileChildrenValue.accept(_evaluator);
157+
if (evaluated is! bool) {
157158
throw new FormatException(
158-
'Angular 2 currently only supports boolean literal values for '
159+
'Angular 2 expects a bool but could not understand the value for '
159160
'Directive#compileChildren.', '$compileChildrenValue' /* source */);
160161
}
161-
meta.compileChildren = (compileChildrenValue as BooleanLiteral).value;
162+
meta.compileChildren = evaluated;
162163
}
163164

164-
void _populateProperties(Expression propertiesValue) {
165-
_checkMeta();
166-
if (propertiesValue is! MapLiteral) {
165+
/// Evaluates the [Map] represented by `expression` and adds all `key`,
166+
/// `value` pairs to `map`. If `expression` does not evaluate to a [Map],
167+
/// throws a descriptive [FormatException].
168+
void _populateMap(Expression expression, Map map, String propertyName) {
169+
var evaluated = expression.accept(_evaluator);
170+
if (evaluated is! Map) {
167171
throw new FormatException(
168-
'Angular 2 currently only supports map literal values for '
169-
'Directive#properties.', '$propertiesValue' /* source */);
170-
}
171-
for (MapLiteralEntry entry in (propertiesValue as MapLiteral).entries) {
172-
var sKey = _expressionToString(entry.key, 'Directive#properties keys');
173-
var sVal = _expressionToString(entry.value, 'Direcive#properties values');
174-
meta.properties[sKey] = sVal;
172+
'Angular 2 expects a Map but could not understand the value for '
173+
'$propertyName.', '$expression' /* source */);
175174
}
175+
evaluated.forEach((key, value) {
176+
if (value != null) {
177+
map[key] = '$value';
178+
}
179+
});
180+
}
181+
182+
void _populateProperties(Expression propertiesValue) {
183+
_checkMeta();
184+
_populateMap(propertiesValue, meta.properties, 'Directive#properties');
176185
}
177186

178187
void _populateHostListeners(Expression hostListenersValue) {
179188
_checkMeta();
180-
if (hostListenersValue is! MapLiteral) {
181-
throw new FormatException(
182-
'Angular 2 currently only supports map literal values for '
183-
'Directive#hostListeners.', '$hostListenersValue' /* source */);
184-
}
185-
for (MapLiteralEntry entry in (hostListenersValue as MapLiteral).entries) {
186-
var sKey = _expressionToString(entry.key, 'Directive#hostListeners keys');
187-
var sVal =
188-
_expressionToString(entry.value, 'Directive#hostListeners values');
189-
meta.hostListeners[sKey] = sVal;
190-
}
189+
_populateMap(
190+
hostListenersValue, meta.hostListeners, 'Directive#hostListeners');
191191
}
192192

193193
void _populateHostProperties(Expression hostPropertyValue) {
194194
_checkMeta();
195-
if (hostPropertyValue is! MapLiteral) {
196-
throw new FormatException(
197-
'Angular 2 currently only supports map literal values for '
198-
'Directive#hostProperties.', '$hostPropertyValue' /* source */);
199-
}
200-
for (MapLiteralEntry entry in (hostPropertyValue as MapLiteral).entries) {
201-
var sKey =
202-
_expressionToString(entry.key, 'Directive#hostProperties keys');
203-
var sVal =
204-
_expressionToString(entry.value, 'Directive#hostProperties values');
205-
meta.hostProperties[sKey] = sVal;
206-
}
195+
_populateMap(
196+
hostPropertyValue, meta.hostProperties, 'Directive#hostProperties');
207197
}
208198

209199
void _populateHostAttributes(Expression hostAttributeValue) {
210200
_checkMeta();
211-
if (hostAttributeValue is! MapLiteral) {
212-
throw new FormatException(
213-
'Angular 2 currently only supports map literal values for '
214-
'Directive#hostAttributes.', '$hostAttributeValue' /* source */);
215-
}
216-
for (MapLiteralEntry entry in (hostAttributeValue as MapLiteral).entries) {
217-
var sKey =
218-
_expressionToString(entry.key, 'Directive#hostAttributes keys');
219-
var sVal =
220-
_expressionToString(entry.value, 'Directive#hostAttributes values');
221-
meta.hostAttributes[sKey] = sVal;
222-
}
201+
_populateMap(
202+
hostAttributeValue, meta.hostAttributes, 'Directive#hostAttributes');
223203
}
224204
}

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ bool _isViewAnnotation(Annotation node) => '${node.name}' == 'View';
210210
class AnnotationsTransformVisitor extends ToSourceVisitor {
211211
final AsyncStringWriter writer;
212212
final XHR _xhr;
213+
final ConstantEvaluator _evaluator = new ConstantEvaluator();
213214
bool _processingView = false;
214215

215216
AnnotationsTransformVisitor(AsyncStringWriter writer, this._xhr)
@@ -257,14 +258,15 @@ class AnnotationsTransformVisitor extends ToSourceVisitor {
257258
return super.visitNamedExpression(node);
258259
}
259260
var keyString = '${node.name.label}';
260-
if (keyString == 'templateUrl' && node.expression is SimpleStringLiteral) {
261-
var url = stringLiteralToString(node.expression);
262-
writer.print("template: r'''");
263-
writer.asyncPrint(_xhr.get(url));
264-
writer.print("'''");
265-
return null;
266-
} else {
267-
return super.visitNamedExpression(node);
261+
if (keyString == 'templateUrl') {
262+
var url = node.expression.accept(_evaluator);
263+
if (url is String) {
264+
writer.print("template: r'''");
265+
writer.asyncPrint(_xhr.get(url));
266+
writer.print("'''");
267+
return null;
268+
}
268269
}
270+
return super.visitNamedExpression(node);
269271
}
270272
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ class CompileStepFactory implements base.CompileStepFactory {
1717

1818
List<CompileStep> createSteps(
1919
ViewDefinition template, List<Future> subTaskPromises) {
20-
// TODO(kegluneq): Add other compile steps from default_steps.dart.
2120
return [
2221
new ViewSplitter(_parser),
2322
new PropertyBindingParser(_parser),

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ class _ViewDefinitionCreator {
155155
class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> {
156156
ViewDefinition viewDef = null;
157157
final Map<String, DirectiveMetadata> _metadataMap;
158+
final ConstantEvaluator _evaluator = new ConstantEvaluator();
158159

159160
_TemplateExtractVisitor(this._metadataMap);
160161

@@ -191,13 +192,13 @@ class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> {
191192
// `templateUrl` property.
192193
if (viewDef == null) return null;
193194

194-
if (node.expression is! SimpleStringLiteral) {
195+
var valueString = node.expression.accept(_evaluator);
196+
if (valueString is! String) {
195197
logger.error(
196198
'Angular 2 currently only supports string literals in directives.'
197199
' Source: ${node}');
198200
return null;
199201
}
200-
var valueString = stringLiteralToString(node.expression);
201202
if (keyString == 'templateUrl') {
202203
if (viewDef.absUrl != null) {
203204
logger.error(
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
library bar.ng_deps.dart;
2+
3+
import 'foo.dart';
4+
import 'package:angular2/src/core/annotations/annotations.dart';
5+
6+
var _visited = false;
7+
void initReflector(reflector) {
8+
if (_visited) return;
9+
_visited = true;
10+
reflector
11+
..registerType(FooComponent, {
12+
'factory': () => new FooComponent(),
13+
'parameters': const [],
14+
'annotations': const [const Component(selector: '[fo' 'o]')]
15+
});
16+
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,17 @@ void allTests() {
101101
expect(extractedMeta.selector).toEqual('[foo]');
102102
});
103103

104+
it('should generate `DirectiveMetadata` from .ng_deps.dart files that use '
105+
'automatic adjacent string concatenation.', () async {
106+
var extracted = await extractDirectiveMetadata(reader, new AssetId('a',
107+
'directive_metadata_extractor/adjacent_strings_files/'
108+
'foo.ng_deps.dart'));
109+
expect(extracted).toContain('FooComponent');
110+
111+
var extractedMeta = extracted['FooComponent'];
112+
expect(extractedMeta.selector).toEqual('[foo]');
113+
});
114+
104115
it('should include `DirectiveMetadata` from exported files.', () async {
105116
var extracted = await extractDirectiveMetadata(reader, new AssetId(
106117
'a', 'directive_metadata_extractor/export_files/foo.ng_deps.dart'));

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ void allTests() {
3939

4040
_testNgDeps(
4141
'should inline `templateUrl` values.', 'url_expression_files/hello.dart');
42+
43+
_testNgDeps('should inline `templateUrl`s expressed as adjacent strings.',
44+
'split_url_expression_files/hello.dart');
4245
}
4346

4447
void _testNgDeps(String name, String inputPath,
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.ng_deps.dart;
2+
3+
import 'hello.dart';
4+
import 'package:angular2/angular2.dart'
5+
show bootstrap, Component, Directive, View, NgElement;
6+
7+
var _visited = false;
8+
void initReflector(reflector) {
9+
if (_visited) return;
10+
_visited = true;
11+
reflector
12+
..registerType(HelloCmp, {
13+
'factory': () => new HelloCmp(),
14+
'parameters': const [],
15+
'annotations': const [
16+
const Component(selector: 'hello-app'),
17+
const View(template: r'''{{greeting}}''')
18+
]
19+
});
20+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
library examples.src.hello_world.index_common_dart;
2+
3+
import 'package:angular2/angular2.dart'
4+
show bootstrap, Component, Directive, View, NgElement;
5+
6+
@Component(selector: 'hello-app')
7+
@View(templateUrl: 'templ' 'ate.html')
8+
class HelloCmp {}

0 commit comments

Comments
 (0)