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.
This commit is contained in:
parent
a9be2ebf1b
commit
5d2af54730
|
@ -8,29 +8,20 @@ import 'package:angular2/src/transform/common/logging.dart';
|
||||||
/// values found.
|
/// values found.
|
||||||
class ExtractSettersVisitor extends Object with RecursiveAstVisitor<Object> {
|
class ExtractSettersVisitor extends Object with RecursiveAstVisitor<Object> {
|
||||||
final Map<String, String> bindMappings = {};
|
final Map<String, String> bindMappings = {};
|
||||||
|
final ConstantEvaluator _evaluator = new ConstantEvaluator();
|
||||||
void _extractFromMapLiteral(MapLiteral map) {
|
|
||||||
map.entries.forEach((entry) {
|
|
||||||
// TODO(kegluneq): Remove this restriction
|
|
||||||
if (entry.key is SimpleStringLiteral &&
|
|
||||||
entry.value is SimpleStringLiteral) {
|
|
||||||
bindMappings[stringLiteralToString(entry.key)] =
|
|
||||||
stringLiteralToString(entry.value);
|
|
||||||
} else {
|
|
||||||
logger.error('`properties` currently only supports string literals '
|
|
||||||
'(${entry})');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Object visitNamedExpression(NamedExpression node) {
|
Object visitNamedExpression(NamedExpression node) {
|
||||||
if ('${node.name.label}' == 'properties') {
|
if ('${node.name.label}' == 'properties') {
|
||||||
// TODO(kegluneq): Remove this restriction.
|
var evaluated = node.expression.accept(_evaluator);
|
||||||
if (node.expression is MapLiteral) {
|
if (evaluated is Map) {
|
||||||
_extractFromMapLiteral(node.expression);
|
evaluated.forEach((key, value) {
|
||||||
|
if (value != null) {
|
||||||
|
bindMappings[key] = '$value';
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
logger.error('`properties` currently only supports map literals');
|
logger.error('`properties` currently only supports Map values');
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,7 @@ num _getDirectiveType(String annotationName, Element element) {
|
||||||
class _DirectiveMetadataVisitor extends Object
|
class _DirectiveMetadataVisitor extends Object
|
||||||
with RecursiveAstVisitor<Object> {
|
with RecursiveAstVisitor<Object> {
|
||||||
DirectiveMetadata meta;
|
DirectiveMetadata meta;
|
||||||
|
final ConstantEvaluator _evaluator = new ConstantEvaluator();
|
||||||
|
|
||||||
void _createEmptyMetadata(num type) {
|
void _createEmptyMetadata(num type) {
|
||||||
assert(type >= 0);
|
assert(type >= 0);
|
||||||
|
@ -130,13 +131,12 @@ class _DirectiveMetadataVisitor extends Object
|
||||||
}
|
}
|
||||||
|
|
||||||
String _expressionToString(Expression node, String nodeDescription) {
|
String _expressionToString(Expression node, String nodeDescription) {
|
||||||
// TODO(kegluneq): Accept more options.
|
var value = node.accept(_evaluator);
|
||||||
if (node is! SimpleStringLiteral) {
|
if (value is! String) {
|
||||||
throw new FormatException(
|
throw new FormatException('Angular 2 could not understand the value '
|
||||||
'Angular 2 currently only supports string literals '
|
|
||||||
'in $nodeDescription.', '$node' /* source */);
|
'in $nodeDescription.', '$node' /* source */);
|
||||||
}
|
}
|
||||||
return stringLiteralToString(node);
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _populateSelector(Expression selectorValue) {
|
void _populateSelector(Expression selectorValue) {
|
||||||
|
@ -153,72 +153,52 @@ class _DirectiveMetadataVisitor extends Object
|
||||||
|
|
||||||
void _populateCompileChildren(Expression compileChildrenValue) {
|
void _populateCompileChildren(Expression compileChildrenValue) {
|
||||||
_checkMeta();
|
_checkMeta();
|
||||||
if (compileChildrenValue is! BooleanLiteral) {
|
var evaluated = compileChildrenValue.accept(_evaluator);
|
||||||
|
if (evaluated is! bool) {
|
||||||
throw new FormatException(
|
throw new FormatException(
|
||||||
'Angular 2 currently only supports boolean literal values for '
|
'Angular 2 expects a bool but could not understand the value for '
|
||||||
'Directive#compileChildren.', '$compileChildrenValue' /* source */);
|
'Directive#compileChildren.', '$compileChildrenValue' /* source */);
|
||||||
}
|
}
|
||||||
meta.compileChildren = (compileChildrenValue as BooleanLiteral).value;
|
meta.compileChildren = evaluated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluates the [Map] represented by `expression` and adds all `key`,
|
||||||
|
/// `value` pairs to `map`. If `expression` does not evaluate to a [Map],
|
||||||
|
/// throws a descriptive [FormatException].
|
||||||
|
void _populateMap(Expression expression, Map map, String propertyName) {
|
||||||
|
var evaluated = expression.accept(_evaluator);
|
||||||
|
if (evaluated is! Map) {
|
||||||
|
throw new FormatException(
|
||||||
|
'Angular 2 expects a Map but could not understand the value for '
|
||||||
|
'$propertyName.', '$expression' /* source */);
|
||||||
|
}
|
||||||
|
evaluated.forEach((key, value) {
|
||||||
|
if (value != null) {
|
||||||
|
map[key] = '$value';
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _populateProperties(Expression propertiesValue) {
|
void _populateProperties(Expression propertiesValue) {
|
||||||
_checkMeta();
|
_checkMeta();
|
||||||
if (propertiesValue is! MapLiteral) {
|
_populateMap(propertiesValue, meta.properties, 'Directive#properties');
|
||||||
throw new FormatException(
|
|
||||||
'Angular 2 currently only supports map literal values for '
|
|
||||||
'Directive#properties.', '$propertiesValue' /* source */);
|
|
||||||
}
|
|
||||||
for (MapLiteralEntry entry in (propertiesValue as MapLiteral).entries) {
|
|
||||||
var sKey = _expressionToString(entry.key, 'Directive#properties keys');
|
|
||||||
var sVal = _expressionToString(entry.value, 'Direcive#properties values');
|
|
||||||
meta.properties[sKey] = sVal;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _populateHostListeners(Expression hostListenersValue) {
|
void _populateHostListeners(Expression hostListenersValue) {
|
||||||
_checkMeta();
|
_checkMeta();
|
||||||
if (hostListenersValue is! MapLiteral) {
|
_populateMap(
|
||||||
throw new FormatException(
|
hostListenersValue, meta.hostListeners, 'Directive#hostListeners');
|
||||||
'Angular 2 currently only supports map literal values for '
|
|
||||||
'Directive#hostListeners.', '$hostListenersValue' /* source */);
|
|
||||||
}
|
|
||||||
for (MapLiteralEntry entry in (hostListenersValue as MapLiteral).entries) {
|
|
||||||
var sKey = _expressionToString(entry.key, 'Directive#hostListeners keys');
|
|
||||||
var sVal =
|
|
||||||
_expressionToString(entry.value, 'Directive#hostListeners values');
|
|
||||||
meta.hostListeners[sKey] = sVal;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _populateHostProperties(Expression hostPropertyValue) {
|
void _populateHostProperties(Expression hostPropertyValue) {
|
||||||
_checkMeta();
|
_checkMeta();
|
||||||
if (hostPropertyValue is! MapLiteral) {
|
_populateMap(
|
||||||
throw new FormatException(
|
hostPropertyValue, meta.hostProperties, 'Directive#hostProperties');
|
||||||
'Angular 2 currently only supports map literal values for '
|
|
||||||
'Directive#hostProperties.', '$hostPropertyValue' /* source */);
|
|
||||||
}
|
|
||||||
for (MapLiteralEntry entry in (hostPropertyValue as MapLiteral).entries) {
|
|
||||||
var sKey =
|
|
||||||
_expressionToString(entry.key, 'Directive#hostProperties keys');
|
|
||||||
var sVal =
|
|
||||||
_expressionToString(entry.value, 'Directive#hostProperties values');
|
|
||||||
meta.hostProperties[sKey] = sVal;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _populateHostAttributes(Expression hostAttributeValue) {
|
void _populateHostAttributes(Expression hostAttributeValue) {
|
||||||
_checkMeta();
|
_checkMeta();
|
||||||
if (hostAttributeValue is! MapLiteral) {
|
_populateMap(
|
||||||
throw new FormatException(
|
hostAttributeValue, meta.hostAttributes, 'Directive#hostAttributes');
|
||||||
'Angular 2 currently only supports map literal values for '
|
|
||||||
'Directive#hostAttributes.', '$hostAttributeValue' /* source */);
|
|
||||||
}
|
|
||||||
for (MapLiteralEntry entry in (hostAttributeValue as MapLiteral).entries) {
|
|
||||||
var sKey =
|
|
||||||
_expressionToString(entry.key, 'Directive#hostAttributes keys');
|
|
||||||
var sVal =
|
|
||||||
_expressionToString(entry.value, 'Directive#hostAttributes values');
|
|
||||||
meta.hostAttributes[sKey] = sVal;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,6 +210,7 @@ bool _isViewAnnotation(Annotation node) => '${node.name}' == 'View';
|
||||||
class AnnotationsTransformVisitor extends ToSourceVisitor {
|
class AnnotationsTransformVisitor extends ToSourceVisitor {
|
||||||
final AsyncStringWriter writer;
|
final AsyncStringWriter writer;
|
||||||
final XHR _xhr;
|
final XHR _xhr;
|
||||||
|
final ConstantEvaluator _evaluator = new ConstantEvaluator();
|
||||||
bool _processingView = false;
|
bool _processingView = false;
|
||||||
|
|
||||||
AnnotationsTransformVisitor(AsyncStringWriter writer, this._xhr)
|
AnnotationsTransformVisitor(AsyncStringWriter writer, this._xhr)
|
||||||
|
@ -257,14 +258,15 @@ class AnnotationsTransformVisitor extends ToSourceVisitor {
|
||||||
return super.visitNamedExpression(node);
|
return super.visitNamedExpression(node);
|
||||||
}
|
}
|
||||||
var keyString = '${node.name.label}';
|
var keyString = '${node.name.label}';
|
||||||
if (keyString == 'templateUrl' && node.expression is SimpleStringLiteral) {
|
if (keyString == 'templateUrl') {
|
||||||
var url = stringLiteralToString(node.expression);
|
var url = node.expression.accept(_evaluator);
|
||||||
|
if (url is String) {
|
||||||
writer.print("template: r'''");
|
writer.print("template: r'''");
|
||||||
writer.asyncPrint(_xhr.get(url));
|
writer.asyncPrint(_xhr.get(url));
|
||||||
writer.print("'''");
|
writer.print("'''");
|
||||||
return null;
|
return null;
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
return super.visitNamedExpression(node);
|
return super.visitNamedExpression(node);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ class CompileStepFactory implements base.CompileStepFactory {
|
||||||
|
|
||||||
List<CompileStep> createSteps(
|
List<CompileStep> createSteps(
|
||||||
ViewDefinition template, List<Future> subTaskPromises) {
|
ViewDefinition template, List<Future> subTaskPromises) {
|
||||||
// TODO(kegluneq): Add other compile steps from default_steps.dart.
|
|
||||||
return [
|
return [
|
||||||
new ViewSplitter(_parser),
|
new ViewSplitter(_parser),
|
||||||
new PropertyBindingParser(_parser),
|
new PropertyBindingParser(_parser),
|
||||||
|
|
|
@ -155,6 +155,7 @@ class _ViewDefinitionCreator {
|
||||||
class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> {
|
class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> {
|
||||||
ViewDefinition viewDef = null;
|
ViewDefinition viewDef = null;
|
||||||
final Map<String, DirectiveMetadata> _metadataMap;
|
final Map<String, DirectiveMetadata> _metadataMap;
|
||||||
|
final ConstantEvaluator _evaluator = new ConstantEvaluator();
|
||||||
|
|
||||||
_TemplateExtractVisitor(this._metadataMap);
|
_TemplateExtractVisitor(this._metadataMap);
|
||||||
|
|
||||||
|
@ -191,13 +192,13 @@ class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> {
|
||||||
// `templateUrl` property.
|
// `templateUrl` property.
|
||||||
if (viewDef == null) return null;
|
if (viewDef == null) return null;
|
||||||
|
|
||||||
if (node.expression is! SimpleStringLiteral) {
|
var valueString = node.expression.accept(_evaluator);
|
||||||
|
if (valueString is! String) {
|
||||||
logger.error(
|
logger.error(
|
||||||
'Angular 2 currently only supports string literals in directives.'
|
'Angular 2 currently only supports string literals in directives.'
|
||||||
' Source: ${node}');
|
' Source: ${node}');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
var valueString = stringLiteralToString(node.expression);
|
|
||||||
if (keyString == 'templateUrl') {
|
if (keyString == 'templateUrl') {
|
||||||
if (viewDef.absUrl != null) {
|
if (viewDef.absUrl != null) {
|
||||||
logger.error(
|
logger.error(
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
library bar.ng_deps.dart;
|
||||||
|
|
||||||
|
import 'foo.dart';
|
||||||
|
import 'package:angular2/src/core/annotations/annotations.dart';
|
||||||
|
|
||||||
|
var _visited = false;
|
||||||
|
void initReflector(reflector) {
|
||||||
|
if (_visited) return;
|
||||||
|
_visited = true;
|
||||||
|
reflector
|
||||||
|
..registerType(FooComponent, {
|
||||||
|
'factory': () => new FooComponent(),
|
||||||
|
'parameters': const [],
|
||||||
|
'annotations': const [const Component(selector: '[fo' 'o]')]
|
||||||
|
});
|
||||||
|
}
|
|
@ -101,6 +101,17 @@ void allTests() {
|
||||||
expect(extractedMeta.selector).toEqual('[foo]');
|
expect(extractedMeta.selector).toEqual('[foo]');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should generate `DirectiveMetadata` from .ng_deps.dart files that use '
|
||||||
|
'automatic adjacent string concatenation.', () async {
|
||||||
|
var extracted = await extractDirectiveMetadata(reader, new AssetId('a',
|
||||||
|
'directive_metadata_extractor/adjacent_strings_files/'
|
||||||
|
'foo.ng_deps.dart'));
|
||||||
|
expect(extracted).toContain('FooComponent');
|
||||||
|
|
||||||
|
var extractedMeta = extracted['FooComponent'];
|
||||||
|
expect(extractedMeta.selector).toEqual('[foo]');
|
||||||
|
});
|
||||||
|
|
||||||
it('should include `DirectiveMetadata` from exported files.', () async {
|
it('should include `DirectiveMetadata` from exported files.', () async {
|
||||||
var extracted = await extractDirectiveMetadata(reader, new AssetId(
|
var extracted = await extractDirectiveMetadata(reader, new AssetId(
|
||||||
'a', 'directive_metadata_extractor/export_files/foo.ng_deps.dart'));
|
'a', 'directive_metadata_extractor/export_files/foo.ng_deps.dart'));
|
||||||
|
|
|
@ -39,6 +39,9 @@ void allTests() {
|
||||||
|
|
||||||
_testNgDeps(
|
_testNgDeps(
|
||||||
'should inline `templateUrl` values.', 'url_expression_files/hello.dart');
|
'should inline `templateUrl` values.', 'url_expression_files/hello.dart');
|
||||||
|
|
||||||
|
_testNgDeps('should inline `templateUrl`s expressed as adjacent strings.',
|
||||||
|
'split_url_expression_files/hello.dart');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _testNgDeps(String name, String inputPath,
|
void _testNgDeps(String name, String inputPath,
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
library examples.src.hello_world.index_common_dart.ng_deps.dart;
|
||||||
|
|
||||||
|
import 'hello.dart';
|
||||||
|
import 'package:angular2/angular2.dart'
|
||||||
|
show bootstrap, Component, Directive, View, NgElement;
|
||||||
|
|
||||||
|
var _visited = false;
|
||||||
|
void initReflector(reflector) {
|
||||||
|
if (_visited) return;
|
||||||
|
_visited = true;
|
||||||
|
reflector
|
||||||
|
..registerType(HelloCmp, {
|
||||||
|
'factory': () => new HelloCmp(),
|
||||||
|
'parameters': const [],
|
||||||
|
'annotations': const [
|
||||||
|
const Component(selector: 'hello-app'),
|
||||||
|
const View(template: r'''{{greeting}}''')
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
library examples.src.hello_world.index_common_dart;
|
||||||
|
|
||||||
|
import 'package:angular2/angular2.dart'
|
||||||
|
show bootstrap, Component, Directive, View, NgElement;
|
||||||
|
|
||||||
|
@Component(selector: 'hello-app')
|
||||||
|
@View(templateUrl: 'templ' 'ate.html')
|
||||||
|
class HelloCmp {}
|
|
@ -0,0 +1 @@
|
||||||
|
{{greeting}}
|
Loading…
Reference in New Issue