feat(dart/transform): Add simple ParseTemplates step

Generate methods in the ParseTemplates step.
Add a test for inline template method generation.
This commit is contained in:
Tim Blasi
2015-03-13 13:55:49 -07:00
parent b3fa1fa4fa
commit 08b56e1c53
14 changed files with 194 additions and 143 deletions

View File

@ -0,0 +1,141 @@
library angular2.src.transform.template_compiler.generator;
import 'dart:async';
import 'package:analyzer/analyzer.dart';
import 'package:angular2/src/change_detection/parser/lexer.dart' as ng;
import 'package:angular2/src/change_detection/parser/parser.dart' as ng;
import 'package:angular2/src/core/compiler/pipeline/compile_pipeline.dart';
import 'package:angular2/src/core/compiler/pipeline/compile_step.dart';
import 'package:angular2/src/core/compiler/pipeline/property_binding_parser.dart';
import 'package:angular2/src/core/compiler/pipeline/text_interpolation_parser.dart';
import 'package:angular2/src/core/compiler/pipeline/view_splitter.dart';
import 'package:angular2/src/dom/dom_adapter.dart';
import 'package:angular2/src/reflection/reflection.dart';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/parser.dart';
import 'package:barback/barback.dart';
import 'recording_reflection_capabilities.dart';
/// Reads the `.ng_deps.dart` file represented by `entryPoint` and parses any
/// Angular 2 `Template` annotations it declares to generate `getter`s,
/// `setter`s, and `method`s that would otherwise be reflectively accessed.
///
/// This method assumes a [DomAdapter] has been registered.
Future<String> processTemplates(AssetReader reader, AssetId entryPoint) async {
var parser = new Parser(reader);
NgDeps ngDeps = await parser.parse(entryPoint);
var registrations = new StringBuffer();
ngDeps.registeredTypes.forEach((rType) {
_processRegisteredType(reader, rType).forEach((String templateText) {
var values = _processTemplate(templateText);
var calls = _generateGetters('${rType.typeName}', values.getterNames);
if (calls.isNotEmpty) {
registrations.write('..${REGISTER_GETTERS_METHOD_NAME}'
'({${calls.join(', ')}})');
}
calls = _generateSetters('${rType.typeName}', values.setterNames);
if (calls.isNotEmpty) {
registrations.write('..${REGISTER_SETTERS_METHOD_NAME}'
'({${calls.join(', ')}})');
}
calls = _generateMethods('${rType.typeName}', values.methodNames);
if (calls.isNotEmpty) {
registrations.write('..${REGISTER_METHODS_METHOD_NAME}'
'({${calls.join(', ')}})');
}
});
});
var code = ngDeps.code;
if (registrations.length == 0) return code;
var codeInjectIdx = ngDeps.registeredTypes.last.registerMethod.end;
return '${code.substring(0, codeInjectIdx)}'
'${registrations}'
'${code.substring(codeInjectIdx)}';
}
Iterable<String> _generateGetters(String typeName, List<String> getterNames) {
return getterNames.map((prop) {
// TODO(kegluneq): Include `typeName` where possible.
return ''''$prop': (o) => o.$prop''';
});
}
Iterable<String> _generateSetters(String typeName, List<String> setterName) {
return setterName.map((prop) {
return ''''$prop': (o, v) => o.$prop = v''';
});
}
Iterable<String> _generateMethods(String typeName, List<String> methodNames) {
return methodNames.map((methodName) {
return ''''$methodName': (o, List args) =>
Function.apply(o.$methodName, args)''';
});
}
RecordingReflectionCapabilities _processTemplate(String templateCode) {
var recordingCapabilities = new RecordingReflectionCapabilities();
var savedReflectionCapabilities = reflector.reflectionCapabilities;
reflector.reflectionCapabilities = recordingCapabilities;
var compilePipeline = new CompilePipeline(_createCompileSteps());
var template = DOM.createTemplate(templateCode);
// TODO(kegluneq): Need to parse this from a file when not inline.
compilePipeline.process(template, templateCode);
reflector.reflectionCapabilities = savedReflectionCapabilities;
return recordingCapabilities;
}
List<CompileStep> _createCompileSteps() {
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<String> _processRegisteredType(AssetReader reader, RegisteredType t) {
var visitor = new _TemplateExtractVisitor(reader);
t.annotations.accept(visitor);
return visitor.templateText;
}
/// Visitor responsible for processing the `annotations` property of a
/// [RegisterType] object and pulling out template text.
class _TemplateExtractVisitor extends Object with RecursiveAstVisitor<Object> {
final List<String> templateText = [];
final AssetReader _reader;
_TemplateExtractVisitor(this._reader);
@override
Object visitNamedExpression(NamedExpression node) {
// TODO(kegluneq): Remove this limitation.
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(
'Angular 2 currently only supports simple identifiers in directives',
' Source: ${node}');
}
return super.visitNamedExpression(node);
}
}

View File

@ -0,0 +1,45 @@
library angular2.src.transform.template_compiler.recording_reflection_capabilities;
import 'package:angular2/src/reflection/reflection_capabilities.dart';
import 'package:angular2/src/reflection/types.dart';
/// ReflectionCapabilities object that records requests for `getter`s,
/// `setter`s, and `method`s so these can be code generated rather than
/// reflectively accessed at runtime.
class RecordingReflectionCapabilities implements ReflectionCapabilities {
/// The names of all requested `getter`s.
final List<String> getterNames = [];
/// The names of all requested `setter`s.
final List<String> setterNames = [];
/// The names of all requested `method`s.
final List<String> methodNames = [];
void _notImplemented(String name) {
throw 'Not implemented: $name';
}
Function factory(Type type) => _notImplemented('factory');
List<List> parameters(typeOrFunc) => _notImplemented('parameters');
List annotations(typeOrFunc) => _notImplemented('annotations');
static GetterFn _nullGetter = (Object p) => null;
static SetterFn _nullSetter = (Object p, v) => null;
static MethodFn _nullMethod = (Object p, List a) => null;
GetterFn getter(String name) {
getterNames.add(name);
return _nullGetter;
}
SetterFn setter(String name) {
setterNames.add(name);
return _nullSetter;
}
MethodFn method(String name) {
methodNames.add(name);
return _nullMethod;
}
}

View File

@ -0,0 +1,46 @@
library angular2.src.transform.template_compiler.transformer;
import 'dart:async';
import 'package:angular2/src/dom/html5lib_adapter.dart';
import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/formatter.dart';
import 'package:angular2/src/transform/common/logging.dart' as log;
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/options.dart';
import 'package:barback/barback.dart';
import 'generator.dart';
/// [Transformer] responsible for detecting and processing Angular 2 templates.
///
/// [TemplateComplier] uses the Angular 2 `Compiler` to process the templates,
/// extracting information about what reflection is necessary to render and
/// use that template. It then generates code in place of those reflective
/// accesses.
class TemplateComplier extends Transformer {
final TransformerOptions options;
TemplateComplier(this.options);
@override
bool isPrimary(AssetId id) => id.path.endsWith(DEPS_EXTENSION);
@override
Future apply(Transform transform) async {
log.init(transform);
try {
Html5LibDomAdapter.makeCurrent();
var id = transform.primaryInput.id;
var reader = new AssetReader.fromTransform(transform);
var transformedCode =
formatter.format(await processTemplates(reader, id));
transform.addOutput(new Asset.fromString(id, transformedCode));
} catch (ex, stackTrace) {
log.logger.error('Parsing ng templates failed.\n'
'Exception: $ex\n'
'Stack Trace: $stackTrace');
}
}
}