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:
@ -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);
|
||||
}
|
||||
|
@ -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
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user