feat(compiler): add change detector generation

Runtime and Codegen.

Part of #3605
Closes #4057
This commit is contained in:
Tobias Bosch 2015-09-08 09:57:51 -07:00
parent 2daf2eedb6
commit 12dd44f7f6
15 changed files with 373 additions and 120 deletions

View File

@ -0,0 +1,81 @@
import {TypeMetadata, SourceModule} from './api';
import {
ChangeDetectorJITGenerator
} from 'angular2/src/core/change_detection/change_detection_jit_generator';
import {createChangeDetectorDefinitions} from './change_definition_factory';
import {isJsObject, CONST_EXPR} from 'angular2/src/core/facade/lang';
import {
ChangeDetectorGenConfig,
ChangeDetectorDefinition,
DynamicProtoChangeDetector,
ChangeDetectionStrategy
} from 'angular2/src/core/change_detection/change_detection';
import {TemplateAst} from './template_ast';
import {Codegen} from 'angular2/src/transform/template_compiler/change_detector_codegen';
var IS_DART = !isJsObject({});
const ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
const UTIL = "ChangeDetectionUtil";
const JS_CHANGE_DETECTOR_IMPORTS = CONST_EXPR([
['angular2/src/core/change_detection/abstract_change_detector', 'acd'],
['angular2/src/core/change_detection/change_detection_util', 'cdu']
]);
const DART_CHANGE_DETECTOR_IMPORTS =
CONST_EXPR([['angular2/src/core/change_detection/pregen_proto_change_detector', '_gen']]);
export class ChangeDetectionCompiler {
constructor(private _genConfig: ChangeDetectorGenConfig) {}
compileComponentRuntime(componentType: TypeMetadata, strategy: ChangeDetectionStrategy,
parsedTemplate: TemplateAst[]): Function[] {
var changeDetectorDefinitions =
createChangeDetectorDefinitions(componentType, strategy, this._genConfig, parsedTemplate);
return changeDetectorDefinitions.map(definition =>
this._createChangeDetectorFactory(definition));
}
private _createChangeDetectorFactory(definition: ChangeDetectorDefinition): Function {
if (IS_DART) {
var proto = new DynamicProtoChangeDetector(definition);
return (dispatcher) => proto.instantiate(dispatcher);
} else {
// TODO(tbosch): provide a flag in _genConfig whether to allow eval or fall back to dynamic
// change detection as well!
return new ChangeDetectorJITGenerator(definition, UTIL, ABSTRACT_CHANGE_DETECTOR).generate();
}
}
compileComponentCodeGen(componentType: TypeMetadata, strategy: ChangeDetectionStrategy,
parsedTemplate: TemplateAst[]): SourceModule {
var changeDetectorDefinitions =
createChangeDetectorDefinitions(componentType, strategy, this._genConfig, parsedTemplate);
var imports = IS_DART ? DART_CHANGE_DETECTOR_IMPORTS : JS_CHANGE_DETECTOR_IMPORTS;
var factories = [];
var sourceParts = changeDetectorDefinitions.map(definition => {
var codegen: any;
// TODO(tbosch): move the 2 code generators to the same place, one with .dart and one with .ts
// suffix
// and have the same API for calling them!
if (IS_DART) {
codegen = new Codegen();
var className = definition.id;
codegen.generate(componentType.typeName, className, definition);
factories.push(`(dispatcher) => new ${className}(dispatcher)`);
return codegen.toString();
} else {
codegen = new ChangeDetectorJITGenerator(definition, `cdu.${UTIL}`,
`acd.${ABSTRACT_CHANGE_DETECTOR}`);
factories.push(`function(dispatcher) { return new ${codegen.typeName}(dispatcher); }`);
return codegen.generateSource();
}
});
sourceParts.push(`var CHANGE_DETECTORS = [ ${factories.join(',')} ];`);
return new SourceModule(componentType.typeUrl, sourceParts.join('\n'), imports);
}
}

View File

@ -76,7 +76,7 @@ export class StyleCompiler {
private _styleCodeGen(moduleName: string, plainStyles: string[], absUrls: string[], shim: boolean, private _styleCodeGen(moduleName: string, plainStyles: string[], absUrls: string[], shim: boolean,
suffix: string): SourceModule { suffix: string): SourceModule {
var imports: string[][] = []; var imports: string[][] = [];
var moduleSource = `${codeGenExportVar('STYLES')} (`; var moduleSource = `var STYLES = (`;
moduleSource += moduleSource +=
`[${plainStyles.map( plainStyle => escapeString(this._shimIfNeeded(plainStyle, shim)) ).join(',')}]`; `[${plainStyles.map( plainStyle => escapeString(this._shimIfNeeded(plainStyle, shim)) ).join(',')}]`;
for (var i = 0; i < absUrls.length; i++) { for (var i = 0; i < absUrls.length; i++) {
@ -109,14 +109,6 @@ function escapeString(input: string): string {
return `'${escapedInput}'`; return `'${escapedInput}'`;
} }
function codeGenExportVar(name: string): string {
if (IS_DART) {
return `var ${name} =`;
} else {
return `var ${name} = exports.${name} =`;
}
}
function codeGenConcatArray(expression: string): string { function codeGenConcatArray(expression: string): string {
return `${IS_DART ? '..addAll' : '.concat'}(${expression})`; return `${IS_DART ? '..addAll' : '.concat'}(${expression})`;
} }

View File

@ -103,7 +103,7 @@ export class PreGeneratedChangeDetection extends ChangeDetection {
this._genConfig = this._genConfig =
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(), isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
assertionsEnabled(), false); assertionsEnabled(), false, false);
} }
static isSupported(): boolean { return PregenProtoChangeDetector.isSupported(); } static isSupported(): boolean { return PregenProtoChangeDetector.isSupported(); }
@ -133,7 +133,7 @@ export class DynamicChangeDetection extends ChangeDetection {
super(); super();
this._genConfig = this._genConfig =
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(), isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
assertionsEnabled(), false); assertionsEnabled(), false, false);
} }
getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector { getProtoChangeDetector(id: string, definition: ChangeDetectorDefinition): ProtoChangeDetector {
@ -157,7 +157,7 @@ export class JitChangeDetection extends ChangeDetection {
super(); super();
this._genConfig = this._genConfig =
isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(), isPresent(config) ? config : new ChangeDetectorGenConfig(assertionsEnabled(),
assertionsEnabled(), false); assertionsEnabled(), false, true);
} }
static isSupported(): boolean { return JitProtoChangeDetector.isSupported(); } static isSupported(): boolean { return JitProtoChangeDetector.isSupported(); }

View File

@ -7,11 +7,16 @@ library change_detection.change_detection_jit_generator;
/// `PregenProtoChangeDetector`, and /// `PregenProtoChangeDetector`, and
/// `src/transform/template_compiler/change_detector_codegen.dart` for details. /// `src/transform/template_compiler/change_detector_codegen.dart` for details.
class ChangeDetectorJITGenerator { class ChangeDetectorJITGenerator {
ChangeDetectorJITGenerator(typeName, strategy, records, directiveMementos) {} String typeName;
ChangeDetectorJITGenerator(definition, changeDetectionUtilVarName, abstractChangeDetectorVarName) {}
generate() { generate() {
throw "Jit Change Detection is not supported in Dart"; throw "Jit Change Detection is not supported in Dart";
} }
generateSource() {
throw "Jit Change Detection is not supported in Dart";
}
static bool isSupported() => false; static bool isSupported() => false;
} }

View File

