feat(dart/transform): Parse `url` values in `Template`s

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.
This commit is contained in:
Tim Blasi 2015-03-19 09:16:01 -07:00
parent d822793229
commit 1a788e6b0d
5 changed files with 107 additions and 66 deletions

View File

@ -42,8 +42,9 @@ class _CtorTransformVisitor extends ToSourceVisitor with VisitorMixin {
ClassDeclaration clazz = ClassDeclaration clazz =
node.getAncestor((node) => node is ClassDeclaration); node.getAncestor((node) => node is ClassDeclaration);
_fieldNameToType.clear(); _fieldNameToType.clear();
clazz.members.where((member) => member is FieldDeclaration).forEach( clazz.members
(FieldDeclaration field) { .where((member) => member is FieldDeclaration)
.forEach((FieldDeclaration field) {
var type = field.fields.type; var type = field.fields.type;
if (type != null) { if (type != null) {
field.fields.variables.forEach((VariableDeclaration decl) { field.fields.variables.forEach((VariableDeclaration decl) {

View File

@ -17,6 +17,7 @@ import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart'; import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/parser.dart'; import 'package:angular2/src/transform/common/parser.dart';
import 'package:barback/barback.dart'; import 'package:barback/barback.dart';
import 'package:code_transformers/assets.dart';
import 'recording_reflection_capabilities.dart'; import 'recording_reflection_capabilities.dart';
@ -28,11 +29,12 @@ import 'recording_reflection_capabilities.dart';
Future<String> processTemplates(AssetReader reader, AssetId entryPoint) async { Future<String> processTemplates(AssetReader reader, AssetId entryPoint) async {
var parser = new Parser(reader); var parser = new Parser(reader);
NgDeps ngDeps = await parser.parse(entryPoint); NgDeps ngDeps = await parser.parse(entryPoint);
var extractor = new _TemplateExtractor(reader, entryPoint);
var registrations = new StringBuffer(); var registrations = new StringBuffer();
ngDeps.registeredTypes.forEach((rType) { for (var rType in ngDeps.registeredTypes) {
_processRegisteredType(reader, rType).forEach((String templateText) { (await extractor.extractTemplates(rType))
var values = _processTemplate(templateText); .forEach((RecordingReflectionCapabilities values) {
var calls = _generateGetters('${rType.typeName}', values.getterNames); var calls = _generateGetters('${rType.typeName}', values.getterNames);
if (calls.isNotEmpty) { if (calls.isNotEmpty) {
registrations.write('..${REGISTER_GETTERS_METHOD_NAME}' registrations.write('..${REGISTER_GETTERS_METHOD_NAME}'
@ -49,7 +51,7 @@ Future<String> processTemplates(AssetReader reader, AssetId entryPoint) async {
'({${calls.join(', ')}})'); '({${calls.join(', ')}})');
} }
}); });
}); }
var code = ngDeps.code; var code = ngDeps.code;
if (registrations.length == 0) return code; if (registrations.length == 0) return code;
@ -60,82 +62,123 @@ Future<String> processTemplates(AssetReader reader, AssetId entryPoint) async {
} }
Iterable<String> _generateGetters(String typeName, List<String> getterNames) { Iterable<String> _generateGetters(String typeName, List<String> getterNames) {
return getterNames.map((prop) { // TODO(kegluneq): Include `typeName` where possible.
// TODO(kegluneq): Include `typeName` where possible. return getterNames.map((prop) => '''
return ''''$prop': (o) => o.$prop'''; '$prop': (o) => o.$prop
}); ''');
} }
Iterable<String> _generateSetters(String typeName, List<String> setterName) { Iterable<String> _generateSetters(String typeName, List<String> setterName) {
return setterName.map((prop) { return setterName.map((prop) => '''
return ''''$prop': (o, v) => o.$prop = v'''; '$prop': (o, v) => o.$prop = v
}); ''');
} }
Iterable<String> _generateMethods(String typeName, List<String> methodNames) { Iterable<String> _generateMethods(String typeName, List<String> methodNames) {
return methodNames.map((methodName) { return methodNames.map((methodName) => '''
return ''''$methodName': (o, List args) => '$methodName': (o, List args) => Function.apply(o.$methodName, args)
Function.apply(o.$methodName, args)'''; ''');
});
} }
RecordingReflectionCapabilities _processTemplate(String templateCode) { /// Extracts `inline` and `url` values from `Template` annotations, reads
var recordingCapabilities = new RecordingReflectionCapabilities(); /// template code if necessary, and determines what values will be
var savedReflectionCapabilities = reflector.reflectionCapabilities; /// reflectively accessed from that template.
reflector.reflectionCapabilities = recordingCapabilities; class _TemplateExtractor {
final AssetReader _reader;
final AssetId _entryPoint;
final CompilePipeline _pipeline;
final _TemplateExtractVisitor _visitor = new _TemplateExtractVisitor();
var compilePipeline = new CompilePipeline(_createCompileSteps()); _TemplateExtractor(this._reader, this._entryPoint)
var template = DOM.createTemplate(templateCode); : _pipeline = new CompilePipeline(_createCompileSteps());
// TODO(kegluneq): Need to parse this from a file when not inline.
compilePipeline.process(template, templateCode);
reflector.reflectionCapabilities = savedReflectionCapabilities; static List<CompileStep> _createCompileSteps() {
return recordingCapabilities; var parser = new ng.Parser(new ng.Lexer());
} // TODO(kegluneq): Add other compile steps from default_steps.dart.
return [
new ViewSplitter(parser),
new PropertyBindingParser(parser),
new TextInterpolationParser(parser)
];
}
List<CompileStep> _createCompileSteps() { Future<List<RecordingReflectionCapabilities>> extractTemplates(
var parser = new ng.Parser(new ng.Lexer()); RegisteredType t) async {
// TODO(kegluneq): Add other compile steps from default_steps.dart. return (await _processRegisteredType(t)).map(_processTemplate).toList();
return [ }
new ViewSplitter(parser),
new PropertyBindingParser(parser),
new TextInterpolationParser(parser)
];
}
List<String> _processRegisteredType(AssetReader reader, RegisteredType t) { RecordingReflectionCapabilities _processTemplate(String templateCode) {
var visitor = new _TemplateExtractVisitor(reader); var recordingCapabilities = new RecordingReflectionCapabilities();
t.annotations.accept(visitor); var savedReflectionCapabilities = reflector.reflectionCapabilities;
return visitor.templateText; reflector.reflectionCapabilities = recordingCapabilities;
_pipeline.process(DOM.createTemplate(templateCode), templateCode);
reflector.reflectionCapabilities = savedReflectionCapabilities;
return recordingCapabilities;
}
Future<List<String>> _processRegisteredType(RegisteredType t) async {
_visitor.reset();
t.annotations.accept(_visitor);
var toReturn = _visitor.inlineValues;
for (var url in _visitor.urlValues) {
var templateText = await _readUrlTemplate(url);
if (templateText != null) {
toReturn.add(templateText);
}
}
return toReturn;
}
// TODO(kegluneq): Rewrite these to `inline` where possible.
// See [https://github.com/angular/angular/issues/1035].
Future<String> _readUrlTemplate(String url) async {
var assetId = uriToAssetId(_entryPoint, url, logger, null);
var templateExists = await _reader.hasInput(assetId);
if (!templateExists) {
logger.error('Could not read template at uri $url from $_entryPoint');
return null;
}
return await _reader.readAsString(assetId);
}
} }
/// Visitor responsible for processing the `annotations` property of a /// Visitor responsible for processing the `annotations` property of a
/// [RegisterType] object and pulling out template text. /// [RegisterType] object and pulling out template text.
class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> { class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> {
final List<String> templateText = []; final List<String> inlineValues = [];
final AssetReader _reader; final List<String> urlValues = [];
_TemplateExtractVisitor(this._reader); void reset() {
inlineValues.clear();
urlValues.clear();
}
@override @override
Object visitNamedExpression(NamedExpression node) { Object visitNamedExpression(NamedExpression node) {
// TODO(kegluneq): Remove this limitation. // TODO(kegluneq): Remove this limitation.
if (node.name is Label && node.name.label is SimpleIdentifier) { 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( logger.error(
'Angular 2 currently only supports simple identifiers in directives', 'Angular 2 currently only supports simple identifiers in directives',
' Source: ${node}'); ' Source: ${node}');
return null;
} }
return super.visitNamedExpression(node); var keyString = '${node.name.label}';
if (keyString == 'inline' || keyString == 'url') {
if (node.expression is! SimpleStringLiteral) {
logger.error(
'Angular 2 currently only supports string literals in directives',
' Source: ${node}');
return null;
}
var valueString = stringLiteralToString(node.expression);
if (keyString == 'url') {
urlValues.add(valueString);
} else {
inlineValues.add(valueString);
}
}
return null;
} }
} }

View File

@ -116,9 +116,8 @@ void allTests() {
return value.endsWith('dart') ? formatter.format(code) : code; return value.endsWith('dart') ? formatter.format(code) : code;
}); });
}); });
testPhases(config.name, [ testPhases(config.name, [[transform]], config.assetPathToInputPath,
[transform] config.assetPathToExpectedOutputPath, []);
], config.assetPathToInputPath, config.assetPathToExpectedOutputPath, []);
} }
} }

View File

@ -12,8 +12,7 @@ void setupReflection(reflector) {
..registerType(MyComponent, { ..registerType(MyComponent, {
'factory': (MyContext c) => new MyComponent(c), 'factory': (MyContext c) => new MyComponent(c),
'parameters': const [const [MyContext]], 'parameters': const [const [MyContext]],
'annotations': const [ 'annotations':
const Component(componentServices: const [MyContext]) const [const Component(componentServices: const [MyContext])]
]
}); });
} }

View File

@ -13,8 +13,7 @@ void setupReflection(reflector) {
'factory': 'factory':
(prefix.MyContext c, String inValue) => new MyComponent(c, inValue), (prefix.MyContext c, String inValue) => new MyComponent(c, inValue),
'parameters': const [const [prefix.MyContext], const [String]], 'parameters': const [const [prefix.MyContext], const [String]],
'annotations': const [ 'annotations':
const Component(selector: prefix.preDefinedSelector) const [const Component(selector: prefix.preDefinedSelector)]
]
}); });
} }