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:
141
modules/angular2/src/transform/template_compiler/generator.dart
Normal file
141
modules/angular2/src/transform/template_compiler/generator.dart
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user