refactor(dart/transform): Remove unnecessary getter/setter codegen

Currently the transformer generates all getters and setters even when
creating pre-generated change detectors, which remove the need for them.

Generate getters and setters via the model provided by `ProtoViewDto`,
which contains enough information to allow omitting unnecessary getters
and setters from code output.

Allow generating getters, setters, and method names which are Dart
pseudo keywords.

Closes #3489
This commit is contained in:
Tim Blasi
2015-08-07 11:54:43 -07:00
parent ba2c077b01
commit 104302a958
26 changed files with 791 additions and 106 deletions

View File

@ -23,8 +23,9 @@ import 'package:barback/barback.dart';
import 'change_detector_codegen.dart' as change;
import 'compile_step_factory.dart';
import 'reflection_capabilities.dart';
import 'reflector_register_codegen.dart' as reg;
import 'reflection/codegen.dart' as reg;
import 'reflection/processor.dart' as reg;
import 'reflection/reflection_capabilities.dart';
import 'view_definition_creator.dart';
/// Reads the `.ng_deps.dart` file represented by `entryPoint` and parses any
@ -42,23 +43,22 @@ Future<String> processTemplates(AssetReader reader, AssetId entryPoint,
var extractor = new _TemplateExtractor(new DomElementSchemaRegistry(),
new TemplateCloner(-1), new XhrImpl(reader, entryPoint));
var registrations = new reg.Codegen();
final processor = new reg.Processor();
var changeDetectorClasses = new change.Codegen();
for (var rType in viewDefResults.viewDefinitions.keys) {
var viewDefEntry = viewDefResults.viewDefinitions[rType];
var result = await extractor.extractTemplates(viewDefEntry.viewDef);
if (result == null) continue;
var protoView = await extractor.extractTemplates(viewDefEntry.viewDef);
if (protoView == null) continue;
if (generateRegistrations) {
// TODO(kegluneq): Generate these getters & setters based on the
// `ProtoViewDto` rather than querying the `ReflectionCapabilities`.
registrations.generate(result.recording);
processor.process(viewDefEntry, protoView);
}
if (result.protoView != null && generateChangeDetectors) {
if (generateChangeDetectors) {
var saved = reflector.reflectionCapabilities;
reflector.reflectionCapabilities = const NullReflectionCapabilities();
var defs = getChangeDetectorDefinitions(viewDefEntry.hostMetadata,
result.protoView, viewDefEntry.viewDef.directives);
protoView, viewDefEntry.viewDef.directives);
for (var i = 0; i < defs.length; ++i) {
changeDetectorClasses.generate('${rType.typeName}',
'_${rType.typeName}_ChangeDetector$i', defs[i]);
@ -67,6 +67,10 @@ Future<String> processTemplates(AssetReader reader, AssetId entryPoint,
}
}
// TODO(kegluneq): Do not hard-code `false` here once i/3436 is fixed.
final registrations = new reg.Codegen(generateChangeDetectors: false);
registrations.generate(processor);
var code = viewDefResults.ngDeps.code;
if (registrations.isEmpty && changeDetectorClasses.isEmpty) return code;
var importInjectIdx =
@ -93,19 +97,16 @@ class _TemplateExtractor {
ElementSchemaRegistry _schemaRegistry;
TemplateCloner _templateCloner;
_TemplateExtractor(ElementSchemaRegistry schemaRegistry,
TemplateCloner templateCloner, XHR xhr)
_TemplateExtractor(this._schemaRegistry, this._templateCloner, XHR xhr)
: _factory = new CompileStepFactory(new ng.Parser(new ng.Lexer())) {
var urlResolver = new UrlResolver();
var styleUrlResolver = new StyleUrlResolver(urlResolver);
var styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
_loader = new ViewLoader(xhr, styleInliner, styleUrlResolver);
_schemaRegistry = schemaRegistry;
_templateCloner = templateCloner;
}
Future<_ExtractResult> extractTemplates(ViewDefinition viewDef) async {
Future<ProtoViewDto> extractTemplates(ViewDefinition viewDef) async {
// Check for "imperative views".
if (viewDef.template == null && viewDef.templateAbsUrl == null) return null;
@ -115,8 +116,7 @@ class _TemplateExtractor {
// operations between saving and restoring it, otherwise we can get into
// a bad state. See issue #2359 for additional context.
var savedReflectionCapabilities = reflector.reflectionCapabilities;
var recordingCapabilities = new RecordingReflectionCapabilities();
reflector.reflectionCapabilities = recordingCapabilities;
reflector.reflectionCapabilities = const NullReflectionCapabilities();
var pipeline = new CompilePipeline(_factory.createSteps(viewDef));
@ -130,13 +130,6 @@ class _TemplateExtractor {
reflector.reflectionCapabilities = savedReflectionCapabilities;
return new _ExtractResult(recordingCapabilities, protoViewDto);
return protoViewDto;
}
}
class _ExtractResult {
final RecordingReflectionCapabilities recording;
final ProtoViewDto protoView;
_ExtractResult(this.recording, this.protoView);
}

View File

@ -1,25 +1,32 @@
library angular2.transform.template_compiler.reflector_register_codegen;
library angular2.transform.template_compiler.reflection.codegen;
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/property_utils.dart' as prop;
import 'reflection_capabilities.dart';
import 'model.dart';
class Codegen {
final StringBuffer _buf = new StringBuffer();
void generate(RecordingReflectionCapabilities recording) {
if (recording != null) {
var calls = _generateGetters(recording.getterNames);
/// Whether we are pre-generating change detectors.
/// If we have pre-generated change detectors, we need
final bool generateChangeDetectors;
Codegen({this.generateChangeDetectors});
void generate(CodegenModel model) {
if (model != null) {
var calls = _generateGetters(_extractNames(model.getterNames));
if (calls.isNotEmpty) {
_buf.write('..${REGISTER_GETTERS_METHOD_NAME}'
'({${calls.join(', ')}})');
}
calls = _generateSetters(recording.setterNames);
calls = _generateSetters(_extractNames(model.setterNames));
if (calls.isNotEmpty) {
_buf.write('..${REGISTER_SETTERS_METHOD_NAME}'
'({${calls.join(', ')}})');
}
calls = _generateMethods(recording.methodNames);
calls = _generateMethods(_extractNames(model.methodNames));
if (calls.isNotEmpty) {
_buf.write('..${REGISTER_METHODS_METHOD_NAME}'
'({${calls.join(', ')}})');
@ -27,6 +34,15 @@ class Codegen {
}
}
Iterable<String> _extractNames(Iterable<ReflectiveAccessor> accessors) {
var names = accessors.where((accessor) {
return accessor.isStaticallyNecessary || !generateChangeDetectors;
}).map((accessor) => accessor.sanitizedName);
var nameList = names.toList();
nameList.sort();
return nameList;
}
bool get isEmpty => _buf.isEmpty;
@override

View File

@ -0,0 +1,49 @@
library angular2.transform.template_compiler.reflection.model;
import 'package:angular2/src/render/dom/util.dart';
/// Defines the names of getters, setters, and methods which need to be
/// available to Angular 2 via the `reflector` at runtime.
/// See [angular2.src.reflection.reflector] for details.
abstract class CodegenModel {
Iterable<ReflectiveAccessor> get getterNames;
Iterable<ReflectiveAccessor> get methodNames;
Iterable<ReflectiveAccessor> get setterNames;
}
/// Wraps a getter, setter, or method that we may need to access reflectively in
/// an Angular2 app.
/// This is essentially a wrapper for `sanitizedName`, which is the name of the
/// actual getter, setter, or method that will be registered. Note that
/// `operator==` and `hashCode` basically forward to `sanitizedName`.
class ReflectiveAccessor {
/// The value in the Ast determining that we need this accessor. This is the
/// value that is actually present in the template, which may not directly
/// correspond to the model on the `Component`.
final String astValue;
/// The sanitized name of this accessor. This is the name of the getter,
/// setter, or method on the `Component`.
final String sanitizedName;
/// Whether this getter, setter, or method is still necessary when we have
/// pre-generated change detectors.
final bool isStaticallyNecessary;
ReflectiveAccessor(String astValue, {this.isStaticallyNecessary})
: this.astValue = astValue,
this.sanitizedName = sanitizePropertyName(astValue);
@override
bool operator ==(other) {
if (other is! ReflectiveAccessor) return false;
return sanitizedName == other.sanitizedName;
}
@override
int get hashCode => sanitizedName.hashCode;
}
String sanitizePropertyName(String name) {
return dashCaseToCamelCase(EventConfig.parse(name).fieldName);
}

View File

@ -0,0 +1,124 @@
library angular2.transform.template_compiler.reflection.processor;
import 'package:angular2/src/change_detection/parser/ast.dart';
import 'package:angular2/src/render/api.dart';
import 'package:angular2/src/transform/template_compiler/view_definition_creator.dart';
import 'model.dart';
class Processor implements CodegenModel {
/// The names of all requested `getter`s.
final Set<ReflectiveAccessor> getterNames = new Set<ReflectiveAccessor>();
/// The names of all requested `setter`s.
final Set<ReflectiveAccessor> setterNames = new Set<ReflectiveAccessor>();
/// The names of all requested `method`s.
final Set<ReflectiveAccessor> methodNames = new Set<ReflectiveAccessor>();
_NgAstVisitor _visitor;
Processor() {
_visitor = new _NgAstVisitor(this);
}
void process(ViewDefinitionEntry viewDefEntry, ProtoViewDto protoViewDto) {
_processViewDefinition(viewDefEntry);
_processProtoViewDto(protoViewDto);
}
/// Extracts the names of necessary getters from the events in host and
/// dependent [DirectiveMetadata].
void _processViewDefinition(ViewDefinitionEntry viewDefEntry) {
// These are necessary even with generated change detectors.
if (viewDefEntry.hostMetadata != null &&
viewDefEntry.hostMetadata.events != null) {
viewDefEntry.hostMetadata.events.forEach((eventName) {
getterNames.add(
new ReflectiveAccessor(eventName, isStaticallyNecessary: true));
});
}
}
void _processProtoViewDto(ProtoViewDto protoViewDto) {
_visitor.isStaticallyNecessary = false;
protoViewDto.textBindings.forEach((ast) => ast.visit(_visitor));
protoViewDto.elementBinders.forEach((binder) {
binder.propertyBindings.forEach((binding) {
binding.astWithSource.visit(_visitor);
setterNames.add(new ReflectiveAccessor(binding.property,
isStaticallyNecessary: false));
});
binder.directives.forEach((directiveBinding) {
directiveBinding.propertyBindings.values
.forEach((propBinding) => propBinding.visit(_visitor));
directiveBinding.propertyBindings.keys.forEach((bindingName) {
setterNames.add(new ReflectiveAccessor(bindingName,
isStaticallyNecessary: false));
});
directiveBinding.hostPropertyBindings.forEach((elementBinding) {
elementBinding.astWithSource.visit(_visitor);
setterNames.add(new ReflectiveAccessor(elementBinding.property,
isStaticallyNecessary: false));
});
});
binder.eventBindings
.forEach((eventBinding) => eventBinding.source.visit(_visitor));
binder.directives.forEach((directiveBinding) {
directiveBinding.eventBindings
.forEach((eventBinding) => eventBinding.source.visit(_visitor));
});
if (binder.nestedProtoView != null) {
_processProtoViewDto(binder.nestedProtoView);
}
});
}
}
class _NgAstVisitor extends RecursiveAstVisitor {
final Processor _result;
/// Whether any getters or setters recorded are necessary when running
/// statically. A getter or setter that is necessary only for change detection
/// is not necessary when running statically because all accesses are handled
/// by the dedicated change detector classes.
bool isStaticallyNecessary = false;
_NgAstVisitor(this._result);
visitMethodCall(MethodCall ast) {
_result.methodNames
.add(new ReflectiveAccessor(ast.name, isStaticallyNecessary: true));
super.visitMethodCall(ast);
}
visitPropertyRead(PropertyRead ast) {
_result.getterNames.add(new ReflectiveAccessor(ast.name,
isStaticallyNecessary: isStaticallyNecessary));
super.visitPropertyRead(ast);
}
visitPropertyWrite(PropertyWrite ast) {
_result.setterNames.add(new ReflectiveAccessor(ast.name,
isStaticallyNecessary: isStaticallyNecessary));
super.visitPropertyWrite(ast);
}
visitSafeMethodCall(SafeMethodCall ast) {
_result.methodNames
.add(new ReflectiveAccessor(ast.name, isStaticallyNecessary: true));
super.visitSafeMethodCall(ast);
}
visitSafePropertyRead(SafePropertyRead ast) {
_result.getterNames.add(new ReflectiveAccessor(ast.name,
isStaticallyNecessary: isStaticallyNecessary));
super.visitSafePropertyRead(ast);
}
}

View File

@ -1,4 +1,4 @@
library angular2.transform.template_compiler.reflection_capabilities;
library angular2.transform.template_compiler.reflection.reflection_capabilities;
import 'package:angular2/src/reflection/reflection_capabilities.dart';
import 'package:angular2/src/reflection/types.dart';
@ -32,32 +32,3 @@ class NullReflectionCapabilities implements ReflectionCapabilities {
_nullGetter(Object p) => null;
_nullSetter(Object p, v) => null;
_nullMethod(Object p, List a) => null;
/// 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 extends NullReflectionCapabilities {
/// The names of all requested `getter`s.
final Set<String> getterNames = new Set<String>();
/// The names of all requested `setter`s.
final Set<String> setterNames = new Set<String>();
/// The names of all requested `method`s.
final Set<String> methodNames = new Set<String>();
GetterFn getter(String name) {
getterNames.add(name);
return super.getter(name);
}
SetterFn setter(String name) {
setterNames.add(name);
return super.setter(name);
}
MethodFn method(String name) {
methodNames.add(name);
return super.method(name);
}
}