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:
parent
d822793229
commit
1a788e6b0d
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, []);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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])]
|
||||||
]
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
]
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue