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

@ -490,14 +490,11 @@ export class _ParseAST {
new MethodCall(receiver, id, fn, args); new MethodCall(receiver, id, fn, args);
} else { } else {
let getter = this.reflector.getter(id);
let setter = this.reflector.setter(id);
if (isSafe) { if (isSafe) {
if (this.optionalOperator("=")) { if (this.optionalOperator("=")) {
this.error("The '?.' operator cannot be used in the assignment"); this.error("The '?.' operator cannot be used in the assignment");
} else { } else {
return new SafePropertyRead(receiver, id, getter); return new SafePropertyRead(receiver, id, this.reflector.getter(id));
} }
} else { } else {
if (this.optionalOperator("=")) { if (this.optionalOperator("=")) {
@ -506,9 +503,9 @@ export class _ParseAST {
} }
let value = this.parseConditional(); let value = this.parseConditional();
return new PropertyWrite(receiver, id, setter, value); return new PropertyWrite(receiver, id, this.reflector.setter(id), value);
} else { } else {
return new PropertyRead(receiver, id, getter); return new PropertyRead(receiver, id, this.reflector.getter(id));
} }
} }
} }

View File

@ -44,6 +44,7 @@ import {ChangeDetector, ChangeDetectorRef} from 'angular2/src/change_detection/c
import {QueryList} from './query_list'; import {QueryList} from './query_list';
import {reflector} from 'angular2/src/reflection/reflection'; import {reflector} from 'angular2/src/reflection/reflection';
import {RenderDirectiveMetadata} from 'angular2/src/render/api'; import {RenderDirectiveMetadata} from 'angular2/src/render/api';
import {EventConfig} from 'angular2/src/render/dom/util';
import {PipeBinding} from '../pipes/pipe_binding'; import {PipeBinding} from '../pipes/pipe_binding';
var _staticKeys; var _staticKeys;
@ -303,18 +304,8 @@ function _createEventEmitterAccessors(bwv: BindingWithVisibility): EventEmitterA
if (!(binding instanceof DirectiveBinding)) return []; if (!(binding instanceof DirectiveBinding)) return [];
var db = <DirectiveBinding>binding; var db = <DirectiveBinding>binding;
return ListWrapper.map(db.eventEmitters, eventConfig => { return ListWrapper.map(db.eventEmitters, eventConfig => {
let fieldName; var parsedEvent = EventConfig.parse(eventConfig);
let eventName; return new EventEmitterAccessor(parsedEvent.eventName, reflector.getter(parsedEvent.fieldName));
var colonIdx = eventConfig.indexOf(':');
if (colonIdx > -1) {
// long format: 'fieldName: eventName'
fieldName = StringWrapper.substring(eventConfig, 0, colonIdx).trim();
eventName = StringWrapper.substring(eventConfig, colonIdx + 1).trim();
} else {
// short format: 'name' when fieldName and eventName are the same
fieldName = eventName = eventConfig;
}
return new EventEmitterAccessor(eventName, reflector.getter(fieldName));
}); });
} }

View File