@ -1,4 +1,10 @@
import {BaseException, Type, isBlank, isPresent} from 'angular2/src/core/facade/lang'; import {
BaseException,
Type,
isBlank,
isPresent,
StringWrapper
} from 'angular2/src/core/facade/lang';
import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/core/facade/collection'; import {ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/core/facade/collection';
import {AbstractChangeDetector} from './abstract_change_detector'; import {AbstractChangeDetector} from './abstract_change_detector';
@ -11,10 +17,9 @@ import {CodegenLogicUtil} from './codegen_logic_util';
import {codify} from './codegen_facade'; import {codify} from './codegen_facade';
import {EventBinding} from './event_binding'; import {EventBinding} from './event_binding';
import {BindingTarget} from './binding_record'; import {BindingTarget} from './binding_record';
import {ChangeDetectorGenConfig} from './interfaces'; import {ChangeDetectorGenConfig, ChangeDetectorDefinition} from './interfaces';
import {ChangeDetectionStrategy} from './constants'; import {ChangeDetectionStrategy} from './constants';
import {createPropertyRecords, createEventRecords} from './proto_change_detector';
/** /**
* The code generator takes a list of proto records and creates a function/class * The code generator takes a list of proto records and creates a function/class
@ -25,39 +30,65 @@ import {ChangeDetectionStrategy} from './constants';
* `angular2.transform.template_compiler.change_detector_codegen` library. If you make updates * `angular2.transform.template_compiler.change_detector_codegen` library. If you make updates
* here, please make equivalent changes there. * here, please make equivalent changes there.
*/ */
const ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
const UTIL = "ChangeDetectionUtil";
const IS_CHANGED_LOCAL = "isChanged"; const IS_CHANGED_LOCAL = "isChanged";
const CHANGES_LOCAL = "changes"; const CHANGES_LOCAL = "changes";
export class ChangeDetectorJITGenerator { export class ChangeDetectorJITGenerator {
_logic: CodegenLogicUtil; private _logic: CodegenLogicUtil;
_names: CodegenNameUtil; private _names: CodegenNameUtil;
_typeName: string; private id: string;
private changeDetectionStrategy: ChangeDetectionStrategy;
private records: ProtoRecord[];
private propertyBindingTargets: BindingTarget[];
private eventBindings: EventBinding[];
private directiveRecords: any[];
private genConfig: ChangeDetectorGenConfig;
typeName: string;
constructor(private id: string, private changeDetectionStrategy: ChangeDetectionStrategy, constructor(definition: ChangeDetectorDefinition, private changeDetectionUtilVarName: string,
private records: ProtoRecord[], private propertyBindingTargets: BindingTarget[], private abstractChangeDetectorVarName: string) {
private eventBindings: EventBinding[], private directiveRecords: any[], var propertyBindingRecords = createPropertyRecords(definition);
private genConfig: ChangeDetectorGenConfig) { var eventBindingRecords = createEventRecords(definition);
this._names = var propertyBindingTargets = definition.bindingRecords.map(b => b.target);
new CodegenNameUtil(this.records, this.eventBindings, this.directiveRecords, UTIL); this.id = definition.id;
this._logic = new CodegenLogicUtil(this._names, UTIL, changeDetectionStrategy); this.changeDetectionStrategy = definition.strategy;
this._typeName = sanitizeName(`ChangeDetector_${this.id}`); this.genConfig = definition.genConfig;
this.records = propertyBindingRecords;
this.propertyBindingTargets = propertyBindingTargets;
this.eventBindings = eventBindingRecords;
this.directiveRecords = definition.directiveRecords;
this._names = new CodegenNameUtil(this.records, this.eventBindings, this.directiveRecords,
this.changeDetectionUtilVarName);
this._logic = new CodegenLogicUtil(this._names, this.changeDetectionUtilVarName,
this.changeDetectionStrategy);
this.typeName = sanitizeName(`ChangeDetector_${this.id}`);
} }
generate(): Function { generate(): Function {
var classDefinition = ` var factorySource = `
var ${this._typeName} = function ${this._typeName}(dispatcher) { ${this.generateSource()}
${ABSTRACT_CHANGE_DETECTOR}.call( return function(dispatcher) {
return new ${this.typeName}(dispatcher);
}
`;
return new Function(this.abstractChangeDetectorVarName, this.changeDetectionUtilVarName,
factorySource)(AbstractChangeDetector, ChangeDetectionUtil);
}
generateSource(): string {
return `
var ${this.typeName} = function ${this.typeName}(dispatcher) {
${this.abstractChangeDetectorVarName}.call(
this, ${JSON.stringify(this.id)}, dispatcher, ${this.records.length}, this, ${JSON.stringify(this.id)}, dispatcher, ${this.records.length},
${this._typeName}.gen_propertyBindingTargets, ${this._typeName}.gen_directiveIndices, ${this.typeName}.gen_propertyBindingTargets, ${this.typeName}.gen_directiveIndices,
${codify(this.changeDetectionStrategy)}); ${codify(this.changeDetectionStrategy)});
this.dehydrateDirectives(false); this.dehydrateDirectives(false);
} }
${this._typeName}.prototype = Object.create(${ABSTRACT_CHANGE_DETECTOR}.prototype); ${this.typeName}.prototype = Object.create(${this.abstractChangeDetectorVarName}.prototype);
${this._typeName}.prototype.detectChangesInRecordsInternal = function(throwOnChange) { ${this.typeName}.prototype.detectChangesInRecordsInternal = function(throwOnChange) {
${this._names.genInitLocals()} ${this._names.genInitLocals()}
var ${IS_CHANGED_LOCAL} = false; var ${IS_CHANGED_LOCAL} = false;
var ${CHANGES_LOCAL} = null; var ${CHANGES_LOCAL} = null;
@ -80,31 +111,25 @@ export class ChangeDetectorJITGenerator {
${this._genPropertyBindingTargets()} ${this._genPropertyBindingTargets()}
${this._genDirectiveIndices()} ${this._genDirectiveIndices()}
return function(dispatcher) {
return new ${this._typeName}(dispatcher);
}
`; `;
return new Function(ABSTRACT_CHANGE_DETECTOR, UTIL, classDefinition)(AbstractChangeDetector,
ChangeDetectionUtil);
} }
_genPropertyBindingTargets(): string { _genPropertyBindingTargets(): string {
var targets = this._logic.genPropertyBindingTargets(this.propertyBindingTargets, var targets = this._logic.genPropertyBindingTargets(this.propertyBindingTargets,
this.genConfig.genDebugInfo); this.genConfig.genDebugInfo);
return `${this._typeName}.gen_propertyBindingTargets = ${targets};`; return `${this.typeName}.gen_propertyBindingTargets = ${targets};`;
} }
_genDirectiveIndices(): string { _genDirectiveIndices(): string {
var indices = this._logic.genDirectiveIndices(this.directiveRecords); var indices = this._logic.genDirectiveIndices(this.directiveRecords);
return `${this._typeName}.gen_directiveIndices = ${indices};`; return `${this.typeName}.gen_directiveIndices = ${indices};`;
} }
_maybeGenHandleEventInternal(): string { _maybeGenHandleEventInternal(): string {
if (this.eventBindings.length > 0) { if (this.eventBindings.length > 0) {
var handlers = this.eventBindings.map(eb => this._genEventBinding(eb)).join("\n"); var handlers = this.eventBindings.map(eb => this._genEventBinding(eb)).join("\n");
return ` return `
${this._typeName}.prototype.handleEventInternal = function(eventName, elIndex, locals) { ${this.typeName}.prototype.handleEventInternal = function(eventName, elIndex, locals) {
var ${this._names.getPreventDefaultAccesor()} = false; var ${this._names.getPreventDefaultAccesor()} = false;
${this._names.genInitEventLocals()} ${this._names.genInitEventLocals()}
${handlers} ${handlers}
@ -156,7 +181,7 @@ export class ChangeDetectorJITGenerator {
} }
var dehydrateFieldsCode = this._names.genDehydrateFields(); var dehydrateFieldsCode = this._names.genDehydrateFields();
if (!destroyPipesCode && !dehydrateFieldsCode) return ''; if (!destroyPipesCode && !dehydrateFieldsCode) return '';
return `${this._typeName}.prototype.dehydrateDirectives = function(destroyPipes) { return `${this.typeName}.prototype.dehydrateDirectives = function(destroyPipes) {
${destroyPipesCode} ${destroyPipesCode}
${dehydrateFieldsCode} ${dehydrateFieldsCode}
}`; }`;
@ -166,7 +191,7 @@ export class ChangeDetectorJITGenerator {
var hydrateDirectivesCode = this._logic.genHydrateDirectives(this.directiveRecords); var hydrateDirectivesCode = this._logic.genHydrateDirectives(this.directiveRecords);
var hydrateDetectorsCode = this._logic.genHydrateDetectors(this.directiveRecords); var hydrateDetectorsCode = this._logic.genHydrateDetectors(this.directiveRecords);
if (!hydrateDirectivesCode && !hydrateDetectorsCode) return ''; if (!hydrateDirectivesCode && !hydrateDetectorsCode) return '';
return `${this._typeName}.prototype.hydrateDirectives = function(directives) { return `${this.typeName}.prototype.hydrateDirectives = function(directives) {
${hydrateDirectivesCode} ${hydrateDirectivesCode}
${hydrateDetectorsCode} ${hydrateDetectorsCode}
}`; }`;
@ -177,7 +202,7 @@ export class ChangeDetectorJITGenerator {
if (notifications.length > 0) { if (notifications.length > 0) {
var directiveNotifications = notifications.join("\n"); var directiveNotifications = notifications.join("\n");
return ` return `
${this._typeName}.prototype.afterContentLifecycleCallbacksInternal = function() { ${this.typeName}.prototype.afterContentLifecycleCallbacksInternal = function() {
${directiveNotifications} ${directiveNotifications}
} }
`; `;
@ -191,7 +216,7 @@ export class ChangeDetectorJITGenerator {
if (notifications.length > 0) { if (notifications.length > 0) {
var directiveNotifications = notifications.join("\n"); var directiveNotifications = notifications.join("\n");
return ` return `
${this._typeName}.prototype.afterViewLifecycleCallbacksInternal = function() { ${this.typeName}.prototype.afterViewLifecycleCallbacksInternal = function() {
${directiveNotifications} ${directiveNotifications}
} }
`; `;
@ -239,7 +264,7 @@ export class ChangeDetectorJITGenerator {
var pipeName = r.name; var pipeName = r.name;
var init = ` var init = `
if (${pipe} === ${UTIL}.uninitialized) { if (${pipe} === ${this.changeDetectionUtilVarName}.uninitialized) {
${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeName}'); ${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeName}');
} }
`; `;
@ -251,7 +276,7 @@ export class ChangeDetectorJITGenerator {
var check = ` var check = `
if (${oldValue} !== ${newValue}) { if (${oldValue} !== ${newValue}) {
${newValue} = ${UTIL}.unwrapValue(${newValue}) ${newValue} = ${this.changeDetectionUtilVarName}.unwrapValue(${newValue})
${this._genChangeMarker(r)} ${this._genChangeMarker(r)}
${this._genUpdateDirectiveOrElement(r)} ${this._genUpdateDirectiveOrElement(r)}
${this._genAddToChanges(r)} ${this._genAddToChanges(r)}
@ -342,7 +367,7 @@ export class ChangeDetectorJITGenerator {
_genCheckNoChanges(): string { _genCheckNoChanges(): string {
if (this.genConfig.genCheckNoChanges) { if (this.genConfig.genCheckNoChanges) {
return `${this._typeName}.prototype.checkNoChanges = function() { this.runDetectChanges(true); }`; return `${this.typeName}.prototype.checkNoChanges = function() { this.runDetectChanges(true); }`;
} else { } else {
return ''; return '';
} }

View File

@ -77,7 +77,7 @@ export interface ProtoChangeDetector { instantiate(dispatcher: ChangeDispatcher)
export class ChangeDetectorGenConfig { export class ChangeDetectorGenConfig {
constructor(public genCheckNoChanges: boolean, public genDebugInfo: boolean, constructor(public genCheckNoChanges: boolean, public genDebugInfo: boolean,
public logBindingUpdate: boolean) {} public logBindingUpdate: boolean, public useJit: boolean) {}
} }
export class ChangeDetectorDefinition { export class ChangeDetectorDefinition {

View File

@ -4,9 +4,6 @@ import {isPresent} from 'angular2/src/core/facade/lang';
import {ProtoChangeDetector, ChangeDetector, ChangeDetectorDefinition} from './interfaces'; import {ProtoChangeDetector, ChangeDetector, ChangeDetectorDefinition} from './interfaces';
import {ChangeDetectorJITGenerator} from './change_detection_jit_generator'; import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';
import {coalesce} from './coalesce';
import {createPropertyRecords, createEventRecords} from './proto_change_detector';
export class JitProtoChangeDetector implements ProtoChangeDetector { export class JitProtoChangeDetector implements ProtoChangeDetector {
_factory: Function; _factory: Function;
@ -19,13 +16,6 @@ export class JitProtoChangeDetector implements ProtoChangeDetector {
instantiate(dispatcher: any): ChangeDetector { return this._factory(dispatcher); } instantiate(dispatcher: any): ChangeDetector { return this._factory(dispatcher); }
_createFactory(definition: ChangeDetectorDefinition) { _createFactory(definition: ChangeDetectorDefinition) {
var propertyBindingRecords = createPropertyRecords(definition); return new ChangeDetectorJITGenerator(definition, 'util', 'AbstractChangeDetector').generate();
var eventBindingRecords = createEventRecords(definition);
var propertyBindingTargets = this.definition.bindingRecords.map(b => b.target);
return new ChangeDetectorJITGenerator(
definition.id, definition.strategy, propertyBindingRecords, propertyBindingTargets,
eventBindingRecords, this.definition.directiveRecords, this.definition.genConfig)
.generate();
} }
} }

View File

@ -0,0 +1,14 @@
import {
ChangeDetectorDefinition,
} from 'angular2/src/core/change_detection/change_detection';
// Note: This class is only here so that we can reference it from TypeScript code.
// The actual implementation lives under modules_dart.
// TODO(tbosch): Move the corresponding code into angular2/src/compiler once
// the new compiler is done.
export class Codegen {
generate(typeName: string, changeDetectorTypeName: string, def: ChangeDetectorDefinition): void {
throw "Not implemented in JS";
}
toString(): string { throw "Not implemented in JS"; }
}

View File

@ -12,7 +12,6 @@ import {
TestComponentBuilder TestComponentBuilder
} from 'angular2/test_lib'; } from 'angular2/test_lib';
import {MapWrapper} from 'angular2/src/core/facade/collection'; import {MapWrapper} from 'angular2/src/core/facade/collection';
import {isBlank} from 'angular2/src/core/facade/lang';
import {HtmlParser} from 'angular2/src/compiler/html_parser'; import {HtmlParser} from 'angular2/src/compiler/html_parser';
import {DirectiveMetadata, TypeMetadata, ChangeDetectionMetadata} from 'angular2/src/compiler/api'; import {DirectiveMetadata, TypeMetadata, ChangeDetectionMetadata} from 'angular2/src/compiler/api';
import {MockSchemaRegistry} from './template_parser_spec'; import {MockSchemaRegistry} from './template_parser_spec';
@ -23,7 +22,6 @@ import {
ChangeDetectorDefinition, ChangeDetectorDefinition,
ChangeDetectorGenConfig, ChangeDetectorGenConfig,
DynamicProtoChangeDetector, DynamicProtoChangeDetector,
ProtoChangeDetector,
ChangeDetectionStrategy, ChangeDetectionStrategy,
ChangeDispatcher, ChangeDispatcher,
DirectiveIndex, DirectiveIndex,
@ -33,6 +31,7 @@ import {
} from 'angular2/src/core/change_detection/change_detection'; } from 'angular2/src/core/change_detection/change_detection';
import {Pipes} from 'angular2/src/core/change_detection/pipes'; import {Pipes} from 'angular2/src/core/change_detection/pipes';
import {createChangeDetectorDefinitions} from 'angular2/src/compiler/change_definition_factory'; import {createChangeDetectorDefinitions} from 'angular2/src/compiler/change_definition_factory';
import {TestContext, TestDirective, TestDispatcher, TestPipes} from './change_detector_mocks';
export function main() { export function main() {
describe('ChangeDefinitionFactory', () => { describe('ChangeDefinitionFactory', () => {
@ -62,8 +61,8 @@ export function main() {
protoViewIndex: number = 0): ChangeDetector { protoViewIndex: number = 0): ChangeDetector {
var protoChangeDetectors = var protoChangeDetectors =
createChangeDetectorDefinitions( createChangeDetectorDefinitions(
new TypeMetadata({typeName: 'SomeComp'}), ChangeDetectionStrategy.CheckAlways, new TypeMetadata({typeName: 'SomeComp'}), ChangeDetectionStrategy.Default,
new ChangeDetectorGenConfig(true, true, false), new ChangeDetectorGenConfig(true, true, false, false),
parser.parse(domParser.parse(template, 'TestComp'), directives)) parser.parse(domParser.parse(template, 'TestComp'), directives))
.map(definition => new DynamicProtoChangeDetector(definition)); .map(definition => new DynamicProtoChangeDetector(definition));
var changeDetector = protoChangeDetectors[protoViewIndex].instantiate(dispatcher); var changeDetector = protoChangeDetectors[protoViewIndex].instantiate(dispatcher);
@ -164,47 +163,3 @@ export function main() {
}); });
}); });
} }
class TestContext {
eventLog: string[] = [];
someProp: string;
someProp2: string;
onEvent(value: string) { this.eventLog.push(value); }
}
class TestDirective {
eventLog: string[] = [];
dirProp: string;
onEvent(value: string) { this.eventLog.push(value); }
}
class TestDispatcher implements ChangeDispatcher {
log: string[];
constructor(public directives: any[], public detectors: ProtoChangeDetector[]) { this.clear(); }
getDirectiveFor(di: DirectiveIndex) { return this.directives[di.directiveIndex]; }
getDetectorFor(di: DirectiveIndex) { return this.detectors[di.directiveIndex]; }
clear() { this.log = []; }
notifyOnBinding(target: BindingTarget, value) {
this.log.push(`${target.mode}(${target.name})=${this._asString(value)}`);
}
logBindingUpdate(target, value) {}
notifyAfterContentChecked() {}
notifyAfterViewChecked() {}
getDebugContext(a, b) { return null; }
_asString(value) { return (isBlank(value) ? 'null' : value.toString()); }
}
class TestPipes implements Pipes {
get(type: string) { return null; }
}

View File

@ -0,0 +1,138 @@
import {
ddescribe,
describe,
xdescribe,
it,
iit,
xit,
expect,
beforeEach,
afterEach,
AsyncTestCompleter,
inject
} from 'angular2/test_lib';
import {IS_DART} from '../platform';
import {CONST_EXPR} from 'angular2/src/core/facade/lang';
import {MapWrapper} from 'angular2/src/core/facade/collection';
import {Promise} from 'angular2/src/core/facade/async';
import {ChangeDetectionCompiler} from 'angular2/src/compiler/change_detector_compiler';
import {HtmlParser} from 'angular2/src/compiler/html_parser';
import {
DirectiveMetadata,
TypeMetadata,
ChangeDetectionMetadata,
SourceModule
} from 'angular2/src/compiler/api';
import {MockSchemaRegistry} from './template_parser_spec';
import {TemplateParser} from 'angular2/src/compiler/template_parser';
import {
Parser,
Lexer,
ChangeDetectorGenConfig,
ChangeDetectionStrategy,
ChangeDispatcher,
DirectiveIndex,
Locals,
BindingTarget,
ChangeDetector
} from 'angular2/src/core/change_detection/change_detection';
import {evalModule} from './eval_module';
import {TestContext, TestDispatcher, TestPipes} from './change_detector_mocks';
// Attention: These module names have to correspond to real modules!
const MODULE_NAME = 'angular2/test/compiler/change_detector_compiler_spec';
export function main() {
describe('ChangeDetectorCompiler', () => {
var domParser: HtmlParser;
var parser: TemplateParser;
function createCompiler(useJit: boolean): ChangeDetectionCompiler {
return new ChangeDetectionCompiler(new ChangeDetectorGenConfig(true, true, false, useJit));
}
beforeEach(() => {
domParser = new HtmlParser();
parser = new TemplateParser(
new Parser(new Lexer()),
new MockSchemaRegistry({'invalidProp': false}, {'mappedAttr': 'mappedProp'}));
});
describe('compileComponentRuntime', () => {
function detectChanges(compiler: ChangeDetectionCompiler, template: string,
directives: DirectiveMetadata[] = CONST_EXPR([])): string[] {
var type = new TypeMetadata({typeName: 'SomeComp'});
var parsedTemplate = parser.parse(domParser.parse(template, 'TestComp'), directives);
var factories =
compiler.compileComponentRuntime(type, ChangeDetectionStrategy.Default, parsedTemplate);
return testChangeDetector(factories[0]);
}
it('should watch element properties (no jit)', () => {
expect(detectChanges(createCompiler(false), '<div [el-prop]="someProp">'))
.toEqual(['elementProperty(elProp)=someValue']);
});
it('should watch element properties (jit)', () => {
expect(detectChanges(createCompiler(true), '<div [el-prop]="someProp">'))
.toEqual(['elementProperty(elProp)=someValue']);
});
});
describe('compileComponentCodeGen', () => {
function detectChanges(compiler: ChangeDetectionCompiler, template: string,
directives: DirectiveMetadata[] = CONST_EXPR([])): Promise<string[]> {
var type = new TypeMetadata({typeName: 'SomeComp'});
var parsedTemplate = parser.parse(domParser.parse(template, 'TestComp'), directives);
var sourceModule =
compiler.compileComponentCodeGen(type, ChangeDetectionStrategy.Default, parsedTemplate);
var testableModule = createTestableModule(sourceModule, 0);
return evalModule(testableModule.source, testableModule.imports, null);
}
it('should watch element properties', inject([AsyncTestCompleter], (async) => {
detectChanges(createCompiler(true), '<div [el-prop]="someProp">')
.then((value) => {
expect(value).toEqual(['elementProperty(elProp)=someValue']);
async.done();
});
}));
});
});
}
function createTestableModule(sourceModule: SourceModule, changeDetectorIndex: number):
SourceModule {
var testableSource;
var testableImports = [[MODULE_NAME, 'mocks']].concat(sourceModule.imports);
if (IS_DART) {
testableSource = `${sourceModule.source}
run(_) { return mocks.testChangeDetector(CHANGE_DETECTORS[${changeDetectorIndex}]); }`;
} else {
testableSource = `${sourceModule.source}
exports.run = function(_) { return mocks.testChangeDetector(CHANGE_DETECTORS[${changeDetectorIndex}]); }`;
}
return new SourceModule(null, testableSource, testableImports);
}
export function testChangeDetector(changeDetectorFactory: Function): string[] {
var dispatcher = new TestDispatcher([], []);
var cd = changeDetectorFactory(dispatcher);
var ctx = new TestContext();
ctx.someProp = 'someValue';
var locals = new Locals(null, MapWrapper.createFromStringMap({'someVar': null}));
cd.hydrate(ctx, locals, dispatcher, new TestPipes());
cd.detectChanges();
return dispatcher.log;
}

View File

@ -0,0 +1,52 @@
import {isBlank} from 'angular2/src/core/facade/lang';
import {Pipes} from 'angular2/src/core/change_detection/pipes';
import {
ProtoChangeDetector,
ChangeDispatcher,
DirectiveIndex,
BindingTarget
} from 'angular2/src/core/change_detection/change_detection';
export class TestContext {
eventLog: string[] = [];
someProp: string;
someProp2: string;
onEvent(value: string) { this.eventLog.push(value); }
}
export class TestDirective {
eventLog: string[] = [];
dirProp: string;
onEvent(value: string) { this.eventLog.push(value); }
}
export class TestDispatcher implements ChangeDispatcher {
log: string[];
constructor(public directives: any[], public detectors: ProtoChangeDetector[]) { this.clear(); }
getDirectiveFor(di: DirectiveIndex) { return this.directives[di.directiveIndex]; }
getDetectorFor(di: DirectiveIndex) { return this.detectors[di.directiveIndex]; }
clear() { this.log = []; }
notifyOnBinding(target: BindingTarget, value) {
this.log.push(`${target.mode}(${target.name})=${this._asString(value)}`);
}
logBindingUpdate(target, value) {}
notifyAfterContentChecked() {}
notifyAfterViewChecked() {}
getDebugContext(a, b) { return null; }
_asString(value) { return (isBlank(value) ? 'null' : value.toString()); }
}
export class TestPipes implements Pipes {
get(type: string) { return null; }
}

View File

@ -65,7 +65,7 @@ export const PROP_NAME = 'propName';
* In this case, we expect `id` and `expression` to be the same string. * In this case, we expect `id` and `expression` to be the same string.
*/ */
export function getDefinition(id: string): TestDefinition { export function getDefinition(id: string): TestDefinition {
var genConfig = new ChangeDetectorGenConfig(true, true, true); var genConfig = new ChangeDetectorGenConfig(true, true, true, true);
var testDef = null; var testDef = null;
if (StringMapWrapper.contains(_ExpressionWithLocals.availableDefinitions, id)) { if (StringMapWrapper.contains(_ExpressionWithLocals.availableDefinitions, id)) {
let val = StringMapWrapper.get(_ExpressionWithLocals.availableDefinitions, id); let val = StringMapWrapper.get(_ExpressionWithLocals.availableDefinitions, id);
@ -121,7 +121,7 @@ export function getDefinition(id: string): TestDefinition {
[_DirectiveUpdating.recordNoCallbacks], genConfig); [_DirectiveUpdating.recordNoCallbacks], genConfig);
testDef = new TestDefinition(id, cdDef, null); testDef = new TestDefinition(id, cdDef, null);
} else if (id == "updateElementProduction") { } else if (id == "updateElementProduction") {
var genConfig = new ChangeDetectorGenConfig(false, false, false); var genConfig = new ChangeDetectorGenConfig(false, false, false, true);
var records = _createBindingRecords("name"); var records = _createBindingRecords("name");
let cdDef = new ChangeDetectorDefinition(id, null, [], records, [], [], genConfig); let cdDef = new ChangeDetectorDefinition(id, null, [], records, [], [], genConfig);
testDef = new TestDefinition(id, cdDef, null); testDef = new TestDefinition(id, cdDef, null);
@ -167,7 +167,7 @@ class _ExpressionWithLocals {
var variableBindings = _convertLocalsToVariableBindings(this.locals); var variableBindings = _convertLocalsToVariableBindings(this.locals);
var bindingRecords = _createBindingRecords(this._expression); var bindingRecords = _createBindingRecords(this._expression);
var directiveRecords = []; var directiveRecords = [];
var genConfig = new ChangeDetectorGenConfig(true, true, true); var genConfig = new ChangeDetectorGenConfig(true, true, true, true);
return new ChangeDetectorDefinition('(empty id)', strategy, variableBindings, bindingRecords, return new ChangeDetectorDefinition('(empty id)', strategy, variableBindings, bindingRecords,
[], directiveRecords, genConfig); [], directiveRecords, genConfig);
} }
@ -231,7 +231,7 @@ class _ExpressionWithMode {
_createHostEventRecords("(host-event)='false'", dirRecordWithOnPush)) _createHostEventRecords("(host-event)='false'", dirRecordWithOnPush))
} }
var genConfig = new ChangeDetectorGenConfig(true, true, true); var genConfig = new ChangeDetectorGenConfig(true, true, true, true);
return new ChangeDetectorDefinition('(empty id)', this._strategy, variableBindings, return new ChangeDetectorDefinition('(empty id)', this._strategy, variableBindings,
bindingRecords, eventRecords, directiveRecords, genConfig); bindingRecords, eventRecords, directiveRecords, genConfig);
@ -260,7 +260,7 @@ class _DirectiveUpdating {
createChangeDetectorDefinition(): ChangeDetectorDefinition { createChangeDetectorDefinition(): ChangeDetectorDefinition {
var strategy = null; var strategy = null;
var variableBindings = []; var variableBindings = [];
var genConfig = new ChangeDetectorGenConfig(true, true, true); var genConfig = new ChangeDetectorGenConfig(true, true, true, true);
return new ChangeDetectorDefinition('(empty id)', strategy, variableBindings, return new ChangeDetectorDefinition('(empty id)', strategy, variableBindings,
this._bindingRecords, [], this._directiveRecords, this._bindingRecords, [], this._directiveRecords,

View File

@ -1512,7 +1512,8 @@ export function main() {
describe('logging property updates', () => { describe('logging property updates', () => {
beforeEachBindings(() => [ beforeEachBindings(() => [
bind(ChangeDetection) bind(ChangeDetection)
.toValue(new DynamicChangeDetection(new ChangeDetectorGenConfig(true, true, true))) .toValue(
new DynamicChangeDetection(new ChangeDetectorGenConfig(true, true, true, false)))
]); ]);
it('should reflect property values as attributes', it('should reflect property values as attributes',

View File

@ -249,7 +249,7 @@ function setUpChangeDetection(changeDetection: ChangeDetection, iterations, obje
var dispatcher = new DummyDispatcher(); var dispatcher = new DummyDispatcher();
var parser = new Parser(new Lexer()); var parser = new Parser(new Lexer());
var genConfig = new ChangeDetectorGenConfig(false, false, false); var genConfig = new ChangeDetectorGenConfig(false, false, false, true);
var parentProto = changeDetection.getProtoChangeDetector( var parentProto = changeDetection.getProtoChangeDetector(
"id", new ChangeDetectorDefinition('parent', null, [], [], [], [], genConfig)); "id", new ChangeDetectorDefinition('parent', null, [], [], [], [], genConfig));
var parentCd = parentProto.instantiate(dispatcher); var parentCd = parentProto.instantiate(dispatcher);

View File

@ -58,7 +58,7 @@ Future<String> processTemplates(AssetReader reader, AssetId entryPoint,
} }
if (generateChangeDetectors) { if (generateChangeDetectors) {
var saved = reflector.reflectionCapabilities; var saved = reflector.reflectionCapabilities;
var genConfig = new ChangeDetectorGenConfig(assertionsEnabled(), assertionsEnabled(), reflectPropertiesAsAttributes); var genConfig = new ChangeDetectorGenConfig(assertionsEnabled(), assertionsEnabled(), reflectPropertiesAsAttributes, false);
reflector.reflectionCapabilities = const NullReflectionCapabilities(); reflector.reflectionCapabilities = const NullReflectionCapabilities();
var defs = getChangeDetectorDefinitions(viewDefEntry.hostMetadata, var defs = getChangeDetectorDefinitions(viewDefEntry.hostMetadata,