@ -10,7 +10,7 @@ import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control'; import {CompileControl} from './compile_control';
import {RenderDirectiveMetadata} from '../../api'; import {RenderDirectiveMetadata} from '../../api';
import {dashCaseToCamelCase, camelCaseToDashCase, EVENT_TARGET_SEPARATOR} from '../util'; import {EventConfig, dashCaseToCamelCase, camelCaseToDashCase} from '../util';
import {DirectiveBuilder, ElementBinderBuilder} from '../view/proto_view_builder'; import {DirectiveBuilder, ElementBinderBuilder} from '../view/proto_view_builder';
/** /**
@ -146,12 +146,9 @@ export class DirectiveParser implements CompileStep {
_bindDirectiveEvent(eventName, action, compileElement, directiveBinderBuilder) { _bindDirectiveEvent(eventName, action, compileElement, directiveBinderBuilder) {
var ast = this._parser.parseAction(action, compileElement.elementDescription); var ast = this._parser.parseAction(action, compileElement.elementDescription);
if (StringWrapper.contains(eventName, EVENT_TARGET_SEPARATOR)) { var parsedEvent = EventConfig.parse(eventName);
var parts = eventName.split(EVENT_TARGET_SEPARATOR); var targetName = parsedEvent.isLongForm ? parsedEvent.fieldName : null;
directiveBinderBuilder.bindEvent(parts[1], ast, parts[0]); directiveBinderBuilder.bindEvent(parsedEvent.eventName, ast, targetName);
} else {
directiveBinderBuilder.bindEvent(eventName, ast);
}
} }
_bindHostProperty(hostPropertyName, expression, compileElement, directiveBinderBuilder) { _bindHostProperty(hostPropertyName, expression, compileElement, directiveBinderBuilder) {

View File

@ -29,6 +29,27 @@ export function dashCaseToCamelCase(input: string): string {
(m) => { return m[1].toUpperCase(); }); (m) => { return m[1].toUpperCase(); });
} }
export class EventConfig {
constructor(public fieldName: string, public eventName: string, public isLongForm: boolean) {}
static parse(eventConfig: string): EventConfig {
var fieldName = eventConfig, eventName = eventConfig, isLongForm = false;
var separatorIdx = eventConfig.indexOf(EVENT_TARGET_SEPARATOR);
if (separatorIdx > -1) {
// long format: 'fieldName: eventName'
fieldName = StringWrapper.substring(eventConfig, 0, separatorIdx).trim();
eventName = StringWrapper.substring(eventConfig, separatorIdx + 1).trim();
isLongForm = true;
}
return new EventConfig(fieldName, eventName, isLongForm);
}
getFullName(): string {
return this.isLongForm ? `${this.fieldName}${EVENT_TARGET_SEPARATOR}${this.eventName}` :
this.eventName;
}
}
// Attention: This is on the hot path, so don't use closures or default values! // Attention: This is on the hot path, so don't use closures or default values!
export function queryBoundElements(templateContent: Node, isSingleElementChild: boolean): export function queryBoundElements(templateContent: Node, isSingleElementChild: boolean):
Element[] { Element[] {

View File

@ -262,7 +262,7 @@ export class DirectiveBuilder {
} }
} }
export class EventBuilder extends AstTransformer { class EventBuilder extends AstTransformer {
locals: List<AST> = []; locals: List<AST> = [];
localEvents: List<Event> = []; localEvents: List<Event> = [];
globalEvents: List<Event> = []; globalEvents: List<Event> = [];

View File

@ -31,7 +31,7 @@ Future<String> createNgSettersAndGetters(
return '$out'; return '$out';
} }
// TODO(kegluneq): De-dupe from template_compiler/generator.dart. // TODO(kegluneq): De-dupe from template_compiler/generator.dart, #3589.
/// Consumes the map generated by {@link _createPropertiesMap} to codegen /// Consumes the map generated by {@link _createPropertiesMap} to codegen
/// setters. /// setters.

View File

@ -3,7 +3,10 @@ library angular2.transform.common.property_utils;
import 'package:analyzer/src/generated/scanner.dart' show Keyword; import 'package:analyzer/src/generated/scanner.dart' show Keyword;
/// Whether `name` is a valid property name. /// Whether `name` is a valid property name.
bool isValid(String name) => !Keyword.keywords.containsKey(name); bool isValid(String name) {
var keyword = Keyword.keywords[name];
return keyword == null || keyword.isPseudoKeyword;
}
/// Prepares `name` to be emitted inside a string. /// Prepares `name` to be emitted inside a string.
String sanitize(String name) => name.replaceAll('\$', '\\\$'); String sanitize(String name) => name.replaceAll('\$', '\\\$');

View File

@ -23,8 +23,9 @@ import 'package:barback/barback.dart';
import 'change_detector_codegen.dart' as change; import 'change_detector_codegen.dart' as change;
import 'compile_step_factory.dart'; import 'compile_step_factory.dart';
import 'reflection_capabilities.dart'; import 'reflection/codegen.dart' as reg;
import 'reflector_register_codegen.dart' as reg; import 'reflection/processor.dart' as reg;
import 'reflection/reflection_capabilities.dart';
import 'view_definition_creator.dart'; import 'view_definition_creator.dart';
/// Reads the `.ng_deps.dart` file represented by `entryPoint` and parses any /// 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(), var extractor = new _TemplateExtractor(new DomElementSchemaRegistry(),
new TemplateCloner(-1), new XhrImpl(reader, entryPoint)); new TemplateCloner(-1), new XhrImpl(reader, entryPoint));
var registrations = new reg.Codegen(); final processor = new reg.Processor();
var changeDetectorClasses = new change.Codegen(); var changeDetectorClasses = new change.Codegen();
for (var rType in viewDefResults.viewDefinitions.keys) { for (var rType in viewDefResults.viewDefinitions.keys) {
var viewDefEntry = viewDefResults.viewDefinitions[rType]; var viewDefEntry = viewDefResults.viewDefinitions[rType];
var result = await extractor.extractTemplates(viewDefEntry.viewDef); var protoView = await extractor.extractTemplates(viewDefEntry.viewDef);
if (result == null) continue; if (protoView == null) continue;
if (generateRegistrations) { if (generateRegistrations) {
// TODO(kegluneq): Generate these getters & setters based on the processor.process(viewDefEntry, protoView);
// `ProtoViewDto` rather than querying the `ReflectionCapabilities`.
registrations.generate(result.recording);
} }
if (result.protoView != null && generateChangeDetectors) { if (generateChangeDetectors) {
var saved = reflector.reflectionCapabilities; var saved = reflector.reflectionCapabilities;
reflector.reflectionCapabilities = const NullReflectionCapabilities(); reflector.reflectionCapabilities = const NullReflectionCapabilities();
var defs = getChangeDetectorDefinitions(viewDefEntry.hostMetadata, var defs = getChangeDetectorDefinitions(viewDefEntry.hostMetadata,
result.protoView, viewDefEntry.viewDef.directives); protoView, viewDefEntry.viewDef.directives);
for (var i = 0; i < defs.length; ++i) { for (var i = 0; i < defs.length; ++i) {
changeDetectorClasses.generate('${rType.typeName}', changeDetectorClasses.generate('${rType.typeName}',
'_${rType.typeName}_ChangeDetector$i', defs[i]); '_${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; var code = viewDefResults.ngDeps.code;
if (registrations.isEmpty && changeDetectorClasses.isEmpty) return code; if (registrations.isEmpty && changeDetectorClasses.isEmpty) return code;
var importInjectIdx = var importInjectIdx =
@ -93,19 +97,16 @@ class _TemplateExtractor {
ElementSchemaRegistry _schemaRegistry; ElementSchemaRegistry _schemaRegistry;
TemplateCloner _templateCloner; TemplateCloner _templateCloner;
_TemplateExtractor(ElementSchemaRegistry schemaRegistry, _TemplateExtractor(this._schemaRegistry, this._templateCloner, XHR xhr)
TemplateCloner templateCloner, XHR xhr)
: _factory = new CompileStepFactory(new ng.Parser(new ng.Lexer())) { : _factory = new CompileStepFactory(new ng.Parser(new ng.Lexer())) {
var urlResolver = new UrlResolver(); var urlResolver = new UrlResolver();
var styleUrlResolver = new StyleUrlResolver(urlResolver); var styleUrlResolver = new StyleUrlResolver(urlResolver);
var styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver); var styleInliner = new StyleInliner(xhr, styleUrlResolver, urlResolver);
_loader = new ViewLoader(xhr, styleInliner, styleUrlResolver); _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". // Check for "imperative views".
if (viewDef.template == null && viewDef.templateAbsUrl == null) return null; 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 // operations between saving and restoring it, otherwise we can get into
// a bad state. See issue #2359 for additional context. // a bad state. See issue #2359 for additional context.
var savedReflectionCapabilities = reflector.reflectionCapabilities; var savedReflectionCapabilities = reflector.reflectionCapabilities;
var recordingCapabilities = new RecordingReflectionCapabilities(); reflector.reflectionCapabilities = const NullReflectionCapabilities();
reflector.reflectionCapabilities = recordingCapabilities;
var pipeline = new CompilePipeline(_factory.createSteps(viewDef)); var pipeline = new CompilePipeline(_factory.createSteps(viewDef));
@ -130,13 +130,6 @@ class _TemplateExtractor {
reflector.reflectionCapabilities = savedReflectionCapabilities; 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/names.dart';
import 'package:angular2/src/transform/common/property_utils.dart' as prop; import 'package:angular2/src/transform/common/property_utils.dart' as prop;
import 'reflection_capabilities.dart';
import 'model.dart';
class Codegen { class Codegen {
final StringBuffer _buf = new StringBuffer(); final StringBuffer _buf = new StringBuffer();
void generate(RecordingReflectionCapabilities recording) { /// Whether we are pre-generating change detectors.
if (recording != null) { /// If we have pre-generated change detectors, we need
var calls = _generateGetters(recording.getterNames); final bool generateChangeDetectors;
Codegen({this.generateChangeDetectors});
void generate(CodegenModel model) {
if (model != null) {
var calls = _generateGetters(_extractNames(model.getterNames));
if (calls.isNotEmpty) { if (calls.isNotEmpty) {
_buf.write('..${REGISTER_GETTERS_METHOD_NAME}' _buf.write('..${REGISTER_GETTERS_METHOD_NAME}'
'({${calls.join(', ')}})'); '({${calls.join(', ')}})');
} }
calls = _generateSetters(recording.setterNames); calls = _generateSetters(_extractNames(model.setterNames));
if (calls.isNotEmpty) { if (calls.isNotEmpty) {
_buf.write('..${REGISTER_SETTERS_METHOD_NAME}' _buf.write('..${REGISTER_SETTERS_METHOD_NAME}'
'({${calls.join(', ')}})'); '({${calls.join(', ')}})');
} }
calls = _generateMethods(recording.methodNames); calls = _generateMethods(_extractNames(model.methodNames));
if (calls.isNotEmpty) { if (calls.isNotEmpty) {
_buf.write('..${REGISTER_METHODS_METHOD_NAME}' _buf.write('..${REGISTER_METHODS_METHOD_NAME}'
'({${calls.join(', ')}})'); '({${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; bool get isEmpty => _buf.isEmpty;
@override @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/reflection_capabilities.dart';
import 'package:angular2/src/reflection/types.dart'; import 'package:angular2/src/reflection/types.dart';
@ -32,32 +32,3 @@ class NullReflectionCapabilities implements ReflectionCapabilities {
_nullGetter(Object p) => null; _nullGetter(Object p) => null;
_nullSetter(Object p, v) => null; _nullSetter(Object p, v) => null;
_nullMethod(Object p, List a) => 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);
}
}

View File

@ -0,0 +1,31 @@
import {EventConfig} from 'angular2/src/render/dom/util';
import {ddescribe, describe, expect, it} from 'angular2/test_lib';
export function main() {
describe('EventConfig', () => {
describe('parse', () => {
it('should handle short form events', () => {
var eventConfig = EventConfig.parse('shortForm');
expect(eventConfig.fieldName).toEqual('shortForm');
expect(eventConfig.eventName).toEqual('shortForm');
expect(eventConfig.isLongForm).toEqual(false);
});
it('should handle long form events', () => {
var eventConfig = EventConfig.parse('fieldName: eventName');
expect(eventConfig.fieldName).toEqual('fieldName');
expect(eventConfig.eventName).toEqual('eventName');
expect(eventConfig.isLongForm).toEqual(true);
});
});
describe('getFullName', () => {
it('should handle short form events', () => {
var eventConfig = new EventConfig('shortForm', 'shortForm', false);
expect(eventConfig.getFullName()).toEqual('shortForm');
});
it('should handle long form events', () => {
var eventConfig = new EventConfig('fieldName', 'eventName', true);
expect(eventConfig.getFullName()).toEqual('fieldName:eventName');
});
});
});
}

View File

@ -19,8 +19,7 @@ void initReflector() {
const Component(selector: '[soup]'), const Component(selector: '[soup]'),
const View(template: 'Salad: {{myNum}} is awesome') const View(template: 'Salad: {{myNum}} is awesome')
], const [], () => new MyComponent())) ], const [], () => new MyComponent()))
..registerGetters({'myNum': (o) => o.myNum}) ..registerGetters({'myNum': (o) => o.myNum});
..registerSetters({'myNum': (o, v) => o.myNum = v});
_gen.preGeneratedProtoDetectors['MyComponent_comp_0'] = _gen.preGeneratedProtoDetectors['MyComponent_comp_0'] =
_MyComponent_ChangeDetector0.newProtoChangeDetector; _MyComponent_ChangeDetector0.newProtoChangeDetector;
} }

View File

@ -7,6 +7,7 @@ import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/logging.dart'; import 'package:angular2/src/transform/common/logging.dart';
import 'package:angular2/src/transform/template_compiler/generator.dart'; import 'package:angular2/src/transform/template_compiler/generator.dart';
import 'package:dart_style/dart_style.dart'; import 'package:dart_style/dart_style.dart';
import 'package:path/path.dart' as path;
import 'package:guinness/guinness.dart'; import 'package:guinness/guinness.dart';
import '../common/read_file.dart'; import '../common/read_file.dart';
@ -31,7 +32,7 @@ void changeDetectorTests() {
// TODO(tbosch): This is just a temporary test that makes sure that the dart server and // TODO(tbosch): This is just a temporary test that makes sure that the dart server and
// dart browser is in sync. Change this to "not contains notifyBinding" // dart browser is in sync. Change this to "not contains notifyBinding"
// when https://github.com/angular/angular/issues/3019 is solved. // when https://github.com/angular/angular/issues/3019 is solved.
it('shouldn always notifyDispatcher for template variables', () async { it('should not always notifyDispatcher for template variables', () async {
var inputPath = 'template_compiler/ng_for_files/hello.ng_deps.dart'; var inputPath = 'template_compiler/ng_for_files/hello.ng_deps.dart';
var output = await (process(new AssetId('a', inputPath))); var output = await (process(new AssetId('a', inputPath)));
expect(output).toContain('notifyDispatcher'); expect(output).toContain('notifyDispatcher');
@ -90,7 +91,7 @@ void noChangeDetectorTests() {
_formatThenExpectEquals(output, expected); _formatThenExpectEquals(output, expected);
}); });
it('should not generated duplicate getters/setters', () async { it('should not generate duplicate getters/setters', () async {
var inputPath = 'template_compiler/duplicate_files/hello.ng_deps.dart'; var inputPath = 'template_compiler/duplicate_files/hello.ng_deps.dart';
var expected = readFile( var expected = readFile(
'template_compiler/duplicate_files/expected/hello.ng_deps.dart'); 'template_compiler/duplicate_files/expected/hello.ng_deps.dart');
@ -144,6 +145,15 @@ void noChangeDetectorTests() {
output = await process(new AssetId('a', inputPath)); output = await process(new AssetId('a', inputPath));
_formatThenExpectEquals(output, expected); _formatThenExpectEquals(output, expected);
}); });
it('should generate all expected getters, setters, & methods.', () async {
var base = 'template_compiler/registrations_files';
var inputPath = path.join(base, 'registrations.ng_deps.dart');
var expected =
readFile(path.join(base, 'expected/registrations.ng_deps.dart'));
var output = await process(new AssetId('a', inputPath));
_formatThenExpectEquals(output, expected);
});
} }
void _formatThenExpectEquals(String actual, String expected) { void _formatThenExpectEquals(String actual, String expected) {

View File

@ -17,6 +17,5 @@ void initReflector(reflector) {
], const [ ], const [
const [] const []
], () => new HelloCmp())) ], () => new HelloCmp()))
..registerGetters({'greeting': (o) => o.greeting}) ..registerGetters({'greeting': (o) => o.greeting});
..registerSetters({'greeting': (o, v) => o.greeting = v});
} }

View File

@ -18,6 +18,5 @@ void initReflector(reflector) {
const [] const []
], () => new HelloCmp())) ], () => new HelloCmp()))
..registerGetters({'b': (o) => o.b, 'greeting': (o) => o.greeting}) ..registerGetters({'b': (o) => o.b, 'greeting': (o) => o.greeting})
..registerSetters( ..registerSetters({'a': (o, v) => o.a = v});
{'b': (o, v) => o.b = v, 'greeting': (o, v) => o.greeting = v});
} }

View File

@ -13,7 +13,7 @@ void initReflector(reflector) {
HelloCmp, HelloCmp,
new ReflectionInfo(const [ new ReflectionInfo(const [
const Component(selector: 'hello-app'), const Component(selector: 'hello-app'),
const View(template: '<button (click)=\"action()\">go</button>') const View(template: '<button (click)="action()">go</button>')
], const [ ], const [
const [] const []
], () => new HelloCmp())) ], () => new HelloCmp()))

View File

@ -13,7 +13,7 @@ void initReflector(reflector) {
HelloCmp, HelloCmp,
new ReflectionInfo(const [ new ReflectionInfo(const [
const Component(selector: 'hello-app'), const Component(selector: 'hello-app'),
const View(template: '<button (click)=\"action()\">go</button>') const View(template: '<button (click)="action()">go</button>')
], const [ ], const [
const [] const []
], () => new HelloCmp())); ], () => new HelloCmp()));

View File

@ -0,0 +1,98 @@
{
"DependencyCmp": {
"kind": "type",
"value": {
"id": "DependencyCmp",
"selector": "dependency",
"compileChildren": true,
"hostProperties": {},
"hostListeners": {},
"hostActions": {},
"hostAttributes": {},
"properties": null,
"readAttributes": null,
"type": null,
"exportAs": null,
"callOnDestroy": null,
"callOnCheck": null,
"callOnInit": null,
"callOnChange": null,
"callOnAllChangesDone": null,
"events": ["dependencyEventName"],
"changeDetection": null,
"version": 1
}
},
"DirectiveProps": {
"kind": "type",
"value": {
"id": "DirectiveProps",
"selector": "[dir-props]",
"compileChildren": true,
"hostProperties": {"hprop": "hprop"},
"hostListeners": {},
"hostActions": {},
"hostAttributes": {},
"properties": ["prop"],
"readAttributes": null,
"type": null,
"exportAs": null,
"callOnDestroy": null,
"callOnCheck": null,
"callOnInit": null,
"callOnChange": null,
"callOnAllChangesDone": null,
"events": null,
"changeDetection": null,
"version": 1
}
},
"DirectiveEvents": {
"kind": "type",
"value": {
"id": "DirectiveEvents",
"selector": "[dir-events]",
"compileChildren": true,
"hostProperties": {},
"hostListeners": {"subevent": "doAThing()"},
"hostActions": {},
"hostAttributes": {},
"properties": [],
"readAttributes": null,
"type": null,
"exportAs": null,
"callOnDestroy": null,
"callOnCheck": null,
"callOnInit": null,
"callOnChange": null,
"callOnAllChangesDone": null,
"events": null,
"changeDetection": null,
"version": 1
}
},
"NgFor": {
"kind": "type",
"value": {
"id": "NgFor",
"selector": "[ng-for][ng-for-of]",
"compileChildren": true,
"hostProperties": {},
"hostListeners": {},
"hostActions": {},
"hostAttributes": {},
"properties": ["ngForOf"],
"readAttributes": null,
"type": null,
"exportAs": null,
"callOnDestroy": null,
"callOnCheck": true,
"callOnInit": null,
"callOnChange": null,
"callOnAllChangesDone": null,
"events": null,
"changeDetection": null,
"version": 1
}
}
}

View File

@ -0,0 +1,108 @@
library examples.hello_world.index_common_dart.ng_deps.dart;
import 'dependency.dart';
import 'dependency.ng_deps.dart' as i0;
import 'hello.dart';
import 'package:angular2/angular2.dart'
show Component, Directive, View, NgElement;
var _visited = false;
void initReflector(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(
TextBindingsCmp,
new ReflectionInfo(const [
const Component(selector: 'text'),
const View(template: '{{textBindings}}')
], const [
const []
], () => new TextBindingsCmp()))
..registerType(
PropertyBindingsCmp,
new ReflectionInfo(const [
const Component(selector: 'props'),
const View(template: '<div [prop-name]="propValue"></div>')
], const [
const []
], () => new PropertyBindingsCmp()))
..registerType(
EventsCmp,
new ReflectionInfo(const [
const Component(selector: 'events', events: const ['eventName']),
const View(template: 'Hi')
], const [
const []
], () => new EventsCmp()))
..registerType(
SubEventsCmp,
new ReflectionInfo(const [
const Component(selector: 'sub-events'),
const View(
template: '<dependency></dependency>',
directives: const [DependencyCmp])
], const [
const []
], () => new SubEventsCmp()))
..registerType(
TemplateEventsCmp,
new ReflectionInfo(const [
const Component(selector: 'template-events'),
const View(template: '<div (mouseover)="onMouseOver()"></div>')
], const [
const []
], () => new TemplateEventsCmp()))
..registerType(
DirectivePropsCmp,
new ReflectionInfo(const [
const Component(selector: 'directive-props-cmp'),
const View(
template: '<div dir-props prop="somevalue">',
directives: const [DirectiveProps])
], const [
const []
], () => new DirectivePropsCmp()))
..registerType(
DirectiveEventsCmp,
new ReflectionInfo(const [
const Component(selector: 'directive-events-cmp'),
const View(
template: '<div dir-events (subevent)="field = 10;">',
directives: const [DirectiveEvents])
], const [
const []
], () => new DirectiveEventsCmp()))
..registerType(
RecursiveCmp,
new ReflectionInfo(const [
const Component(selector: 'recursive-cmp'),
const View(
template:
'<li *ng-for="#thing of things" [recursive-prop]="thing"><div>test</div></li>',
directives: const [NgFor])
], const [
const []
], () => new RecursiveCmp()))
..registerGetters({
'eventName': (o) => o.eventName,
'hprop': (o) => o.hprop,
'propValue': (o) => o.propValue,
'textBindings': (o) => o.textBindings,
'thing': (o) => o.thing,
'things': (o) => o.things
})
..registerSetters({
'field': (o, v) => o.field = v,
'hprop': (o, v) => o.hprop = v,
'ngForOf': (o, v) => o.ngForOf = v,
'prop': (o, v) => o.prop = v,
'propName': (o, v) => o.propName = v,
'recursiveProp': (o, v) => o.recursiveProp = v
})
..registerMethods({
'doAThing': (o, List args) => Function.apply(o.doAThing, args),
'onMouseOver': (o, List args) => Function.apply(o.onMouseOver, args)
});
i0.initReflector();
}

View File

@ -0,0 +1,88 @@
library examples.hello_world.index_common_dart.ng_deps.dart;
import 'dependency.dart';
import 'dependency.ng_deps.dart' as i0;
import 'hello.dart';
import 'package:angular2/angular2.dart'
show Component, Directive, View, NgElement;
var _visited = false;
void initReflector(reflector) {
if (_visited) return;
_visited = true;
reflector
..registerType(
TextBindingsCmp,
new ReflectionInfo(const [
const Component(selector: 'text'),
const View(template: '{{textBindings}}')
], const [
const []
], () => new TextBindingsCmp()))
..registerType(
PropertyBindingsCmp,
new ReflectionInfo(const [
const Component(selector: 'props'),
const View(template: '<div [prop-name]="propValue"></div>')
], const [
const []
], () => new PropertyBindingsCmp()))
..registerType(
EventsCmp,
new ReflectionInfo(const [
const Component(selector: 'events', events: const ['eventName']),
const View(template: 'Hi')
], const [
const []
], () => new EventsCmp()))
..registerType(
SubEventsCmp,
new ReflectionInfo(const [
const Component(selector: 'sub-events'),
const View(
template: '<dependency></dependency>',
directives: const [DependencyCmp])
], const [
const []
], () => new SubEventsCmp()))
..registerType(
TemplateEventsCmp,
new ReflectionInfo(const [
const Component(selector: 'template-events'),
const View(template: '<div (mouseover)="onMouseOver()"></div>')
], const [
const []
], () => new TemplateEventsCmp()))
..registerType(
DirectivePropsCmp,
new ReflectionInfo(const [
const Component(selector: 'directive-props-cmp'),
const View(
template: '<div dir-props prop="somevalue">',
directives: const [DirectiveProps])
], const [
const []
], () => new DirectivePropsCmp()))
..registerType(
DirectiveEventsCmp,
new ReflectionInfo(const [
const Component(selector: 'directive-events-cmp'),
const View(
template: '<div dir-events (subevent)="field = 10;">',
directives: const [DirectiveEvents])
], const [
const []
], () => new DirectiveEventsCmp()))
..registerType(
RecursiveCmp,
new ReflectionInfo(const [
const Component(selector: 'recursive-cmp'),
const View(
template:
'<li *ng-for="#thing of things" [recursive-prop]="thing"><div>test</div></li>',
directives: const [NgFor])
], const [
const []
], () => new RecursiveCmp()));
i0.initReflector();
}

View File

@ -0,0 +1,194 @@
{
"TextBindingsCmp": {
"kind": "type",
"value": {
"id": "TextBindingsCmp",
"selector": "text",
"compileChildren": true,
"hostProperties": {},
"hostListeners": {},
"hostActions": {},
"hostAttributes": {},
"properties": null,
"readAttributes": null,
"type": null,
"exportAs": null,
"callOnDestroy": null,
"callOnCheck": null,
"callOnInit": null,
"callOnChange": null,
"callOnAllChangesDone": null,
"events": null,
"changeDetection": null,
"version": 1
}
},
"PropertyBindingsCmp": {
"kind": "type",
"value": {
"id": "PropertyBindingsCmp",
"selector": "props",
"compileChildren": true,
"hostProperties": {},
"hostListeners": {},
"hostActions": {},
"hostAttributes": {},
"properties": [],
"readAttributes": [],
"type": 0,
"exportAs": null,
"callOnDestroy": false,
"callOnCheck": false,
"callOnInit": false,
"callOnChange": false,
"callOnAllChangesDone": false,
"events": [],
"changeDetection": null,
"version": 1
}
},
"EventsCmp": {
"kind": "type",
"value": {
"id": "EventsCmp",
"selector": "events",
"compileChildren": true,
"hostProperties": {},
"hostListeners": {},
"hostActions": {},
"hostAttributes": {},
"properties": [],
"readAttributes": [],
"type": 1,
"exportAs": null,
"callOnDestroy": false,
"callOnCheck": false,
"callOnInit": false,
"callOnChange": false,
"callOnAllChangesDone": false,
"events": ["eventName"],
"changeDetection": null,
"version": 1
}
},
"SubEventsCmp": {
"kind": "type",
"value": {
"id": "SubEventsCmp",
"selector": "events",
"compileChildren": true,
"hostProperties": {},
"hostListeners": {},
"hostActions": {},
"hostAttributes": {},
"properties": [],
"readAttributes": [],
"type": 1,
"exportAs": null,
"callOnDestroy": false,
"callOnCheck": false,
"callOnInit": false,
"callOnChange": false,
"callOnAllChangesDone": false,
"events": [],
"changeDetection": null,
"version": 1
}
},
"TemplateEventsCmp": {
"kind": "type",
"value": {
"id": "TemplateEventsCmp",
"selector": "template-events",
"compileChildren": true,
"hostProperties": {},
"hostListeners": {},
"hostActions": {},
"hostAttributes": {},
"properties": [],
"readAttributes": [],
"type": 1,
"exportAs": null,
"callOnDestroy": false,
"callOnCheck": false,
"callOnInit": false,
"callOnChange": false,
"callOnAllChangesDone": false,
"events": [],
"changeDetection": null,
"version": 1
}
},
"DirectivePropsCmp": {
"kind": "type",
"value": {
"id": "DirectivePropsCmp",
"selector": "directive-props-cmp",
"compileChildren": true,
"hostProperties": {},
"hostListeners": {},
"hostActions": {},
"hostAttributes": {},
"properties": [],
"readAttributes": [],
"type": 1,
"exportAs": null,
"callOnDestroy": false,
"callOnCheck": false,
"callOnInit": false,
"callOnChange": false,
"callOnAllChangesDone": false,
"events": [],
"changeDetection": null,
"version": 1
}
},
"DirectiveEventsCmp": {
"kind": "type",
"value": {
"id": "DirectiveEventsCmp",
"selector": "directive-events-cmp",
"compileChildren": true,
"hostProperties": {},
"hostListeners": {},
"hostActions": {},
"hostAttributes": {},
"properties": [],
"readAttributes": [],
"type": 1,
"exportAs": null,
"callOnDestroy": false,
"callOnCheck": false,
"callOnInit": false,
"callOnChange": false,
"callOnAllChangesDone": false,
"events": [],
"changeDetection": null,
"version": 1
}
},
"RecursiveCmp": {
"kind": "type",
"value": {
"id": "RecursiveCmp",
"selector": "recursive-cmp",
"compileChildren": true,
"hostProperties": {},
"hostListeners": {},
"hostActions": {},
"hostAttributes": {},
"properties": [],
"readAttributes": [],
"type": 1,
"exportAs": null,
"callOnDestroy": false,
"callOnCheck": false,
"callOnInit": false,
"callOnChange": false,
"callOnAllChangesDone": false,
"events": [],
"changeDetection": null,
"version": 1
}
}
}

View File

@ -17,6 +17,5 @@ void initReflector(reflector) {
], const [ ], const [
const [] const []
], () => new HelloCmp())) ], () => new HelloCmp()))
..registerGetters({'greeting': (o) => o.greeting}) ..registerGetters({'greeting': (o) => o.greeting});
..registerSetters({'greeting': (o, v) => o.greeting = v});
} }

View File

@ -17,6 +17,5 @@ void initReflector(reflector) {
], const [ ], const [
const [] const []
], () => new GoodbyeCmp())) ], () => new GoodbyeCmp()))
..registerGetters({'name': (o) => o.name}) ..registerGetters({'name': (o) => o.name});
..registerSetters({'name': (o, v) => o.name = v});
} }

View File

@ -17,6 +17,5 @@ void initReflector(reflector) {
], const [ ], const [
const [] const []
], () => new MyApp())) ], () => new MyApp()))
..registerGetters({'name': (o) => o.name}) ..registerGetters({'name': (o) => o.name});
..registerSetters({'name': (o, v) => o.name = v});
} }