build(typescript): Migrated change detection to typescript

This commit is contained in:
vsavkin 2015-05-01 14:05:19 -07:00
parent f0ef72d6cc
commit fa28b28d0a
45 changed files with 2343 additions and 2576 deletions

View File

@ -6,17 +6,40 @@
*/ */
export { export {
ASTWithSource, AST, AstTransformer, AccessMember, LiteralArray, ImplicitReceiver ASTWithSource,
AST,
AstTransformer,
AccessMember,
LiteralArray,
ImplicitReceiver
} from './src/change_detection/parser/ast'; } from './src/change_detection/parser/ast';
export {Lexer} from './src/change_detection/parser/lexer'; export {Lexer} from './src/change_detection/parser/lexer';
export {Parser} from './src/change_detection/parser/parser'; export {Parser} from './src/change_detection/parser/parser';
export {Locals} from './src/change_detection/parser/locals'; export {Locals} from './src/change_detection/parser/locals';
export {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './src/change_detection/exceptions'; export {
export {ProtoChangeDetector, ChangeDispatcher, ChangeDetector, ChangeDetection} from './src/change_detection/interfaces'; ExpressionChangedAfterItHasBeenChecked,
export {CHECK_ONCE, CHECK_ALWAYS, DETACHED, CHECKED, ON_PUSH, DEFAULT} from './src/change_detection/constants'; ChangeDetectionError
export {DynamicProtoChangeDetector, JitProtoChangeDetector} from './src/change_detection/proto_change_detector'; } from './src/change_detection/exceptions';
export {
ProtoChangeDetector,
ChangeDispatcher,
ChangeDetector,
ChangeDetection
} from './src/change_detection/interfaces';
export {
CHECK_ONCE,
CHECK_ALWAYS,
DETACHED,
CHECKED,
ON_PUSH,
DEFAULT
} from './src/change_detection/constants';
export {
DynamicProtoChangeDetector,
JitProtoChangeDetector
} from './src/change_detection/proto_change_detector';
export {BindingRecord} from './src/change_detection/binding_record'; export {BindingRecord} from './src/change_detection/binding_record';
export {DirectiveIndex, DirectiveRecord} from './src/change_detection/directive_record'; export {DirectiveIndex, DirectiveRecord} from './src/change_detection/directive_record';
export {DynamicChangeDetector} from './src/change_detection/dynamic_change_detector'; export {DynamicChangeDetector} from './src/change_detection/dynamic_change_detector';
@ -25,5 +48,13 @@ export {PipeRegistry} from './src/change_detection/pipes/pipe_registry';
export {uninitialized} from './src/change_detection/change_detection_util'; export {uninitialized} from './src/change_detection/change_detection_util';
export {WrappedValue, Pipe} from './src/change_detection/pipes/pipe'; export {WrappedValue, Pipe} from './src/change_detection/pipes/pipe';
export { export {
defaultPipes, DynamicChangeDetection, JitChangeDetection, defaultPipeRegistry defaultPipes,
DynamicChangeDetection,
JitChangeDetection,
defaultPipeRegistry
} from './src/change_detection/change_detection'; } from './src/change_detection/change_detection';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;

View File

@ -4,12 +4,17 @@ import {ChangeDetectorRef} from './change_detector_ref';
import {ChangeDetector} from './interfaces'; import {ChangeDetector} from './interfaces';
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants'; import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
export class AbstractChangeDetector extends ChangeDetector { export class AbstractChangeDetector extends ChangeDetector {
lightDomChildren:List; lightDomChildren: List<any>;
shadowDomChildren:List; shadowDomChildren: List<any>;
parent:ChangeDetector; parent: ChangeDetector;
mode:string; mode: string;
ref:ChangeDetectorRef; ref: ChangeDetectorRef;
constructor() { constructor() {
super(); super();
@ -19,37 +24,27 @@ export class AbstractChangeDetector extends ChangeDetector {
this.mode = null; this.mode = null;
} }
addChild(cd:ChangeDetector) { addChild(cd: ChangeDetector) {
ListWrapper.push(this.lightDomChildren, cd); ListWrapper.push(this.lightDomChildren, cd);
cd.parent = this; cd.parent = this;
} }
removeChild(cd:ChangeDetector) { removeChild(cd: ChangeDetector) { ListWrapper.remove(this.lightDomChildren, cd); }
ListWrapper.remove(this.lightDomChildren, cd);
}
addShadowDomChild(cd:ChangeDetector) { addShadowDomChild(cd: ChangeDetector) {
ListWrapper.push(this.shadowDomChildren, cd); ListWrapper.push(this.shadowDomChildren, cd);
cd.parent = this; cd.parent = this;
} }
removeShadowDomChild(cd:ChangeDetector) { removeShadowDomChild(cd: ChangeDetector) { ListWrapper.remove(this.shadowDomChildren, cd); }
ListWrapper.remove(this.shadowDomChildren, cd);
}
remove() { remove() { this.parent.removeChild(this); }
this.parent.removeChild(this);
}
detectChanges() { detectChanges() { this._detectChanges(false); }
this._detectChanges(false);
}
checkNoChanges() { checkNoChanges() { this._detectChanges(true); }
this._detectChanges(true);
}
_detectChanges(throwOnChange:boolean) { _detectChanges(throwOnChange: boolean) {
if (this.mode === DETACHED || this.mode === CHECKED) return; if (this.mode === DETACHED || this.mode === CHECKED) return;
this.detectChangesInRecords(throwOnChange); this.detectChangesInRecords(throwOnChange);
@ -63,30 +58,28 @@ export class AbstractChangeDetector extends ChangeDetector {
if (this.mode === CHECK_ONCE) this.mode = CHECKED; if (this.mode === CHECK_ONCE) this.mode = CHECKED;
} }
detectChangesInRecords(throwOnChange:boolean){} detectChangesInRecords(throwOnChange: boolean) {}
callOnAllChangesDone(){} callOnAllChangesDone() {}
_detectChangesInLightDomChildren(throwOnChange:boolean) { _detectChangesInLightDomChildren(throwOnChange: boolean) {
var c = this.lightDomChildren; var c = this.lightDomChildren;
for(var i = 0; i < c.length; ++i) { for (var i = 0; i < c.length; ++i) {
c[i]._detectChanges(throwOnChange); c[i]._detectChanges(throwOnChange);
} }
} }
_detectChangesInShadowDomChildren(throwOnChange:boolean) { _detectChangesInShadowDomChildren(throwOnChange: boolean) {
var c = this.shadowDomChildren; var c = this.shadowDomChildren;
for(var i = 0; i < c.length; ++i) { for (var i = 0; i < c.length; ++i) {
c[i]._detectChanges(throwOnChange); c[i]._detectChanges(throwOnChange);
} }
} }
markAsCheckOnce() { markAsCheckOnce() { this.mode = CHECK_ONCE; }
this.mode = CHECK_ONCE;
}
markPathToRootAsCheckOnce() { markPathToRootAsCheckOnce() {
var c = this; var c: ChangeDetector = this;
while(isPresent(c) && c.mode != DETACHED) { while (isPresent(c) && c.mode != DETACHED) {
if (c.mode === CHECKED) c.mode = CHECK_ONCE; if (c.mode === CHECKED) c.mode = CHECK_ONCE;
c = c.parent; c = c.parent;
} }

View File

@ -1,68 +0,0 @@
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {SetterFn} from 'angular2/src/reflection/types';
import {AST} from './parser/ast';
import {DirectiveIndex, DirectiveRecord} from './directive_record';
const DIRECTIVE="directive";
const ELEMENT="element";
const TEXT_NODE="textNode";
export class BindingRecord {
mode:string;
ast:AST;
implicitReceiver:any; //number | DirectiveIndex
elementIndex:number;
propertyName:string;
setter:SetterFn;
directiveRecord:DirectiveRecord;
constructor(mode:string, implicitReceiver:any, ast:AST, elementIndex:number, propertyName:string, setter:SetterFn, directiveRecord:DirectiveRecord) {
this.mode = mode;
this.implicitReceiver = implicitReceiver;
this.ast = ast;
this.elementIndex = elementIndex;
this.propertyName = propertyName;
this.setter = setter;
this.directiveRecord = directiveRecord;
}
callOnChange() {
return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange;
}
isOnPushChangeDetection() {
return isPresent(this.directiveRecord) && this.directiveRecord.isOnPushChangeDetection();
}
isDirective() {
return this.mode === DIRECTIVE;
}
isElement() {
return this.mode === ELEMENT;
}
isTextNode() {
return this.mode === TEXT_NODE;
}
static createForDirective(ast:AST, propertyName:string, setter:SetterFn, directiveRecord:DirectiveRecord) {
return new BindingRecord(DIRECTIVE, 0, ast, 0, propertyName, setter, directiveRecord);
}
static createForElement(ast:AST, elementIndex:number, propertyName:string) {
return new BindingRecord(ELEMENT, 0, ast, elementIndex, propertyName, null, null);
}
static createForHostProperty(directiveIndex:DirectiveIndex, ast:AST, propertyName:string) {
return new BindingRecord(ELEMENT, directiveIndex, ast, directiveIndex.elementIndex, propertyName, null, null);
}
static createForTextNode(ast:AST, elementIndex:number) {
return new BindingRecord(TEXT_NODE, 0, ast, elementIndex, null, null, null);
}
}

View File

@ -0,0 +1,49 @@
import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {SetterFn} from 'angular2/src/reflection/types';
import {AST} from './parser/ast';
import {DirectiveIndex, DirectiveRecord} from './directive_record';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
const DIRECTIVE = "directive";
const ELEMENT = "element";
const TEXT_NODE = "textNode";
export class BindingRecord {
constructor(public mode: string, public implicitReceiver: any, public ast: AST,
public elementIndex: number, public propertyName: string, public setter: SetterFn,
public directiveRecord: DirectiveRecord) {}
callOnChange() { return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange; }
isOnPushChangeDetection() {
return isPresent(this.directiveRecord) && this.directiveRecord.isOnPushChangeDetection();
}
isDirective() { return this.mode === DIRECTIVE; }
isElement() { return this.mode === ELEMENT; }
isTextNode() { return this.mode === TEXT_NODE; }
static createForDirective(ast: AST, propertyName: string, setter: SetterFn,
directiveRecord: DirectiveRecord) {
return new BindingRecord(DIRECTIVE, 0, ast, 0, propertyName, setter, directiveRecord);
}
static createForElement(ast: AST, elementIndex: number, propertyName: string) {
return new BindingRecord(ELEMENT, 0, ast, elementIndex, propertyName, null, null);
}
static createForHostProperty(directiveIndex: DirectiveIndex, ast: AST, propertyName: string) {
return new BindingRecord(ELEMENT, directiveIndex, ast, directiveIndex.elementIndex,
propertyName, null, null);
}
static createForTextNode(ast: AST, elementIndex: number) {
return new BindingRecord(TEXT_NODE, 0, ast, elementIndex, null, null, null);
}
}

View File

@ -1,96 +0,0 @@
import {DynamicProtoChangeDetector, JitProtoChangeDetector} from './proto_change_detector';
import {PipeFactory} from './pipes/pipe';
import {PipeRegistry} from './pipes/pipe_registry';
import {IterableChangesFactory} from './pipes/iterable_changes';
import {KeyValueChangesFactory} from './pipes/keyvalue_changes';
import {AsyncPipeFactory} from './pipes/async_pipe';
import {NullPipeFactory} from './pipes/null_pipe';
import {BindingRecord} from './binding_record';
import {DirectiveRecord} from './directive_record';
import {DEFAULT} from './constants';
import {ChangeDetection, ProtoChangeDetector} from './interfaces';
import {Injectable} from 'angular2/src/di/annotations_impl';
import {List} from 'angular2/src/facade/collection';
/**
* Structural diffing for `Object`s and `Map`s.
*
* @exportedAs angular2/pipes
*/
export var keyValDiff:List<PipeFactory> = [
new KeyValueChangesFactory(),
new NullPipeFactory()
];
/**
* Structural diffing for `Iterable` types such as `Array`s.
*
* @exportedAs angular2/pipes
*/
export var iterableDiff:List<PipeFactory> = [
new IterableChangesFactory(),
new NullPipeFactory()
];
/**
* Async binding to such types as Observable.
*
* @exportedAs angular2/pipes
*/
export var async:List<PipeFactory> = [
new AsyncPipeFactory(),
new NullPipeFactory()
];
export var defaultPipes:Map<String, List<PipeFactory>> = {
"iterableDiff" : iterableDiff,
"keyValDiff" : keyValDiff,
"async" : async
};
/**
* Implements change detection that does not require `eval()`.
*
* This is slower than {@link JitChangeDetection}.
*
* @exportedAs angular2/change_detection
*/
@Injectable()
export class DynamicChangeDetection extends ChangeDetection {
registry:PipeRegistry;
constructor(registry:PipeRegistry) {
super();
this.registry = registry;
}
createProtoChangeDetector(name:string, bindingRecords:List<BindingRecord>, variableBindings:List<string>,
directiveRecords:List<DirectiveRecord>, changeControlStrategy:string = DEFAULT):ProtoChangeDetector{
return new DynamicProtoChangeDetector(this.registry, bindingRecords, variableBindings, directiveRecords, changeControlStrategy);
}
}
/**
* Implements faster change detection, by generating source code.
*
* This requires `eval()`. For change detection that does not require `eval()`, see {@link DynamicChangeDetection}.
*
* @exportedAs angular2/change_detection
*/
@Injectable()
export class JitChangeDetection extends ChangeDetection {
registry:PipeRegistry;
constructor(registry:PipeRegistry) {
super();
this.registry = registry;
}
createProtoChangeDetector(name:string, bindingRecords:List<BindingRecord>, variableBindings:List<string>,
directiveRecords:List<DirectiveRecord>, changeControlStrategy:string = DEFAULT):ProtoChangeDetector{
return new JitProtoChangeDetector(this.registry, bindingRecords, variableBindings, directiveRecords, changeControlStrategy);
}
}
export var defaultPipeRegistry:PipeRegistry = new PipeRegistry(defaultPipes);

View File

@ -0,0 +1,88 @@
import {DynamicProtoChangeDetector, JitProtoChangeDetector} from './proto_change_detector';
import {PipeFactory} from './pipes/pipe';
import {PipeRegistry} from './pipes/pipe_registry';
import {IterableChangesFactory} from './pipes/iterable_changes';
import {KeyValueChangesFactory} from './pipes/keyvalue_changes';
import {AsyncPipeFactory} from './pipes/async_pipe';
import {NullPipeFactory} from './pipes/null_pipe';
import {BindingRecord} from './binding_record';
import {DirectiveRecord} from './directive_record';
import {DEFAULT} from './constants';
import {ChangeDetection, ProtoChangeDetector} from './interfaces';
import {Injectable} from 'angular2/src/di/decorators';
import {List} from 'angular2/src/facade/collection';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
/**
* Structural diffing for `Object`s and `Map`s.
*
* @exportedAs angular2/pipes
*/
export var keyValDiff: List < PipeFactory >= [new KeyValueChangesFactory(), new NullPipeFactory()];
/**
* Structural diffing for `Iterable` types such as `Array`s.
*
* @exportedAs angular2/pipes
*/
export var iterableDiff: List <
PipeFactory >= [new IterableChangesFactory(), new NullPipeFactory()];
/**
* Async binding to such types as Observable.
*
* @exportedAs angular2/pipes
*/
export var async: List < PipeFactory >= [new AsyncPipeFactory(), new NullPipeFactory()];
export var defaultPipes = {
"iterableDiff": iterableDiff,
"keyValDiff": keyValDiff,
"async": async
};
/**
* Implements change detection that does not require `eval()`.
*
* This is slower than {@link JitChangeDetection}.
*
* @exportedAs angular2/change_detection
*/
@Injectable()
export class DynamicChangeDetection extends ChangeDetection {
constructor(public registry: PipeRegistry) { super(); }
createProtoChangeDetector(name: string, bindingRecords: List<BindingRecord>,
variableBindings: List<string>, directiveRecords: List<DirectiveRecord>,
changeControlStrategy: string = DEFAULT): ProtoChangeDetector {
return new DynamicProtoChangeDetector(this.registry, bindingRecords, variableBindings,
directiveRecords, changeControlStrategy);
}
}
/**
* Implements faster change detection, by generating source code.
*
* This requires `eval()`. For change detection that does not require `eval()`, see {@link
*DynamicChangeDetection}.
*
* @exportedAs angular2/change_detection
*/
@Injectable()
export class JitChangeDetection extends ChangeDetection {
constructor(public registry: PipeRegistry) { super(); }
createProtoChangeDetector(name: string, bindingRecords: List<BindingRecord>,
variableBindings: List<string>, directiveRecords: List<DirectiveRecord>,
changeControlStrategy: string = DEFAULT): ProtoChangeDetector {
return new JitProtoChangeDetector(this.registry, bindingRecords, variableBindings,
directiveRecords, changeControlStrategy);
}
}
export var defaultPipeRegistry: PipeRegistry = new PipeRegistry(defaultPipes);

View File

@ -18,7 +18,13 @@ import {
RECORD_TYPE_PIPE, RECORD_TYPE_PIPE,
RECORD_TYPE_BINDING_PIPE, RECORD_TYPE_BINDING_PIPE,
RECORD_TYPE_INTERPOLATE RECORD_TYPE_INTERPOLATE
} from './proto_record'; } from './proto_record';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
/** /**
* 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
@ -43,8 +49,8 @@ var MODE_ACCESSOR = "this.mode";
var TEMP_LOCAL = "temp"; var TEMP_LOCAL = "temp";
var CURRENT_PROTO = "currentProto"; var CURRENT_PROTO = "currentProto";
function typeTemplate(type:string, cons:string, detectChanges:string, function typeTemplate(type: string, cons: string, detectChanges: string,
notifyOnAllChangesDone:string, setContext:string):string { notifyOnAllChangesDone: string, setContext: string): string {
return ` return `
${cons} ${cons}
${detectChanges} ${detectChanges}
@ -57,7 +63,7 @@ return function(dispatcher, pipeRegistry) {
`; `;
} }
function constructorTemplate(type:string, fieldsDefinitions:string):string { function constructorTemplate(type: string, fieldsDefinitions: string): string {
return ` return `
var ${type} = function ${type}(dispatcher, pipeRegistry, protos, directiveRecords) { var ${type} = function ${type}(dispatcher, pipeRegistry, protos, directiveRecords) {
${ABSTRACT_CHANGE_DETECTOR}.call(this); ${ABSTRACT_CHANGE_DETECTOR}.call(this);
@ -73,20 +79,23 @@ ${type}.prototype = Object.create(${ABSTRACT_CHANGE_DETECTOR}.prototype);
`; `;
} }
function pipeOnDestroyTemplate(pipeNames:List) { function pipeOnDestroyTemplate(pipeNames: List<any>) {
return pipeNames.map((p) => `${p}.onDestroy()`).join("\n"); return pipeNames.map((p) => `${p}.onDestroy()`).join("\n");
} }
function hydrateTemplate(type:string, mode:string, fieldDefinitions:string, pipeOnDestroy:string, function hydrateTemplate(type: string, mode: string, fieldDefinitions: string,
directiveFieldNames:List<String>, detectorFieldNames:List<String>):string { pipeOnDestroy: string, directiveFieldNames: List<String>,
detectorFieldNames: List<String>): string {
var directiveInit = ""; var directiveInit = "";
for(var i = 0; i < directiveFieldNames.length; ++i) { for (var i = 0; i < directiveFieldNames.length; ++i) {
directiveInit += `${directiveFieldNames[i]} = directives.getDirectiveFor(this.directiveRecords[${i}].directiveIndex);\n`; directiveInit +=
`${directiveFieldNames[i]} = directives.getDirectiveFor(this.directiveRecords[${i}].directiveIndex);\n`;
} }
var detectorInit = ""; var detectorInit = "";
for(var i = 0; i < detectorFieldNames.length; ++i) { for (var i = 0; i < detectorFieldNames.length; ++i) {
detectorInit += `${detectorFieldNames[i]} = directives.getDetectorFor(this.directiveRecords[${i}].directiveIndex);\n`; detectorInit +=
`${detectorFieldNames[i]} = directives.getDetectorFor(this.directiveRecords[${i}].directiveIndex);\n`;
} }
return ` return `
@ -108,7 +117,7 @@ ${type}.prototype.hydrated = function() {
`; `;
} }
function detectChangesTemplate(type:string, body:string):string { function detectChangesTemplate(type: string, body: string): string {
return ` return `
${type}.prototype.detectChangesInRecords = function(throwOnChange) { ${type}.prototype.detectChangesInRecords = function(throwOnChange) {
${body} ${body}
@ -116,7 +125,7 @@ ${type}.prototype.detectChangesInRecords = function(throwOnChange) {
`; `;
} }
function callOnAllChangesDoneTemplate(type:string, body:string):string { function callOnAllChangesDoneTemplate(type: string, body: string): string {
return ` return `
${type}.prototype.callOnAllChangesDone = function() { ${type}.prototype.callOnAllChangesDone = function() {
${body} ${body}
@ -124,12 +133,13 @@ ${type}.prototype.callOnAllChangesDone = function() {
`; `;
} }
function onAllChangesDoneTemplate(directive:string):string { function onAllChangesDoneTemplate(directive: string): string {
return `${directive}.onAllChangesDone();`; return `${directive}.onAllChangesDone();`;
} }
function detectChangesBodyTemplate(localDefinitions:string, changeDefinitions:string, records:string):string { function detectChangesBodyTemplate(localDefinitions: string, changeDefinitions: string,
records: string): string {
return ` return `
${localDefinitions} ${localDefinitions}
${changeDefinitions} ${changeDefinitions}
@ -143,9 +153,10 @@ ${records}
`; `;
} }
function pipeCheckTemplate(protoIndex:number, context:string, bindingPropagationConfig:string, pipe:string, pipeType:string, function pipeCheckTemplate(protoIndex: number, context: string, bindingPropagationConfig: string,
oldValue:string, newValue:string, change:string, update:string, pipe: string, pipeType: string, oldValue: string, newValue: string,
addToChanges, lastInDirective:string):string{ change: string, update: string, addToChanges,
lastInDirective: string): string {
return ` return `
${CURRENT_PROTO} = ${PROTOS_ACCESSOR}[${protoIndex}]; ${CURRENT_PROTO} = ${PROTOS_ACCESSOR}[${protoIndex}];
if (${pipe} === ${UTIL}.unitialized()) { if (${pipe} === ${UTIL}.unitialized()) {
@ -167,8 +178,9 @@ ${lastInDirective}
`; `;
} }
function referenceCheckTemplate(protoIndex:number, assignment:string, oldValue:string, newValue:string, change:string, function referenceCheckTemplate(protoIndex: number, assignment: string, oldValue: string,
update:string, addToChanges:string, lastInDirective:string):string { newValue: string, change: string, update: string,
addToChanges: string, lastInDirective: string): string {
return ` return `
${CURRENT_PROTO} = ${PROTOS_ACCESSOR}[${protoIndex}]; ${CURRENT_PROTO} = ${PROTOS_ACCESSOR}[${protoIndex}];
${assignment} ${assignment}
@ -182,23 +194,23 @@ ${lastInDirective}
`; `;
} }
function assignmentTemplate(field:string, value:string) { function assignmentTemplate(field: string, value: string) {
return `${field} = ${value};`; return `${field} = ${value};`;
} }
function localDefinitionsTemplate(names:List):string { function localDefinitionsTemplate(names: List<any>): string {
return names.map((n) => `var ${n};`).join("\n"); return names.map((n) => `var ${n};`).join("\n");
} }
function changeDefinitionsTemplate(names:List):string { function changeDefinitionsTemplate(names: List<any>): string {
return names.map((n) => `var ${n} = false;`).join("\n"); return names.map((n) => `var ${n} = false;`).join("\n");
} }
function fieldDefinitionsTemplate(names:List):string { function fieldDefinitionsTemplate(names: List<any>): string {
return names.map((n) => `${n} = ${UTIL}.unitialized();`).join("\n"); return names.map((n) => `${n} = ${UTIL}.unitialized();`).join("\n");
} }
function ifChangedGuardTemplate(changeNames:List, body:string):string { function ifChangedGuardTemplate(changeNames: List<any>, body: string): string {
var cond = changeNames.join(" || "); var cond = changeNames.join(" || ");
return ` return `
if (${cond}) { if (${cond}) {
@ -207,11 +219,12 @@ if (${cond}) {
`; `;
} }
function addToChangesTemplate(oldValue:string, newValue:string):string { function addToChangesTemplate(oldValue: string, newValue: string): string {
return `${CHANGES_LOCAL} = ${UTIL}.addChange(${CHANGES_LOCAL}, ${CURRENT_PROTO}.bindingRecord.propertyName, ${UTIL}.simpleChange(${oldValue}, ${newValue}));`; return `${CHANGES_LOCAL} = ${UTIL}.addChange(${CHANGES_LOCAL}, ${CURRENT_PROTO}.bindingRecord.propertyName, ${UTIL}.simpleChange(${oldValue}, ${newValue}));`;
} }
function updateDirectiveTemplate(oldValue:string, newValue:string, directiveProperty:string):string { function updateDirectiveTemplate(oldValue: string, newValue: string,
directiveProperty: string): string {
return ` return `
if(throwOnChange) ${UTIL}.throwOnChange(${CURRENT_PROTO}, ${UTIL}.simpleChange(${oldValue}, ${newValue})); if(throwOnChange) ${UTIL}.throwOnChange(${CURRENT_PROTO}, ${UTIL}.simpleChange(${oldValue}, ${newValue}));
${directiveProperty} = ${newValue}; ${directiveProperty} = ${newValue};
@ -219,15 +232,15 @@ ${IS_CHANGED_LOCAL} = true;
`; `;
} }
function updateElementTemplate(oldValue:string, newValue:string):string { function updateElementTemplate(oldValue: string, newValue: string): string {
return ` return `
if(throwOnChange) ${UTIL}.throwOnChange(${CURRENT_PROTO}, ${UTIL}.simpleChange(${oldValue}, ${newValue})); if(throwOnChange) ${UTIL}.throwOnChange(${CURRENT_PROTO}, ${UTIL}.simpleChange(${oldValue}, ${newValue}));
${DISPATCHER_ACCESSOR}.notifyOnBinding(${CURRENT_PROTO}.bindingRecord, ${newValue}); ${DISPATCHER_ACCESSOR}.notifyOnBinding(${CURRENT_PROTO}.bindingRecord, ${newValue});
`; `;
} }
function notifyOnChangesTemplate(directive:string):string{ function notifyOnChangesTemplate(directive: string): string {
return ` return `
if(${CHANGES_LOCAL}) { if(${CHANGES_LOCAL}) {
${directive}.onChange(${CHANGES_LOCAL}); ${directive}.onChange(${CHANGES_LOCAL});
${CHANGES_LOCAL} = null; ${CHANGES_LOCAL} = null;
@ -235,16 +248,16 @@ if(${CHANGES_LOCAL}) {
`; `;
} }
function notifyOnPushDetectorsTemplate(detector:string):string{ function notifyOnPushDetectorsTemplate(detector: string): string {
return ` return `
if(${IS_CHANGED_LOCAL}) { if(${IS_CHANGED_LOCAL}) {
${detector}.markAsCheckOnce(); ${detector}.markAsCheckOnce();
} }
`; `;
} }
function lastInDirectiveTemplate(notifyOnChanges:string, notifyOnPush:string):string{ function lastInDirectiveTemplate(notifyOnChanges: string, notifyOnPush: string): string {
return ` return `
${notifyOnChanges} ${notifyOnChanges}
${notifyOnPush} ${notifyOnPush}
${IS_CHANGED_LOCAL} = false; ${IS_CHANGED_LOCAL} = false;
@ -253,28 +266,20 @@ ${IS_CHANGED_LOCAL} = false;
export class ChangeDetectorJITGenerator { export class ChangeDetectorJITGenerator {
typeName:string; localNames: List<string>;
records:List<ProtoRecord>; changeNames: List<string>;
directiveRecords:List; fieldNames: List<string>;
localNames:List<string>; pipeNames: List<string>;
changeNames:List<string>;
fieldNames:List<string>;
pipeNames:List<string>;
changeDetectionStrategy:stirng;
constructor(typeName:string, changeDetectionStrategy:string, records:List<ProtoRecord>, directiveRecords:List) {
this.typeName = typeName;
this.changeDetectionStrategy = changeDetectionStrategy;
this.records = records;
this.directiveRecords = directiveRecords;
constructor(public typeName: string, public changeDetectionStrategy: string,
public records: List<ProtoRecord>, public directiveRecords: List<any>) {
this.localNames = this.getLocalNames(records); this.localNames = this.getLocalNames(records);
this.changeNames = this.getChangeNames(this.localNames); this.changeNames = this.getChangeNames(this.localNames);
this.fieldNames = this.getFieldNames(this.localNames); this.fieldNames = this.getFieldNames(this.localNames);
this.pipeNames = this.getPipeNames(this.localNames); this.pipeNames = this.getPipeNames(this.localNames);
} }
getLocalNames(records:List<ProtoRecord>):List<string> { getLocalNames(records: List<ProtoRecord>): List<string> {
var index = 0; var index = 0;
var names = records.map((r) => { var names = records.map((r) => {
var sanitizedName = r.name.replace(new RegExp("\\W", "g"), ''); var sanitizedName = r.name.replace(new RegExp("\\W", "g"), '');
@ -283,51 +288,49 @@ export class ChangeDetectorJITGenerator {
return ["context"].concat(names); return ["context"].concat(names);
} }
getChangeNames(localNames:List<string>):List<string> { getChangeNames(localNames: List<string>): List<string> {
return localNames.map((n) => `change_${n}`); return localNames.map((n) => `change_${n}`);
} }
getFieldNames(localNames:List<string>):List<string> { getFieldNames(localNames: List<string>): List<string> {
return localNames.map((n) => `this.${n}`); return localNames.map((n) => `this.${n}`);
} }
getPipeNames(localNames:List<string>):List<string> { getPipeNames(localNames: List<string>): List<string> {
return localNames.map((n) => `this.${n}_pipe`); return localNames.map((n) => `this.${n}_pipe`);
} }
generate():Function { generate(): Function {
var text = typeTemplate(this.typeName, this.genConstructor(), this.genDetectChanges(), var text = typeTemplate(this.typeName, this.genConstructor(), this.genDetectChanges(),
this.genCallOnAllChangesDone(), this.genHydrate()); this.genCallOnAllChangesDone(), this.genHydrate());
return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'protos', 'directiveRecords', text) return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'protos',
(AbstractChangeDetector, ChangeDetectionUtil, this.records, this.directiveRecords); 'directiveRecords', text)(AbstractChangeDetector, ChangeDetectionUtil,
this.records, this.directiveRecords);
} }
genConstructor():string { genConstructor(): string {
return constructorTemplate(this.typeName, this.genFieldDefinitions()); return constructorTemplate(this.typeName, this.genFieldDefinitions());
} }
genHydrate():string { genHydrate(): string {
var mode = ChangeDetectionUtil.changeDetectionMode(this.changeDetectionStrategy); var mode = ChangeDetectionUtil.changeDetectionMode(this.changeDetectionStrategy);
return hydrateTemplate(this.typeName, mode, this.genFieldDefinitions(), return hydrateTemplate(this.typeName, mode, this.genFieldDefinitions(),
pipeOnDestroyTemplate(this.getNonNullPipeNames()), pipeOnDestroyTemplate(this.getNonNullPipeNames()),
this.getDirectiveFieldNames(), this.getDetectorFieldNames()); this.getDirectiveFieldNames(), this.getDetectorFieldNames());
} }
getDirectiveFieldNames():List<string> { getDirectiveFieldNames(): List<string> {
return this.directiveRecords.map((d) => this.getDirective(d.directiveIndex)); return this.directiveRecords.map((d) => this.getDirective(d.directiveIndex));
} }
getDetectorFieldNames():List<string> { getDetectorFieldNames(): List<string> {
return this.directiveRecords.filter(r => r.isOnPushChangeDetection()).map((d) => this.getDetector(d.directiveIndex)); return this.directiveRecords.filter(r => r.isOnPushChangeDetection())
.map((d) => this.getDetector(d.directiveIndex));
} }
getDirective(d:DirectiveIndex) { getDirective(d: DirectiveIndex) { return `this.directive_${d.name}`; }
return `this.directive_${d.name}`;
}
getDetector(d:DirectiveIndex) { getDetector(d: DirectiveIndex) { return `this.detector_${d.name}`; }
return `this.detector_${d.name}`;
}
genFieldDefinitions() { genFieldDefinitions() {
var fields = []; var fields = [];
@ -338,7 +341,7 @@ export class ChangeDetectorJITGenerator {
return fieldDefinitionsTemplate(fields); return fieldDefinitionsTemplate(fields);
} }
getNonNullPipeNames():List<string> { getNonNullPipeNames(): List<string> {
var pipes = []; var pipes = [];
this.records.forEach((r) => { this.records.forEach((r) => {
if (r.mode === RECORD_TYPE_PIPE || r.mode === RECORD_TYPE_BINDING_PIPE) { if (r.mode === RECORD_TYPE_PIPE || r.mode === RECORD_TYPE_BINDING_PIPE) {
@ -348,12 +351,12 @@ export class ChangeDetectorJITGenerator {
return pipes; return pipes;
} }
genDetectChanges():string { genDetectChanges(): string {
var body = this.genDetectChangesBody(); var body = this.genDetectChangesBody();
return detectChangesTemplate(this.typeName, body); return detectChangesTemplate(this.typeName, body);
} }
genCallOnAllChangesDone():string { genCallOnAllChangesDone(): string {
var notifications = []; var notifications = [];
var dirs = this.directiveRecords; var dirs = this.directiveRecords;
@ -368,28 +371,24 @@ export class ChangeDetectorJITGenerator {
return callOnAllChangesDoneTemplate(this.typeName, notifications.join(";\n")); return callOnAllChangesDoneTemplate(this.typeName, notifications.join(";\n"));
} }
genDetectChangesBody():string { genDetectChangesBody(): string {
var rec = this.records.map((r) => this.genRecord(r)).join("\n"); var rec = this.records.map((r) => this.genRecord(r)).join("\n");
return detectChangesBodyTemplate(this.genLocalDefinitions(), this.genChangeDefinitions(), rec); return detectChangesBodyTemplate(this.genLocalDefinitions(), this.genChangeDefinitions(), rec);
} }
genLocalDefinitions():string { genLocalDefinitions(): string { return localDefinitionsTemplate(this.localNames); }
return localDefinitionsTemplate(this.localNames);
}
genChangeDefinitions():string { genChangeDefinitions(): string { return changeDefinitionsTemplate(this.changeNames); }
return changeDefinitionsTemplate(this.changeNames);
}
genRecord(r:ProtoRecord):string { genRecord(r: ProtoRecord): string {
if (r.mode === RECORD_TYPE_PIPE || r.mode === RECORD_TYPE_BINDING_PIPE) { if (r.mode === RECORD_TYPE_PIPE || r.mode === RECORD_TYPE_BINDING_PIPE) {
return this.genPipeCheck (r); return this.genPipeCheck(r);
} else { } else {
return this.genReferenceCheck(r); return this.genReferenceCheck(r);
} }
} }
genPipeCheck(r:ProtoRecord):string { genPipeCheck(r: ProtoRecord): string {
var context = this.localNames[r.contextIndex]; var context = this.localNames[r.contextIndex];
var oldValue = this.fieldNames[r.selfIndex]; var oldValue = this.fieldNames[r.selfIndex];
var newValue = this.localNames[r.selfIndex]; var newValue = this.localNames[r.selfIndex];
@ -402,11 +401,11 @@ export class ChangeDetectorJITGenerator {
var addToChanges = this.genAddToChanges(r); var addToChanges = this.genAddToChanges(r);
var lastInDirective = this.genLastInDirective(r); var lastInDirective = this.genLastInDirective(r);
return pipeCheckTemplate(r.selfIndex - 1, context, cdRef, pipe, r.name, oldValue, newValue, change, return pipeCheckTemplate(r.selfIndex - 1, context, cdRef, pipe, r.name, oldValue, newValue,
update, addToChanges, lastInDirective); change, update, addToChanges, lastInDirective);
} }
genReferenceCheck(r:ProtoRecord):string { genReferenceCheck(r: ProtoRecord): string {
var oldValue = this.fieldNames[r.selfIndex]; var oldValue = this.fieldNames[r.selfIndex];
var newValue = this.localNames[r.selfIndex]; var newValue = this.localNames[r.selfIndex];
var change = this.changeNames[r.selfIndex]; var change = this.changeNames[r.selfIndex];
@ -417,7 +416,7 @@ export class ChangeDetectorJITGenerator {
var lastInDirective = this.genLastInDirective(r); var lastInDirective = this.genLastInDirective(r);
var check = referenceCheckTemplate(r.selfIndex - 1, assignment, oldValue, newValue, change, var check = referenceCheckTemplate(r.selfIndex - 1, assignment, oldValue, newValue, change,
update, addToChanges, lastInDirective); update, addToChanges, lastInDirective);
if (r.isPureFunction()) { if (r.isPureFunction()) {
return this.ifChangedGuard(r, check); return this.ifChangedGuard(r, check);
} else { } else {
@ -425,7 +424,7 @@ export class ChangeDetectorJITGenerator {
} }
} }
genUpdateCurrentValue(r:ProtoRecord):string { genUpdateCurrentValue(r: ProtoRecord): string {
var context = this.getContext(r); var context = this.getContext(r);
var newValue = this.localNames[r.selfIndex]; var newValue = this.localNames[r.selfIndex];
var args = this.genArgs(r); var args = this.genArgs(r);
@ -464,7 +463,7 @@ export class ChangeDetectorJITGenerator {
} }
} }
getContext(r:ProtoRecord):string { getContext(r: ProtoRecord): string {
if (r.contextIndex == -1) { if (r.contextIndex == -1) {
return this.getDirective(r.directiveIndex); return this.getDirective(r.directiveIndex);
} else { } else {
@ -472,11 +471,11 @@ export class ChangeDetectorJITGenerator {
} }
} }
ifChangedGuard(r:ProtoRecord, body:string):string { ifChangedGuard(r: ProtoRecord, body: string): string {
return ifChangedGuardTemplate(r.args.map((a) => this.changeNames[a]), body); return ifChangedGuardTemplate(r.args.map((a) => this.changeNames[a]), body);
} }
genInterpolation(r:ProtoRecord):string{ genInterpolation(r: ProtoRecord): string {
var res = ""; var res = "";
for (var i = 0; i < r.args.length; ++i) { for (var i = 0; i < r.args.length; ++i) {
res += this.genLiteral(r.fixedArgs[i]); res += this.genLiteral(r.fixedArgs[i]);
@ -488,38 +487,37 @@ export class ChangeDetectorJITGenerator {
return res; return res;
} }
genLiteral(value):string { genLiteral(value): string { return JSON.stringify(value); }
return JSON.stringify(value);
}
genUpdateDirectiveOrElement(r:ProtoRecord):string { genUpdateDirectiveOrElement(r: ProtoRecord): string {
if (! r.lastInBinding) return ""; if (!r.lastInBinding) return "";
var newValue = this.localNames[r.selfIndex]; var newValue = this.localNames[r.selfIndex];
var oldValue = this.fieldNames[r.selfIndex]; var oldValue = this.fieldNames[r.selfIndex];
var br = r.bindingRecord; var br = r.bindingRecord;
if (br.isDirective()) { if (br.isDirective()) {
var directiveProperty = `${this.getDirective(br.directiveRecord.directiveIndex)}.${br.propertyName}`; var directiveProperty =
`${this.getDirective(br.directiveRecord.directiveIndex)}.${br.propertyName}`;
return updateDirectiveTemplate(oldValue, newValue, directiveProperty); return updateDirectiveTemplate(oldValue, newValue, directiveProperty);
} else { } else {
return updateElementTemplate(oldValue, newValue); return updateElementTemplate(oldValue, newValue);
} }
} }
genAddToChanges(r:ProtoRecord):string { genAddToChanges(r: ProtoRecord): string {
var newValue = this.localNames[r.selfIndex]; var newValue = this.localNames[r.selfIndex];
var oldValue = this.fieldNames[r.selfIndex]; var oldValue = this.fieldNames[r.selfIndex];
return r.bindingRecord.callOnChange() ? addToChangesTemplate(oldValue, newValue) : ""; return r.bindingRecord.callOnChange() ? addToChangesTemplate(oldValue, newValue) : "";
} }
genLastInDirective(r:ProtoRecord):string{ genLastInDirective(r: ProtoRecord): string {
var onChanges = this.genNotifyOnChanges(r); var onChanges = this.genNotifyOnChanges(r);
var onPush = this.genNotifyOnPushDetectors(r); var onPush = this.genNotifyOnPushDetectors(r);
return lastInDirectiveTemplate(onChanges, onPush); return lastInDirectiveTemplate(onChanges, onPush);
} }
genNotifyOnChanges(r:ProtoRecord):string{ genNotifyOnChanges(r: ProtoRecord): string {
var br = r.bindingRecord; var br = r.bindingRecord;
if (r.lastInDirective && br.callOnChange()) { if (r.lastInDirective && br.callOnChange()) {
return notifyOnChangesTemplate(this.getDirective(br.directiveRecord.directiveIndex)); return notifyOnChangesTemplate(this.getDirective(br.directiveRecord.directiveIndex));
@ -528,7 +526,7 @@ export class ChangeDetectorJITGenerator {
} }
} }
genNotifyOnPushDetectors(r:ProtoRecord):string{ genNotifyOnPushDetectors(r: ProtoRecord): string {
var br = r.bindingRecord; var br = r.bindingRecord;
if (r.lastInDirective && br.isOnPushChangeDetection()) { if (r.lastInDirective && br.isOnPushChangeDetection()) {
return notifyOnPushDetectorsTemplate(this.getDetector(br.directiveRecord.directiveIndex)); return notifyOnPushDetectorsTemplate(this.getDetector(br.directiveRecord.directiveIndex));
@ -537,11 +535,5 @@ export class ChangeDetectorJITGenerator {
} }
} }
genArgs(r:ProtoRecord):string { genArgs(r: ProtoRecord): string { return r.args.map((arg) => this.localNames[arg]).join(", "); }
return r.args.map((arg) => this.localNames[arg]).join(", ");
}
} }

View File

@ -1,139 +0,0 @@
import {isPresent, isBlank, BaseException, Type} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {ProtoRecord} from './proto_record';
import {ExpressionChangedAfterItHasBeenChecked} from './exceptions';
import {WrappedValue} from './pipes/pipe';
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants';
export var uninitialized = new Object();
export class SimpleChange {
previousValue:any;
currentValue:any;
constructor(previousValue:any, currentValue:any) {
this.previousValue = previousValue;
this.currentValue = currentValue;
}
}
var _simpleChangesIndex = 0;
var _simpleChanges = [
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null)
];
function _simpleChange(previousValue, currentValue) {
var index = _simpleChangesIndex++ % 20;
var s = _simpleChanges[index];
s.previousValue = previousValue;
s.currentValue = currentValue;
return s;
}
export class ChangeDetectionUtil {
static unitialized() {
return uninitialized;
}
static arrayFn0() { return []; }
static arrayFn1(a1) { return [a1]; }
static arrayFn2(a1, a2) { return [a1, a2]; }
static arrayFn3(a1, a2, a3) { return [a1, a2, a3]; }
static arrayFn4(a1, a2, a3, a4) { return [a1, a2, a3, a4]; }
static arrayFn5(a1, a2, a3, a4, a5) { return [a1, a2, a3, a4, a5]; }
static arrayFn6(a1, a2, a3, a4, a5, a6) { return [a1, a2, a3, a4, a5, a6]; }
static arrayFn7(a1, a2, a3, a4, a5, a6, a7) { return [a1, a2, a3, a4, a5, a6, a7]; }
static arrayFn8(a1, a2, a3, a4, a5, a6, a7, a8) { return [a1, a2, a3, a4, a5, a6, a7, a8]; }
static arrayFn9(a1, a2, a3, a4, a5, a6, a7, a8, a9) { return [a1, a2, a3, a4, a5, a6, a7, a8, a9]; }
static operation_negate(value) {return !value;}
static operation_add(left, right) {return left + right;}
static operation_subtract(left, right) {return left - right;}
static operation_multiply(left, right) {return left * right;}
static operation_divide(left, right) {return left / right;}
static operation_remainder(left, right) {return left % right;}
static operation_equals(left, right) {return left == right;}
static operation_not_equals(left, right) {return left != right;}
static operation_less_then(left, right) {return left < right;}
static operation_greater_then(left, right) {return left > right;}
static operation_less_or_equals_then(left, right) {return left <= right;}
static operation_greater_or_equals_then(left, right) {return left >= right;}
static operation_logical_and(left, right) {return left && right;}
static operation_logical_or(left, right) {return left || right;}
static cond(cond, trueVal, falseVal) {return cond ? trueVal : falseVal;}
static mapFn(keys:List) {
function buildMap(values) {
var res = StringMapWrapper.create();
for(var i = 0; i < keys.length; ++i) {
StringMapWrapper.set(res, keys[i], values[i]);
}
return res;
}
switch (keys.length) {
case 0: return () => [];
case 1: return (a1) => buildMap([a1]);
case 2: return (a1, a2) => buildMap([a1, a2]);
case 3: return (a1, a2, a3) => buildMap([a1, a2, a3]);
case 4: return (a1, a2, a3, a4) => buildMap([a1, a2, a3, a4]);
case 5: return (a1, a2, a3, a4, a5) => buildMap([a1, a2, a3, a4, a5]);
case 6: return (a1, a2, a3, a4, a5, a6) => buildMap([a1, a2, a3, a4, a5, a6]);
case 7: return (a1, a2, a3, a4, a5, a6, a7) => buildMap([a1, a2, a3, a4, a5, a6, a7]);
case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) => buildMap([a1, a2, a3, a4, a5, a6, a7, a8]);
case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => buildMap([a1, a2, a3, a4, a5, a6, a7, a8, a9]);
default: throw new BaseException(`Does not support literal maps with more than 9 elements`);
}
}
static keyedAccess(obj, args) {
return obj[args[0]];
}
static unwrapValue(value:any):any {
if (value instanceof WrappedValue) {
return value.wrapped;
} else {
return value;
}
}
static throwOnChange(proto:ProtoRecord, change) {
throw new ExpressionChangedAfterItHasBeenChecked(proto, change);
}
static changeDetectionMode(strategy:string) {
return strategy == ON_PUSH ? CHECK_ONCE : CHECK_ALWAYS;
}
static simpleChange(previousValue:any, currentValue:any):SimpleChange {
return _simpleChange(previousValue, currentValue);
}
static addChange(changes, propertyName:string, change){
if (isBlank(changes)) {
changes = {};
}
changes[propertyName] = change;
return changes;
}
}

View File

@ -0,0 +1,148 @@
import {isPresent, isBlank, BaseException, Type} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {ProtoRecord} from './proto_record';
import {ExpressionChangedAfterItHasBeenChecked} from './exceptions';
import {WrappedValue} from './pipes/pipe';
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
export var uninitialized = new Object();
export class SimpleChange {
constructor(public previousValue: any, public currentValue: any) {}
}
var _simpleChangesIndex = 0;
var _simpleChanges = [
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null),
new SimpleChange(null, null)
];
function _simpleChange(previousValue, currentValue) {
var index = _simpleChangesIndex++ % 20;
var s = _simpleChanges[index];
s.previousValue = previousValue;
s.currentValue = currentValue;
return s;
}
export class ChangeDetectionUtil {
static unitialized() { return uninitialized; }
static arrayFn0() { return []; }
static arrayFn1(a1) { return [a1]; }
static arrayFn2(a1, a2) { return [a1, a2]; }
static arrayFn3(a1, a2, a3) { return [a1, a2, a3]; }
static arrayFn4(a1, a2, a3, a4) { return [a1, a2, a3, a4]; }
static arrayFn5(a1, a2, a3, a4, a5) { return [a1, a2, a3, a4, a5]; }
static arrayFn6(a1, a2, a3, a4, a5, a6) { return [a1, a2, a3, a4, a5, a6]; }
static arrayFn7(a1, a2, a3, a4, a5, a6, a7) { return [a1, a2, a3, a4, a5, a6, a7]; }
static arrayFn8(a1, a2, a3, a4, a5, a6, a7, a8) { return [a1, a2, a3, a4, a5, a6, a7, a8]; }
static arrayFn9(a1, a2, a3, a4, a5, a6, a7, a8, a9) {
return [a1, a2, a3, a4, a5, a6, a7, a8, a9];
}
static operation_negate(value) { return !value; }
static operation_add(left, right) { return left + right; }
static operation_subtract(left, right) { return left - right; }
static operation_multiply(left, right) { return left * right; }
static operation_divide(left, right) { return left / right; }
static operation_remainder(left, right) { return left % right; }
static operation_equals(left, right) { return left == right; }
static operation_not_equals(left, right) { return left != right; }
static operation_less_then(left, right) { return left < right; }
static operation_greater_then(left, right) { return left > right; }
static operation_less_or_equals_then(left, right) { return left <= right; }
static operation_greater_or_equals_then(left, right) { return left >= right; }
static operation_logical_and(left, right) { return left && right; }
static operation_logical_or(left, right) { return left || right; }
static cond(cond, trueVal, falseVal) { return cond ? trueVal : falseVal; }
static mapFn(keys: List<any>) {
function buildMap(values) {
var res = StringMapWrapper.create();
for (var i = 0; i < keys.length; ++i) {
StringMapWrapper.set(res, keys[i], values[i]);
}
return res;
}
switch (keys.length) {
case 0:
return () => [];
case 1:
return (a1) => buildMap([a1]);
case 2:
return (a1, a2) => buildMap([a1, a2]);
case 3:
return (a1, a2, a3) => buildMap([a1, a2, a3]);
case 4:
return (a1, a2, a3, a4) => buildMap([a1, a2, a3, a4]);
case 5:
return (a1, a2, a3, a4, a5) => buildMap([a1, a2, a3, a4, a5]);
case 6:
return (a1, a2, a3, a4, a5, a6) => buildMap([a1, a2, a3, a4, a5, a6]);
case 7:
return (a1, a2, a3, a4, a5, a6, a7) => buildMap([a1, a2, a3, a4, a5, a6, a7]);
case 8:
return (a1, a2, a3, a4, a5, a6, a7, a8) => buildMap([a1, a2, a3, a4, a5, a6, a7, a8]);
case 9:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9) =>
buildMap([a1, a2, a3, a4, a5, a6, a7, a8, a9]);
default:
throw new BaseException(`Does not support literal maps with more than 9 elements`);
}
}
static keyedAccess(obj, args) { return obj[args[0]]; }
static unwrapValue(value: any): any {
if (value instanceof WrappedValue) {
return value.wrapped;
} else {
return value;
}
}
static throwOnChange(proto: ProtoRecord, change) {
throw new ExpressionChangedAfterItHasBeenChecked(proto, change);
}
static changeDetectionMode(strategy: string) {
return strategy == ON_PUSH ? CHECK_ONCE : CHECK_ALWAYS;
}
static simpleChange(previousValue: any, currentValue: any): SimpleChange {
return _simpleChange(previousValue, currentValue);
}
static addChange(changes, propertyName: string, change) {
if (isBlank(changes)) {
changes = {};
}
changes[propertyName] = change;
return changes;
}
}

View File

@ -1,41 +1,40 @@
import {ChangeDetector} from './interfaces'; import {ChangeDetector} from './interfaces';
import {CHECK_ONCE, DETACHED, CHECK_ALWAYS} from './constants'; import {CHECK_ONCE, DETACHED, CHECK_ALWAYS} from './constants';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
/** /**
* Controls change detection. * Controls change detection.
* *
* {@link ChangeDetectorRef} allows requesting checks for detectors that rely on observables. It also allows detaching and * {@link ChangeDetectorRef} allows requesting checks for detectors that rely on observables. It
*also allows detaching and
* attaching change detector subtrees. * attaching change detector subtrees.
* *
* @exportedAs angular2/change_detection * @exportedAs angular2/change_detection
*/ */
export class ChangeDetectorRef { export class ChangeDetectorRef {
_cd:ChangeDetector; constructor(private _cd: ChangeDetector) {}
constructor(cd:ChangeDetector) {
this._cd = cd;
}
/** /**
* Request to check all ON_PUSH ancestors. * Request to check all ON_PUSH ancestors.
*/ */
requestCheck() { requestCheck() { this._cd.markPathToRootAsCheckOnce(); }
this._cd.markPathToRootAsCheckOnce();
}
/** /**
* Detaches the change detector from the change detector tree. * Detaches the change detector from the change detector tree.
* *
* The detached change detector will not be checked until it is reattached. * The detached change detector will not be checked until it is reattached.
*/ */
detach() { detach() { this._cd.mode = DETACHED; }
this._cd.mode = DETACHED;
}
/** /**
* Reattach the change detector to the change detector tree. * Reattach the change detector to the change detector tree.
* *
* This also requests a check of this change detector. This reattached change detector will be checked during the * This also requests a check of this change detector. This reattached change detector will be
*checked during the
* next change detection run. * next change detection run.
*/ */
reattach() { reattach() {

View File

@ -2,6 +2,11 @@ import {isPresent} from 'angular2/src/facade/lang';
import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection'; import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection';
import {RECORD_TYPE_SELF, ProtoRecord} from './proto_record'; import {RECORD_TYPE_SELF, ProtoRecord} from './proto_record';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
/** /**
* Removes "duplicate" records. It assuming that record evaluation does not * Removes "duplicate" records. It assuming that record evaluation does not
* have side-effects. * have side-effects.
@ -12,7 +17,7 @@ import {RECORD_TYPE_SELF, ProtoRecord} from './proto_record';
* Records that are last in bindings CANNOT be removed, and instead are * Records that are last in bindings CANNOT be removed, and instead are
* replaced with very cheap SELF records. * replaced with very cheap SELF records.
*/ */
export function coalesce(records:List<ProtoRecord>):List<ProtoRecord> { export function coalesce(records: List<ProtoRecord>): List<ProtoRecord> {
var res = ListWrapper.create(); var res = ListWrapper.create();
var indexMap = MapWrapper.create(); var indexMap = MapWrapper.create();
@ -37,52 +42,27 @@ export function coalesce(records:List<ProtoRecord>):List<ProtoRecord> {
return res; return res;
} }
function _selfRecord(r:ProtoRecord, contextIndex:number, selfIndex:number):ProtoRecord { function _selfRecord(r: ProtoRecord, contextIndex: number, selfIndex: number): ProtoRecord {
return new ProtoRecord( return new ProtoRecord(RECORD_TYPE_SELF, "self", null, [], r.fixedArgs, contextIndex,
RECORD_TYPE_SELF, r.directiveIndex, selfIndex, r.bindingRecord, r.expressionAsString,
"self", r.lastInBinding, r.lastInDirective);
null,
[],
r.fixedArgs,
contextIndex,
r.directiveIndex,
selfIndex,
r.bindingRecord,
r.expressionAsString,
r.lastInBinding,
r.lastInDirective
);
} }
function _findMatching(r:ProtoRecord, rs:List<ProtoRecord>){ function _findMatching(r: ProtoRecord, rs: List<ProtoRecord>) {
return ListWrapper.find(rs, (rr) => return ListWrapper.find(rs, (rr) => rr.mode === r.mode && rr.funcOrValue === r.funcOrValue &&
rr.mode === r.mode && rr.contextIndex === r.contextIndex &&
rr.funcOrValue === r.funcOrValue && ListWrapper.equals(rr.args, r.args));
rr.contextIndex === r.contextIndex &&
ListWrapper.equals(rr.args, r.args)
);
} }
function _replaceIndices(r:ProtoRecord, selfIndex:number, indexMap:Map) { function _replaceIndices(r: ProtoRecord, selfIndex: number, indexMap: Map<any, any>) {
var args = ListWrapper.map(r.args, (a) => _map(indexMap, a)); var args = ListWrapper.map(r.args, (a) => _map(indexMap, a));
var contextIndex = _map(indexMap, r.contextIndex); var contextIndex = _map(indexMap, r.contextIndex);
return new ProtoRecord( return new ProtoRecord(r.mode, r.name, r.funcOrValue, args, r.fixedArgs, contextIndex,
r.mode, r.directiveIndex, selfIndex, r.bindingRecord, r.expressionAsString,
r.name, r.lastInBinding, r.lastInDirective);
r.funcOrValue,
args,
r.fixedArgs,
contextIndex,
r.directiveIndex,
selfIndex,
r.bindingRecord,
r.expressionAsString,
r.lastInBinding,
r.lastInDirective
);
} }
function _map(indexMap:Map, value:number) { function _map(indexMap: Map<any, any>, value: number) {
var r = MapWrapper.get(indexMap, value) var r = MapWrapper.get(indexMap, value);
return isPresent(r) ? r : value; return isPresent(r) ? r : value;
} }

View File

@ -1,28 +1,33 @@
//TODO:vsavkin Use enums after switching to TypeScript // TODO:vsavkin Use enums after switching to TypeScript
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
/** /**
* CHECK_ONCE means that after calling detectChanges the mode of the change detector * CHECK_ONCE means that after calling detectChanges the mode of the change detector
* will become CHECKED. * will become CHECKED.
*/ */
export const CHECK_ONCE="CHECK_ONCE"; export const CHECK_ONCE = "CHECK_ONCE";
/** /**
* CHECKED means that the change detector should be skipped until its mode changes to * CHECKED means that the change detector should be skipped until its mode changes to
* CHECK_ONCE or CHECK_ALWAYS. * CHECK_ONCE or CHECK_ALWAYS.
*/ */
export const CHECKED="CHECKED"; export const CHECKED = "CHECKED";
/** /**
* CHECK_ALWAYS means that after calling detectChanges the mode of the change detector * CHECK_ALWAYS means that after calling detectChanges the mode of the change detector
* will remain CHECK_ALWAYS. * will remain CHECK_ALWAYS.
*/ */
export const CHECK_ALWAYS="ALWAYS_CHECK"; export const CHECK_ALWAYS = "ALWAYS_CHECK";
/** /**
* DETACHED means that the change detector sub tree is not a part of the main tree and * DETACHED means that the change detector sub tree is not a part of the main tree and
* should be skipped. * should be skipped.
*/ */
export const DETACHED="DETACHED"; export const DETACHED = "DETACHED";
/** /**
* ON_PUSH means that the change detector's mode will be set to CHECK_ONCE during hydration. * ON_PUSH means that the change detector's mode will be set to CHECK_ONCE during hydration.

View File

@ -1,34 +0,0 @@
import {ON_PUSH} from './constants';
import {StringWrapper} from 'angular2/src/facade/lang';
export class DirectiveIndex {
elementIndex:number;
directiveIndex:number;
constructor(elementIndex:number, directiveIndex:number) {
this.elementIndex = elementIndex;
this.directiveIndex = directiveIndex;
}
get name() {
return `${this.elementIndex}_${this.directiveIndex}`;
}
}
export class DirectiveRecord {
directiveIndex:DirectiveIndex;
callOnAllChangesDone:boolean;
callOnChange:boolean;
changeDetection:string;
constructor(directiveIndex:DirectiveIndex, callOnAllChangesDone:boolean, callOnChange:boolean, changeDetection:string) {
this.directiveIndex = directiveIndex;
this.callOnAllChangesDone = callOnAllChangesDone;
this.callOnChange = callOnChange;
this.changeDetection = changeDetection;
}
isOnPushChangeDetection():boolean {
return StringWrapper.equals(this.changeDetection, ON_PUSH);
}
}

View File

@ -0,0 +1,20 @@
import {ON_PUSH} from './constants';
import {StringWrapper} from 'angular2/src/facade/lang';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
export class DirectiveIndex {
constructor(public elementIndex: number, public directiveIndex: number) {}
get name() { return `${this.elementIndex}_${this.directiveIndex}`; }
}
export class DirectiveRecord {
constructor(public directiveIndex: DirectiveIndex, public callOnAllChangesDone: boolean,
public callOnChange: boolean, public changeDetection: string) {}
isOnPushChangeDetection(): boolean { return StringWrapper.equals(this.changeDetection, ON_PUSH); }
}

View File

@ -4,7 +4,7 @@ import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/faca
import {AbstractChangeDetector} from './abstract_change_detector'; import {AbstractChangeDetector} from './abstract_change_detector';
import {BindingRecord} from './binding_record'; import {BindingRecord} from './binding_record';
import {PipeRegistry} from './pipes/pipe_registry'; import {PipeRegistry} from './pipes/pipe_registry';
import {ChangeDetectionUtil, uninitialized} from './change_detection_util'; import {ChangeDetectionUtil, SimpleChange, uninitialized} from './change_detection_util';
import { import {
@ -20,35 +20,31 @@ import {
RECORD_TYPE_PIPE, RECORD_TYPE_PIPE,
RECORD_TYPE_BINDING_PIPE, RECORD_TYPE_BINDING_PIPE,
RECORD_TYPE_INTERPOLATE RECORD_TYPE_INTERPOLATE
} from './proto_record'; } from './proto_record';
import {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './exceptions'; import {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './exceptions';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
export class DynamicChangeDetector extends AbstractChangeDetector { export class DynamicChangeDetector extends AbstractChangeDetector {
dispatcher:any; locals: any;
pipeRegistry; values: List<any>;
changes: List<any>;
pipes: List<any>;
prevContexts: List<any>;
directives: any;
locals:any; constructor(private changeControlStrategy: string, private dispatcher: any,
values:List; private pipeRegistry: PipeRegistry, private protos: List<ProtoRecord>,
changes:List; private directiveRecords: List<any>) {
pipes:List;
prevContexts:List;
protos:List<ProtoRecord>;
directives:any;
directiveRecords:List;
changeControlStrategy:string;
constructor(changeControlStrategy:string, dispatcher:any, pipeRegistry:PipeRegistry,
protoRecords:List<ProtoRecord>, directiveRecords:List) {
super(); super();
this.dispatcher = dispatcher; this.values = ListWrapper.createFixedSize(protos.length + 1);
this.pipeRegistry = pipeRegistry; this.pipes = ListWrapper.createFixedSize(protos.length + 1);
this.prevContexts = ListWrapper.createFixedSize(protos.length + 1);
this.values = ListWrapper.createFixedSize(protoRecords.length + 1); this.changes = ListWrapper.createFixedSize(protos.length + 1);
this.pipes = ListWrapper.createFixedSize(protoRecords.length + 1);
this.prevContexts = ListWrapper.createFixedSize(protoRecords.length + 1);
this.changes = ListWrapper.createFixedSize(protoRecords.length + 1);
ListWrapper.fill(this.values, uninitialized); ListWrapper.fill(this.values, uninitialized);
ListWrapper.fill(this.pipes, null); ListWrapper.fill(this.pipes, null);
@ -56,13 +52,9 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
ListWrapper.fill(this.changes, false); ListWrapper.fill(this.changes, false);
this.locals = null; this.locals = null;
this.directives = null; this.directives = null;
this.protos = protoRecords;
this.directiveRecords = directiveRecords;
this.changeControlStrategy = changeControlStrategy;
} }
hydrate(context:any, locals:any, directives:any) { hydrate(context: any, locals: any, directives: any) {
this.mode = ChangeDetectionUtil.changeDetectionMode(this.changeControlStrategy); this.mode = ChangeDetectionUtil.changeDetectionMode(this.changeControlStrategy);
this.values[0] = context; this.values[0] = context;
this.locals = locals; this.locals = locals;
@ -79,24 +71,22 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
} }
_destroyPipes() { _destroyPipes() {
for(var i = 0; i < this.pipes.length; ++i) { for (var i = 0; i < this.pipes.length; ++i) {
if (isPresent(this.pipes[i])) { if (isPresent(this.pipes[i])) {
this.pipes[i].onDestroy(); this.pipes[i].onDestroy();
} }
} }
} }
hydrated():boolean { hydrated(): boolean { return this.values[0] !== uninitialized; }
return this.values[0] !== uninitialized;
}
detectChangesInRecords(throwOnChange:boolean) { detectChangesInRecords(throwOnChange: boolean) {
var protos:List<ProtoRecord> = this.protos; var protos: List < ProtoRecord >= this.protos;
var changes = null; var changes = null;
var isChanged = false; var isChanged = false;
for (var i = 0; i < protos.length; ++i) { for (var i = 0; i < protos.length; ++i) {
var proto:ProtoRecord = protos[i]; var proto: ProtoRecord = protos[i];
var bindingRecord = proto.bindingRecord; var bindingRecord = proto.bindingRecord;
var directiveRecord = bindingRecord.directiveRecord; var directiveRecord = bindingRecord.directiveRecord;
@ -142,7 +132,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
} }
} }
_addChange(bindingRecord:BindingRecord, change, changes) { _addChange(bindingRecord: BindingRecord, change, changes) {
if (bindingRecord.callOnChange()) { if (bindingRecord.callOnChange()) {
return ChangeDetectionUtil.addChange(changes, bindingRecord.propertyName, change); return ChangeDetectionUtil.addChange(changes, bindingRecord.propertyName, change);
} else { } else {
@ -150,15 +140,11 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
} }
} }
_getDirectiveFor(directiveIndex) { _getDirectiveFor(directiveIndex) { return this.directives.getDirectiveFor(directiveIndex); }
return this.directives.getDirectiveFor(directiveIndex);
}
_getDetectorFor(directiveIndex) { _getDetectorFor(directiveIndex) { return this.directives.getDetectorFor(directiveIndex); }
return this.directives.getDetectorFor(directiveIndex);
}
_check(proto:ProtoRecord) { _check(proto: ProtoRecord): SimpleChange {
try { try {
if (proto.mode === RECORD_TYPE_PIPE || proto.mode === RECORD_TYPE_BINDING_PIPE) { if (proto.mode === RECORD_TYPE_PIPE || proto.mode === RECORD_TYPE_BINDING_PIPE) {
return this._pipeCheck(proto); return this._pipeCheck(proto);
@ -170,7 +156,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
} }
} }
_referenceCheck(proto:ProtoRecord) { _referenceCheck(proto: ProtoRecord) {
if (this._pureFuncAndArgsDidNotChange(proto)) { if (this._pureFuncAndArgsDidNotChange(proto)) {
this._setChanged(proto, false); this._setChanged(proto, false);
return null; return null;
@ -194,7 +180,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
} }
} }
_calculateCurrValue(proto:ProtoRecord) { _calculateCurrValue(proto: ProtoRecord) {
switch (proto.mode) { switch (proto.mode) {
case RECORD_TYPE_SELF: case RECORD_TYPE_SELF:
return this._readContext(proto); return this._readContext(proto);
@ -230,7 +216,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
} }
} }
_pipeCheck(proto:ProtoRecord) { _pipeCheck(proto: ProtoRecord) {
var context = this._readContext(proto); var context = this._readContext(proto);
var pipe = this._pipeFor(proto, context); var pipe = this._pipeFor(proto, context);
var prevValue = this._readSelf(proto); var prevValue = this._readSelf(proto);
@ -254,7 +240,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
} }
} }
_pipeFor(proto:ProtoRecord, context) { _pipeFor(proto: ProtoRecord, context) {
var storedPipe = this._readPipe(proto); var storedPipe = this._readPipe(proto);
if (isPresent(storedPipe) && storedPipe.supports(context)) { if (isPresent(storedPipe) && storedPipe.supports(context)) {
return storedPipe; return storedPipe;
@ -274,7 +260,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
return pipe; return pipe;
} }
_readContext(proto:ProtoRecord) { _readContext(proto: ProtoRecord) {
if (proto.contextIndex == -1) { if (proto.contextIndex == -1) {
return this._getDirectiveFor(proto.directiveIndex); return this._getDirectiveFor(proto.directiveIndex);
} else { } else {
@ -284,33 +270,23 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
return this.values[proto.contextIndex]; return this.values[proto.contextIndex];
} }
_readSelf(proto:ProtoRecord) { _readSelf(proto: ProtoRecord) { return this.values[proto.selfIndex]; }
return this.values[proto.selfIndex];
}
_writeSelf(proto:ProtoRecord, value) { _writeSelf(proto: ProtoRecord, value) { this.values[proto.selfIndex] = value; }
this.values[proto.selfIndex] = value;
}
_readPipe(proto:ProtoRecord) { _readPipe(proto: ProtoRecord) { return this.pipes[proto.selfIndex]; }
return this.pipes[proto.selfIndex];
}
_writePipe(proto:ProtoRecord, value) { _writePipe(proto: ProtoRecord, value) { this.pipes[proto.selfIndex] = value; }
this.pipes[proto.selfIndex] = value;
}
_setChanged(proto:ProtoRecord, value:boolean) { _setChanged(proto: ProtoRecord, value: boolean) { this.changes[proto.selfIndex] = value; }
this.changes[proto.selfIndex] = value;
}
_pureFuncAndArgsDidNotChange(proto:ProtoRecord):boolean { _pureFuncAndArgsDidNotChange(proto: ProtoRecord): boolean {
return proto.isPureFunction() && !this._argsChanged(proto); return proto.isPureFunction() && !this._argsChanged(proto);
} }
_argsChanged(proto:ProtoRecord):boolean { _argsChanged(proto: ProtoRecord): boolean {
var args = proto.args; var args = proto.args;
for(var i = 0; i < args.length; ++i) { for (var i = 0; i < args.length; ++i) {
if (this.changes[args[i]]) { if (this.changes[args[i]]) {
return true; return true;
} }
@ -318,7 +294,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
return false; return false;
} }
_readArgs(proto:ProtoRecord) { _readArgs(proto: ProtoRecord) {
var res = ListWrapper.createFixedSize(proto.args.length); var res = ListWrapper.createFixedSize(proto.args.length);
var args = proto.args; var args = proto.args;
for (var i = 0; i < args.length; ++i) { for (var i = 0; i < args.length; ++i) {
@ -334,6 +310,3 @@ function isSame(a, b) {
if ((a !== a) && (b !== b)) return true; if ((a !== a) && (b !== b)) return true;
return false; return false;
} }

View File

@ -1,33 +0,0 @@
import {ProtoRecord} from './proto_record';
import {BaseException} from "angular2/src/facade/lang";
export class ExpressionChangedAfterItHasBeenChecked extends BaseException {
message:string;
constructor(proto:ProtoRecord, change:any) {
super();
this.message = `Expression '${proto.expressionAsString}' has changed after it was checked. ` +
`Previous value: '${change.previousValue}'. Current value: '${change.currentValue}'`;
}
toString():string {
return this.message;
}
}
export class ChangeDetectionError extends BaseException {
message:string;
originalException:any;
location:string;
constructor(proto:ProtoRecord, originalException:any) {
super();
this.originalException = originalException;
this.location = proto.expressionAsString;
this.message = `${this.originalException} in [${this.location}]`;
}
toString():string {
return this.message;
}
}

View File

@ -0,0 +1,36 @@
import {ProtoRecord} from './proto_record';
import {BaseException} from "angular2/src/facade/lang";
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
export class ExpressionChangedAfterItHasBeenChecked extends BaseException {
message: string;
constructor(proto: ProtoRecord, change: any) {
super();
this.message =
`Expression '${proto.expressionAsString}' has changed after it was checked. ` +
`Previous value: '${change.previousValue}'. Current value: '${change.currentValue}'`;
}
toString(): string { return this.message; }
}
export class ChangeDetectionError extends BaseException {
message: string;
originalException: any;
location: string;
constructor(proto: ProtoRecord, originalException: any) {
super();
this.originalException = originalException;
this.location = proto.expressionAsString;
this.message = `${this.originalException} in [${this.location}]`;
}
toString(): string { return this.message; }
}

View File

@ -3,10 +3,13 @@ import {Locals} from './parser/locals';
import {DEFAULT} from './constants'; import {DEFAULT} from './constants';
import {BindingRecord} from './binding_record'; import {BindingRecord} from './binding_record';
export class ProtoChangeDetector { // HACK: workaround for Traceur behavior.
instantiate(dispatcher:any):ChangeDetector{ // It expects all transpiled modules to contain this marker.
return null; // TODO: remove this when we no longer use traceur
} export var __esModule = true;
export class ProtoChangeDetector {
instantiate(dispatcher: any): ChangeDetector { return null; }
} }
/** /**
@ -17,10 +20,12 @@ export class ProtoChangeDetector {
* - {@link DynamicChangeDetection}: slower, but does not require `eval()`. * - {@link DynamicChangeDetection}: slower, but does not require `eval()`.
* - {@link JitChangeDetection}: faster, but requires `eval()`. * - {@link JitChangeDetection}: faster, but requires `eval()`.
* *
* In JavaScript, you should always use `JitChangeDetection`, unless you are in an environment that has * In JavaScript, you should always use `JitChangeDetection`, unless you are in an environment that
*has
* [CSP](https://developer.mozilla.org/en-US/docs/Web/Security/CSP), such as a Chrome Extension. * [CSP](https://developer.mozilla.org/en-US/docs/Web/Security/CSP), such as a Chrome Extension.
* *
* In Dart, use `DynamicChangeDetection` during development. The Angular transformer generates an analog to the * In Dart, use `DynamicChangeDetection` during development. The Angular transformer generates an
*analog to the
* `JitChangeDetection` strategy at compile time. * `JitChangeDetection` strategy at compile time.
* *
* *
@ -33,26 +38,27 @@ export class ProtoChangeDetector {
* @exportedAs angular2/change_detection * @exportedAs angular2/change_detection
*/ */
export class ChangeDetection { export class ChangeDetection {
createProtoChangeDetector(name:string, bindingRecords:List, variableBindings:List, directiveRecords:List, createProtoChangeDetector(name: string, bindingRecords: List<any>, variableBindings: List<any>,
changeControlStrategy:string=DEFAULT):ProtoChangeDetector{ directiveRecords: List<any>,
changeControlStrategy: string = DEFAULT): ProtoChangeDetector {
return null; return null;
} }
} }
export class ChangeDispatcher { export class ChangeDispatcher {
notifyOnBinding(bindingRecord:BindingRecord, value:any) {} notifyOnBinding(bindingRecord: BindingRecord, value: any) {}
} }
export class ChangeDetector { export class ChangeDetector {
parent:ChangeDetector; parent: ChangeDetector;
mode:string; mode: string;
addChild(cd:ChangeDetector) {} addChild(cd: ChangeDetector) {}
addShadowDomChild(cd:ChangeDetector) {} addShadowDomChild(cd: ChangeDetector) {}
removeChild(cd:ChangeDetector) {} removeChild(cd: ChangeDetector) {}
removeShadowDomChild(cd:ChangeDetector) {} removeShadowDomChild(cd: ChangeDetector) {}
remove() {} remove() {}
hydrate(context:any, locals:Locals, directives:any) {} hydrate(context: any, locals: Locals, directives: any) {}
dehydrate() {} dehydrate() {}
markPathToRootAsCheckOnce() {} markPathToRootAsCheckOnce() {}

View File

@ -1,532 +0,0 @@
import {autoConvertAdd, isBlank, isPresent, FunctionWrapper, BaseException} from "angular2/src/facade/lang";
import {List, Map, ListWrapper, StringMapWrapper} from "angular2/src/facade/collection";
export class AST {
eval(context, locals) {
throw new BaseException("Not supported");
}
get isAssignable():boolean {
return false;
}
assign(context, locals, value) {
throw new BaseException("Not supported");
}
visit(visitor) {
}
toString():string {
return "AST";
}
}
export class EmptyExpr extends AST {
eval(context, locals) {
return null;
}
visit(visitor) {
//do nothing
}
}
export class ImplicitReceiver extends AST {
eval(context, locals) {
return context;
}
visit(visitor) {
return visitor.visitImplicitReceiver(this);
}
}
/**
* Multiple expressions separated by a semicolon.
*/
export class Chain extends AST {
expressions:List;
constructor(expressions:List) {
super();
this.expressions = expressions;
}
eval(context, locals) {
var result;
for (var i = 0; i < this.expressions.length; i++) {
var last = this.expressions[i].eval(context, locals);
if (isPresent(last)) result = last;
}
return result;
}
visit(visitor) {
return visitor.visitChain(this);
}
}
export class Conditional extends AST {
condition:AST;
trueExp:AST;
falseExp:AST;
constructor(condition:AST, trueExp:AST, falseExp:AST){
super();
this.condition = condition;
this.trueExp = trueExp;
this.falseExp = falseExp;
}
eval(context, locals) {
if(this.condition.eval(context, locals)) {
return this.trueExp.eval(context, locals);
} else {
return this.falseExp.eval(context, locals);
}
}
visit(visitor) {
return visitor.visitConditional(this);
}
}
export class AccessMember extends AST {
receiver:AST;
name:string;
getter:Function;
setter:Function;
constructor(receiver:AST, name:string, getter:Function, setter:Function) {
super();
this.receiver = receiver;
this.name = name;
this.getter = getter;
this.setter = setter;
}
eval(context, locals) {
if (this.receiver instanceof ImplicitReceiver &&
isPresent(locals) && locals.contains(this.name)) {
return locals.get(this.name);
} else {
var evaluatedReceiver = this.receiver.eval(context, locals);
return this.getter(evaluatedReceiver);
}
}
get isAssignable():boolean {
return true;
}
assign(context, locals, value) {
var evaluatedContext = this.receiver.eval(context, locals);
if (this.receiver instanceof ImplicitReceiver &&
isPresent(locals) && locals.contains(this.name)) {
throw new BaseException(`Cannot reassign a variable binding ${this.name}`);
} else {
return this.setter(evaluatedContext, value);
}
}
visit(visitor) {
return visitor.visitAccessMember(this);
}
}
export class KeyedAccess extends AST {
obj:AST;
key:AST;
constructor(obj:AST, key:AST) {
super();
this.obj = obj;
this.key = key;
}
eval(context, locals) {
var obj = this.obj.eval(context, locals);
var key = this.key.eval(context, locals);
return obj[key];
}
get isAssignable():boolean {
return true;
}
assign(context, locals, value) {
var obj = this.obj.eval(context, locals);
var key = this.key.eval(context, locals);
obj[key] = value;
return value;
}
visit(visitor) {
return visitor.visitKeyedAccess(this);
}
}
export class Pipe extends AST {
exp:AST;
name:string;
args:List<AST>;
inBinding:boolean;
constructor(exp:AST, name:string, args:List, inBinding:boolean) {
super();
this.exp = exp;
this.name = name;
this.args = args;
this.inBinding = inBinding;
}
visit(visitor) {
return visitor.visitPipe(this);
}
}
export class LiteralPrimitive extends AST {
value;
constructor(value) {
super();
this.value = value;
}
eval(context, locals) {
return this.value;
}
visit(visitor) {
return visitor.visitLiteralPrimitive(this);
}
}
export class LiteralArray extends AST {
expressions:List;
constructor(expressions:List) {
super();
this.expressions = expressions;
}
eval(context, locals) {
return ListWrapper.map(this.expressions, (e) => e.eval(context, locals));
}
visit(visitor) {
return visitor.visitLiteralArray(this);
}
}
export class LiteralMap extends AST {
keys:List;
values:List;
constructor(keys:List, values:List) {
super();
this.keys = keys;
this.values = values;
}
eval(context, locals) {
var res = StringMapWrapper.create();
for(var i = 0; i < this.keys.length; ++i) {
StringMapWrapper.set(res, this.keys[i], this.values[i].eval(context, locals));
}
return res;
}
visit(visitor) {
return visitor.visitLiteralMap(this);
}
}
export class Interpolation extends AST {
strings:List;
expressions:List;
constructor(strings:List, expressions:List) {
super();
this.strings = strings;
this.expressions = expressions;
}
eval(context, locals) {
throw new BaseException("evaluating an Interpolation is not supported");
}
visit(visitor) {
visitor.visitInterpolation(this);
}
}
export class Binary extends AST {
operation:string;
left:AST;
right:AST;
constructor(operation:string, left:AST, right:AST) {
super();
this.operation = operation;
this.left = left;
this.right = right;
}
eval(context, locals) {
var left = this.left.eval(context, locals);
switch (this.operation) {
case '&&': return left && this.right.eval(context, locals);
case '||': return left || this.right.eval(context, locals);
}
var right = this.right.eval(context, locals);
switch (this.operation) {
case '+' : return left + right;
case '-' : return left - right;
case '*' : return left * right;
case '/' : return left / right;
case '%' : return left % right;
case '==' : return left == right;
case '!=' : return left != right;
case '===' : return left === right;
case '!==' : return left !== right;
case '<' : return left < right;
case '>' : return left > right;
case '<=' : return left <= right;
case '>=' : return left >= right;
case '^' : return left ^ right;
case '&' : return left & right;
}
throw 'Internal error [$operation] not handled';
}
visit(visitor) {
return visitor.visitBinary(this);
}
}
export class PrefixNot extends AST {
expression:AST;
constructor(expression:AST) {
super();
this.expression = expression;
}
eval(context, locals) {
return !this.expression.eval(context, locals);
}
visit(visitor) {
return visitor.visitPrefixNot(this);
}
}
export class Assignment extends AST {
target:AST;
value:AST;
constructor(target:AST, value:AST) {
super();
this.target = target;
this.value = value;
}
eval(context, locals) {
return this.target.assign(context, locals, this.value.eval(context, locals));
}
visit(visitor) {
return visitor.visitAssignment(this);
}
}
export class MethodCall extends AST {
receiver:AST;
fn:Function;
args:List;
name:string;
constructor(receiver:AST, name:string, fn:Function, args:List) {
super();
this.receiver = receiver;
this.fn = fn;
this.args = args;
this.name = name;
}
eval(context, locals) {
var evaluatedArgs = evalList(context, locals, this.args);
if (this.receiver instanceof ImplicitReceiver &&
isPresent(locals) && locals.contains(this.name)) {
var fn = locals.get(this.name);
return FunctionWrapper.apply(fn, evaluatedArgs);
} else {
var evaluatedReceiver = this.receiver.eval(context, locals);
return this.fn(evaluatedReceiver, evaluatedArgs);
}
}
visit(visitor) {
return visitor.visitMethodCall(this);
}
}
export class FunctionCall extends AST {
target:AST;
args:List;
constructor(target:AST, args:List) {
super();
this.target = target;
this.args = args;
}
eval(context, locals) {
var obj = this.target.eval(context, locals);
if (! (obj instanceof Function)) {
throw new BaseException(`${obj} is not a function`);
}
return FunctionWrapper.apply(obj, evalList(context, locals, this.args));
}
visit(visitor) {
return visitor.visitFunctionCall(this);
}
}
export class ASTWithSource extends AST {
ast:AST;
source:string;
location:string;
constructor(ast:AST, source:string, location:string) {
super();
this.source = source;
this.location = location;
this.ast = ast;
}
eval(context, locals) {
return this.ast.eval(context, locals);
}
get isAssignable():boolean {
return this.ast.isAssignable;
}
assign(context, locals, value) {
return this.ast.assign(context, locals, value);
}
visit(visitor) {
return this.ast.visit(visitor);
}
toString():string {
return `${this.source} in ${this.location}`;
}
}
export class TemplateBinding {
key:string;
keyIsVar:boolean;
name:string;
expression:ASTWithSource;
constructor(key:string, keyIsVar:boolean, name:string, expression:ASTWithSource) {
this.key = key;
this.keyIsVar = keyIsVar;
// only either name or expression will be filled.
this.name = name;
this.expression = expression;
}
}
//INTERFACE
export class AstVisitor {
visitAccessMember(ast:AccessMember) {}
visitAssignment(ast:Assignment) {}
visitBinary(ast:Binary) {}
visitChain(ast:Chain){}
visitConditional(ast:Conditional) {}
visitPipe(ast:Pipe) {}
visitFunctionCall(ast:FunctionCall) {}
visitImplicitReceiver(ast:ImplicitReceiver) {}
visitKeyedAccess(ast:KeyedAccess) {}
visitLiteralArray(ast:LiteralArray) {}
visitLiteralMap(ast:LiteralMap) {}
visitLiteralPrimitive(ast:LiteralPrimitive) {}
visitMethodCall(ast:MethodCall) {}
visitPrefixNot(ast:PrefixNot) {}
}
export class AstTransformer {
visitImplicitReceiver(ast:ImplicitReceiver) {
return ast;
}
visitInterpolation(ast:Interpolation) {
return new Interpolation(ast.strings, this.visitAll(ast.expressions));
}
visitLiteralPrimitive(ast:LiteralPrimitive) {
return new LiteralPrimitive(ast.value);
}
visitAccessMember(ast:AccessMember) {
return new AccessMember(ast.receiver.visit(this), ast.name, ast.getter, ast.setter);
}
visitMethodCall(ast:MethodCall) {
return new MethodCall(ast.receiver.visit(this), ast.name, ast.fn, this.visitAll(ast.args));
}
visitFunctionCall(ast:FunctionCall) {
return new FunctionCall(ast.target.visit(this), this.visitAll(ast.args));
}
visitLiteralArray(ast:LiteralArray) {
return new LiteralArray(this.visitAll(ast.expressions));
}
visitLiteralMap(ast:LiteralMap) {
return new LiteralMap(ast.keys, this.visitAll(ast.values));
}
visitBinary(ast:Binary) {
return new Binary(ast.operation, ast.left.visit(this), ast.right.visit(this));
}
visitPrefixNot(ast:PrefixNot) {
return new PrefixNot(ast.expression.visit(this));
}
visitConditional(ast:Conditional) {
return new Conditional(
ast.condition.visit(this),
ast.trueExp.visit(this),
ast.falseExp.visit(this)
);
}
visitPipe(ast:Pipe) {
return new Pipe(ast.exp.visit(this), ast.name, this.visitAll(ast.args), ast.inBinding);
}
visitKeyedAccess(ast:KeyedAccess) {
return new KeyedAccess(ast.obj.visit(this), ast.key.visit(this));
}
visitAll(asts:List) {
var res = ListWrapper.createFixedSize(asts.length);
for (var i = 0; i < asts.length; ++i) {
res[i] = asts[i].visit(this);
}
return res;
}
}
var _evalListCache = [[],[0],[0,0],[0,0,0],[0,0,0,0],[0,0,0,0,0],
[0,0,0,0,0,0], [0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0]];
function evalList(context, locals, exps:List){
var length = exps.length;
if (length > 10) {
throw new BaseException("Cannot have more than 10 argument");
}
var result = _evalListCache[length];
for (var i = 0; i < length; i++) {
result[i] = exps[i].eval(context, locals);
}
return result;
}

View File

@ -0,0 +1,388 @@
import {isBlank, isPresent, FunctionWrapper, BaseException} from "angular2/src/facade/lang";
import {List, Map, ListWrapper, StringMapWrapper} from "angular2/src/facade/collection";
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
export class AST {
eval(context, locals) { throw new BaseException("Not supported"); }
get isAssignable(): boolean { return false; }
assign(context, locals, value) { throw new BaseException("Not supported"); }
visit(visitor): any { return null; }
toString(): string { return "AST"; }
}
export class EmptyExpr extends AST {
eval(context, locals) { return null; }
visit(visitor) {
// do nothing
}
}
export class ImplicitReceiver extends AST {
eval(context, locals) { return context; }
visit(visitor) { return visitor.visitImplicitReceiver(this); }
}
/**
* Multiple expressions separated by a semicolon.
*/
export class Chain extends AST {
constructor(public expressions: List<any>) { super(); }
eval(context, locals) {
var result;
for (var i = 0; i < this.expressions.length; i++) {
var last = this.expressions[i].eval(context, locals);
if (isPresent(last)) result = last;
}
return result;
}
visit(visitor) { return visitor.visitChain(this); }
}
export class Conditional extends AST {
constructor(public condition: AST, public trueExp: AST, public falseExp: AST) { super(); }
eval(context, locals) {
if (this.condition.eval(context, locals)) {
return this.trueExp.eval(context, locals);
} else {
return this.falseExp.eval(context, locals);
}
}
visit(visitor) { return visitor.visitConditional(this); }
}
export class AccessMember extends AST {
constructor(public receiver: AST, public name: string, public getter: Function,
public setter: Function) {
super();
}
eval(context, locals) {
if (this.receiver instanceof ImplicitReceiver && isPresent(locals) &&
locals.contains(this.name)) {
return locals.get(this.name);
} else {
var evaluatedReceiver = this.receiver.eval(context, locals);
return this.getter(evaluatedReceiver);
}
}
get isAssignable(): boolean { return true; }
assign(context, locals, value) {
var evaluatedContext = this.receiver.eval(context, locals);
if (this.receiver instanceof ImplicitReceiver && isPresent(locals) &&
locals.contains(this.name)) {
throw new BaseException(`Cannot reassign a variable binding ${this.name}`);
} else {
return this.setter(evaluatedContext, value);
}
}
visit(visitor) { return visitor.visitAccessMember(this); }
}
export class KeyedAccess extends AST {
constructor(public obj: AST, public key: AST) { super(); }
eval(context, locals) {
var obj: any = this.obj.eval(context, locals);
var key: any = this.key.eval(context, locals);
return obj[key];
}
get isAssignable(): boolean { return true; }
assign(context, locals, value) {
var obj: any = this.obj.eval(context, locals);
var key: any = this.key.eval(context, locals);
obj[key] = value;
return value;
}
visit(visitor) { return visitor.visitKeyedAccess(this); }
}
export class Pipe extends AST {
constructor(public exp: AST, public name: string, public args: List<any>,
public inBinding: boolean) {
super();
}
visit(visitor) { return visitor.visitPipe(this); }
}
export class LiteralPrimitive extends AST {
constructor(public value) { super(); }
eval(context, locals) { return this.value; }
visit(visitor) { return visitor.visitLiteralPrimitive(this); }
}
export class LiteralArray extends AST {
constructor(public expressions: List<any>) { super(); }
eval(context, locals) {
return ListWrapper.map(this.expressions, (e) => e.eval(context, locals));
}
visit(visitor) { return visitor.visitLiteralArray(this); }
}
export class LiteralMap extends AST {
constructor(public keys: List<any>, public values: List<any>) { super(); }
eval(context, locals) {
var res = StringMapWrapper.create();
for (var i = 0; i < this.keys.length; ++i) {
StringMapWrapper.set(res, this.keys[i], this.values[i].eval(context, locals));
}
return res;
}
visit(visitor) { return visitor.visitLiteralMap(this); }
}
export class Interpolation extends AST {
constructor(public strings: List<any>, public expressions: List<any>) { super(); }
eval(context, locals) { throw new BaseException("evaluating an Interpolation is not supported"); }
visit(visitor) { visitor.visitInterpolation(this); }
}
export class Binary extends AST {
constructor(public operation: string, public left: AST, public right: AST) { super(); }
eval(context, locals) {
var left: any = this.left.eval(context, locals);
switch (this.operation) {
case '&&':
return left && this.right.eval(context, locals);
case '||':
return left || this.right.eval(context, locals);
}
var right: any = this.right.eval(context, locals);
switch (this.operation) {
case '+':
return left + right;
case '-':
return left - right;
case '*':
return left * right;
case '/':
return left / right;
case '%':
return left % right;
case '==':
return left == right;
case '!=':
return left != right;
case '===':
return left === right;
case '!==':
return left !== right;
case '<':
return left < right;
case '>':
return left > right;
case '<=':
return left <= right;
case '>=':
return left >= right;
case '^':
return left ^ right;
case '&':
return left & right;
}
throw 'Internal error [$operation] not handled';
}
visit(visitor) { return visitor.visitBinary(this); }
}
export class PrefixNot extends AST {
constructor(public expression: AST) { super(); }
eval(context, locals) { return !this.expression.eval(context, locals); }
visit(visitor) { return visitor.visitPrefixNot(this); }
}
export class Assignment extends AST {
constructor(public target: AST, public value: AST) { super(); }
eval(context, locals) {
return this.target.assign(context, locals, this.value.eval(context, locals));
}
visit(visitor) { return visitor.visitAssignment(this); }
}
export class MethodCall extends AST {
constructor(public receiver: AST, public name: string, public fn: Function,
public args: List<any>) {
super();
}
eval(context, locals) {
var evaluatedArgs = evalList(context, locals, this.args);
if (this.receiver instanceof ImplicitReceiver && isPresent(locals) &&
locals.contains(this.name)) {
var fn = locals.get(this.name);
return FunctionWrapper.apply(fn, evaluatedArgs);
} else {
var evaluatedReceiver = this.receiver.eval(context, locals);
return this.fn(evaluatedReceiver, evaluatedArgs);
}
}
visit(visitor) { return visitor.visitMethodCall(this); }
}
export class FunctionCall extends AST {
constructor(public target: AST, public args: List<any>) { super(); }
eval(context, locals) {
var obj: any = this.target.eval(context, locals);
if (!(obj instanceof Function)) {
throw new BaseException(`${obj} is not a function`);
}
return FunctionWrapper.apply(obj, evalList(context, locals, this.args));
}
visit(visitor) { return visitor.visitFunctionCall(this); }
}
export class ASTWithSource extends AST {
constructor(public ast: AST, public source: string, public location: string) { super(); }
eval(context, locals) { return this.ast.eval(context, locals); }
get isAssignable(): boolean { return this.ast.isAssignable; }
assign(context, locals, value) { return this.ast.assign(context, locals, value); }
visit(visitor) { return this.ast.visit(visitor); }
toString(): string { return `${this.source} in ${this.location}`; }
}
export class TemplateBinding {
constructor(public key: string, public keyIsVar: boolean, public name: string,
public expression: ASTWithSource) {}
}
// INTERFACE
export class AstVisitor {
visitAccessMember(ast: AccessMember) {}
visitAssignment(ast: Assignment) {}
visitBinary(ast: Binary) {}
visitChain(ast: Chain) {}
visitConditional(ast: Conditional) {}
visitPipe(ast: Pipe) {}
visitFunctionCall(ast: FunctionCall) {}
visitImplicitReceiver(ast: ImplicitReceiver) {}
visitKeyedAccess(ast: KeyedAccess) {}
visitLiteralArray(ast: LiteralArray) {}
visitLiteralMap(ast: LiteralMap) {}
visitLiteralPrimitive(ast: LiteralPrimitive) {}
visitMethodCall(ast: MethodCall) {}
visitPrefixNot(ast: PrefixNot) {}
}
export class AstTransformer {
visitImplicitReceiver(ast: ImplicitReceiver) { return ast; }
visitInterpolation(ast: Interpolation) {
return new Interpolation(ast.strings, this.visitAll(ast.expressions));
}
visitLiteralPrimitive(ast: LiteralPrimitive) { return new LiteralPrimitive(ast.value); }
visitAccessMember(ast: AccessMember) {
return new AccessMember(ast.receiver.visit(this), ast.name, ast.getter, ast.setter);
}
visitMethodCall(ast: MethodCall) {
return new MethodCall(ast.receiver.visit(this), ast.name, ast.fn, this.visitAll(ast.args));
}
visitFunctionCall(ast: FunctionCall) {
return new FunctionCall(ast.target.visit(this), this.visitAll(ast.args));
}
visitLiteralArray(ast: LiteralArray) { return new LiteralArray(this.visitAll(ast.expressions)); }
visitLiteralMap(ast: LiteralMap) { return new LiteralMap(ast.keys, this.visitAll(ast.values)); }
visitBinary(ast: Binary) {
return new Binary(ast.operation, ast.left.visit(this), ast.right.visit(this));
}
visitPrefixNot(ast: PrefixNot) { return new PrefixNot(ast.expression.visit(this)); }
visitConditional(ast: Conditional) {
return new Conditional(ast.condition.visit(this), ast.trueExp.visit(this),
ast.falseExp.visit(this));
}
visitPipe(ast: Pipe) {
return new Pipe(ast.exp.visit(this), ast.name, this.visitAll(ast.args), ast.inBinding);
}
visitKeyedAccess(ast: KeyedAccess) {
return new KeyedAccess(ast.obj.visit(this), ast.key.visit(this));
}
visitAll(asts: List<any>) {
var res = ListWrapper.createFixedSize(asts.length);
for (var i = 0; i < asts.length; ++i) {
res[i] = asts[i].visit(this);
}
return res;
}
}
var _evalListCache = [
[],
[0],
[0, 0],
[0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]
];
function evalList(context, locals, exps: List<any>) {
var length = exps.length;
if (length > 10) {
throw new BaseException("Cannot have more than 10 argument");
}
var result = _evalListCache[length];
for (var i = 0; i < length; i++) {
result[i] = exps[i].eval(context, locals);
}
return result;
}

View File

@ -1,476 +0,0 @@
import {Injectable} from 'angular2/src/di/annotations_impl';
import {List, ListWrapper, SetWrapper} from "angular2/src/facade/collection";
import {int, NumberWrapper, StringJoiner, StringWrapper, BaseException} from "angular2/src/facade/lang";
export const TOKEN_TYPE_CHARACTER = 1;
export const TOKEN_TYPE_IDENTIFIER = 2;
export const TOKEN_TYPE_KEYWORD = 3;
export const TOKEN_TYPE_STRING = 4;
export const TOKEN_TYPE_OPERATOR = 5;
export const TOKEN_TYPE_NUMBER = 6;
@Injectable()
export class Lexer {
text:string;
tokenize(text:string):List {
var scanner = new _Scanner(text);
var tokens = [];
var token = scanner.scanToken();
while (token != null) {
ListWrapper.push(tokens, token);
token = scanner.scanToken();
}
return tokens;
}
}
export class Token {
index:int;
type:int;
_numValue:number;
_strValue:string;
constructor(index:int, type:int, numValue:number, strValue:string) {
/**
* NOTE: To ensure that this constructor creates the same hidden class each time, ensure that
* all the fields are assigned to in the exact same order in each run of this constructor.
*/
this.index = index;
this.type = type;
this._numValue = numValue;
this._strValue = strValue;
}
isCharacter(code:int):boolean {
return (this.type == TOKEN_TYPE_CHARACTER && this._numValue == code);
}
isNumber():boolean {
return (this.type == TOKEN_TYPE_NUMBER);
}
isString():boolean {
return (this.type == TOKEN_TYPE_STRING);
}
isOperator(operater:string):boolean {
return (this.type == TOKEN_TYPE_OPERATOR && this._strValue == operater);
}
isIdentifier():boolean {
return (this.type == TOKEN_TYPE_IDENTIFIER);
}
isKeyword():boolean {
return (this.type == TOKEN_TYPE_KEYWORD);
}
isKeywordVar():boolean {
return (this.type == TOKEN_TYPE_KEYWORD && this._strValue == "var");
}
isKeywordNull():boolean {
return (this.type == TOKEN_TYPE_KEYWORD && this._strValue == "null");
}
isKeywordUndefined():boolean {
return (this.type == TOKEN_TYPE_KEYWORD && this._strValue == "undefined");
}
isKeywordTrue():boolean {
return (this.type == TOKEN_TYPE_KEYWORD && this._strValue == "true");
}
isKeywordFalse():boolean {
return (this.type == TOKEN_TYPE_KEYWORD && this._strValue == "false");
}
toNumber():number {
// -1 instead of NULL ok?
return (this.type == TOKEN_TYPE_NUMBER) ? this._numValue : -1;
}
toString():string {
var type:int = this.type;
if (type >= TOKEN_TYPE_CHARACTER && type <= TOKEN_TYPE_STRING) {
return this._strValue;
} else if (type == TOKEN_TYPE_NUMBER) {
return this._numValue.toString();
} else {
return null;
}
}
}
function newCharacterToken(index:int, code:int):Token {
return new Token(index, TOKEN_TYPE_CHARACTER, code, StringWrapper.fromCharCode(code));
}
function newIdentifierToken(index:int, text:string):Token {
return new Token(index, TOKEN_TYPE_IDENTIFIER, 0, text);
}
function newKeywordToken(index:int, text:string):Token {
return new Token(index, TOKEN_TYPE_KEYWORD, 0, text);
}
function newOperatorToken(index:int, text:string):Token {
return new Token(index, TOKEN_TYPE_OPERATOR, 0, text);
}
function newStringToken(index:int, text:string):Token {
return new Token(index, TOKEN_TYPE_STRING, 0, text);
}
function newNumberToken(index:int, n:number):Token {
return new Token(index, TOKEN_TYPE_NUMBER, n, "");
}
export var EOF:Token = new Token(-1, 0, 0, "");
export const $EOF = 0;
export const $TAB = 9;
export const $LF = 10;
export const $VTAB = 11;
export const $FF = 12;
export const $CR = 13;
export const $SPACE = 32;
export const $BANG = 33;
export const $DQ = 34;
export const $HASH = 35;
export const $$ = 36;
export const $PERCENT = 37;
export const $AMPERSAND = 38;
export const $SQ = 39;
export const $LPAREN = 40;
export const $RPAREN = 41;
export const $STAR = 42;
export const $PLUS = 43;
export const $COMMA = 44;
export const $MINUS = 45;
export const $PERIOD = 46;
export const $SLASH = 47;
export const $COLON = 58;
export const $SEMICOLON = 59;
export const $LT = 60;
export const $EQ = 61;
export const $GT = 62;
export const $QUESTION = 63;
const $0 = 48;
const $9 = 57;
const $A = 65, $E = 69, $Z = 90;
export const $LBRACKET = 91;
export const $BACKSLASH = 92;
export const $RBRACKET = 93;
const $CARET = 94;
const $_ = 95;
const $a = 97, $e = 101, $f = 102, $n = 110, $r = 114, $t = 116, $u = 117, $v = 118, $z = 122;
export const $LBRACE = 123;
export const $BAR = 124;
export const $RBRACE = 125;
const $NBSP = 160;
export class ScannerError extends BaseException {
message:string;
constructor(message) {
super();
this.message = message;
}
toString() {
return this.message;
}
}
class _Scanner {
input:string;
length:int;
peek:int;
index:int;
constructor(input:string) {
this.input = input;
this.length = input.length;
this.peek = 0;
this.index = -1;
this.advance();
}
advance() {
this.peek = ++this.index >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, this.index);
}
scanToken():Token {
var input = this.input,
length = this.length,
peek = this.peek,
index = this.index;
// Skip whitespace.
while (peek <= $SPACE) {
if (++index >= length) {
peek = $EOF;
break;
} else {
peek = StringWrapper.charCodeAt(input, index);
}
}
this.peek = peek;
this.index = index;
if (index >= length) {
return null;
}
// Handle identifiers and numbers.
if (isIdentifierStart(peek)) return this.scanIdentifier();
if (isDigit(peek)) return this.scanNumber(index);
var start:int = index;
switch (peek) {
case $PERIOD:
this.advance();
return isDigit(this.peek) ? this.scanNumber(start) :
newCharacterToken(start, $PERIOD);
case $LPAREN: case $RPAREN:
case $LBRACE: case $RBRACE:
case $LBRACKET: case $RBRACKET:
case $COMMA:
case $COLON:
case $SEMICOLON:
return this.scanCharacter(start, peek);
case $SQ:
case $DQ:
return this.scanString();
case $HASH:
return this.scanOperator(start, StringWrapper.fromCharCode(peek));
case $PLUS:
case $MINUS:
case $STAR:
case $SLASH:
case $PERCENT:
case $CARET:
case $QUESTION:
return this.scanOperator(start, StringWrapper.fromCharCode(peek));
case $LT:
case $GT:
case $BANG:
case $EQ:
return this.scanComplexOperator(start, $EQ, StringWrapper.fromCharCode(peek), '=');
case $AMPERSAND:
return this.scanComplexOperator(start, $AMPERSAND, '&', '&');
case $BAR:
return this.scanComplexOperator(start, $BAR, '|', '|');
case $NBSP:
while (isWhitespace(this.peek)) this.advance();
return this.scanToken();
}
this.error(`Unexpected character [${StringWrapper.fromCharCode(peek)}]`, 0);
return null;
}
scanCharacter(start:int, code:int):Token {
assert(this.peek == code);
this.advance();
return newCharacterToken(start, code);
}
scanOperator(start:int, str:string):Token {
assert(this.peek == StringWrapper.charCodeAt(str, 0));
assert(SetWrapper.has(OPERATORS, str));
this.advance();
return newOperatorToken(start, str);
}
scanComplexOperator(start:int, code:int, one:string, two:string):Token {
assert(this.peek == StringWrapper.charCodeAt(one, 0));
this.advance();
var str:string = one;
while (this.peek == code) {
this.advance();
str += two;
}
assert(SetWrapper.has(OPERATORS, str));
return newOperatorToken(start, str);
}
scanIdentifier():Token {
assert(isIdentifierStart(this.peek));
var start:int = this.index;
this.advance();
while (isIdentifierPart(this.peek)) this.advance();
var str:string = this.input.substring(start, this.index);
if (SetWrapper.has(KEYWORDS, str)) {
return newKeywordToken(start, str);
} else {
return newIdentifierToken(start, str);
}
}
scanNumber(start:int):Token {
assert(isDigit(this.peek));
var simple:boolean = (this.index === start);
this.advance(); // Skip initial digit.
while (true) {
if (isDigit(this.peek)) {
// Do nothing.
} else if (this.peek == $PERIOD) {
simple = false;
} else if (isExponentStart(this.peek)) {
this.advance();
if (isExponentSign(this.peek)) this.advance();
if (!isDigit(this.peek)) this.error('Invalid exponent', -1);
simple = false;
} else {
break;
}
this.advance();
}
var str:string = this.input.substring(start, this.index);
// TODO
var value:number = simple ? NumberWrapper.parseIntAutoRadix(str) : NumberWrapper.parseFloat(str);
return newNumberToken(start, value);
}
scanString():Token {
assert(this.peek == $SQ || this.peek == $DQ);
var start:int = this.index;
var quote:int = this.peek;
this.advance(); // Skip initial quote.
var buffer:StringJoiner;
var marker:int = this.index;
var input:string = this.input;
while (this.peek != quote) {
if (this.peek == $BACKSLASH) {
if (buffer == null) buffer = new StringJoiner();
buffer.add(input.substring(marker, this.index));
this.advance();
var unescapedCode:int;
if (this.peek == $u) {
// 4 character hex code for unicode character.
var hex:string = input.substring(this.index + 1, this.index + 5);
try {
unescapedCode = NumberWrapper.parseInt(hex, 16);
} catch (e) {
this.error(`Invalid unicode escape [\\u${hex}]`, 0);
}
for (var i:int = 0; i < 5; i++) {
this.advance();
}
} else {
unescapedCode = unescape(this.peek);
this.advance();
}
buffer.add(StringWrapper.fromCharCode(unescapedCode));
marker = this.index;
} else if (this.peek == $EOF) {
this.error('Unterminated quote', 0);
} else {
this.advance();
}
}
var last:string = input.substring(marker, this.index);
this.advance(); // Skip terminating quote.
// Compute the unescaped string value.
var unescaped:string = last;
if (buffer != null) {
buffer.add(last);
unescaped = buffer.toString();
}
return newStringToken(start, unescaped);
}
error(message:string, offset:int) {
var position:int = this.index + offset;
throw new ScannerError(`Lexer Error: ${message} at column ${position} in expression [${this.input}]`);
}
}
function isWhitespace(code:int):boolean {
return (code >= $TAB && code <= $SPACE) || (code == $NBSP);
}
function isIdentifierStart(code:int):boolean {
return ($a <= code && code <= $z) ||
($A <= code && code <= $Z) ||
(code == $_) ||
(code == $$);
}
function isIdentifierPart(code:int):boolean {
return ($a <= code && code <= $z) ||
($A <= code && code <= $Z) ||
($0 <= code && code <= $9) ||
(code == $_) ||
(code == $$);
}
function isDigit(code:int):boolean {
return $0 <= code && code <= $9;
}
function isExponentStart(code:int):boolean {
return code == $e || code == $E;
}
function isExponentSign(code:int):boolean {
return code == $MINUS || code == $PLUS;
}
function unescape(code:int):int {
switch(code) {
case $n: return $LF;
case $f: return $FF;
case $r: return $CR;
case $t: return $TAB;
case $v: return $VTAB;
default: return code;
}
}
var OPERATORS = SetWrapper.createFromList([
'+',
'-',
'*',
'/',
'%',
'^',
'=',
'==',
'!=',
'===',
'!==',
'<',
'>',
'<=',
'>=',
'&&',
'||',
'&',
'|',
'!',
'?',
'#'
]);
var KEYWORDS = SetWrapper.createFromList([
'var',
'null',
'undefined',
'true',
'false'
]);

View File

@ -0,0 +1,452 @@
import {Injectable} from 'angular2/src/di/decorators';
import {List, ListWrapper, SetWrapper} from "angular2/src/facade/collection";
import {
int,
NumberWrapper,
StringJoiner,
StringWrapper,
BaseException
} from "angular2/src/facade/lang";
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
export const TOKEN_TYPE_CHARACTER = 1;
export const TOKEN_TYPE_IDENTIFIER = 2;
export const TOKEN_TYPE_KEYWORD = 3;
export const TOKEN_TYPE_STRING = 4;
export const TOKEN_TYPE_OPERATOR = 5;
export const TOKEN_TYPE_NUMBER = 6;
@Injectable()
export class Lexer {
tokenize(text: string): List<any> {
var scanner = new _Scanner(text);
var tokens = [];
var token = scanner.scanToken();
while (token != null) {
ListWrapper.push(tokens, token);
token = scanner.scanToken();
}
return tokens;
}
}
export class Token {
constructor(public index: int, public type: int, public numValue: number,
public strValue: string) {}
isCharacter(code: int): boolean {
return (this.type == TOKEN_TYPE_CHARACTER && this.numValue == code);
}
isNumber(): boolean { return (this.type == TOKEN_TYPE_NUMBER); }
isString(): boolean { return (this.type == TOKEN_TYPE_STRING); }
isOperator(operater: string): boolean {
return (this.type == TOKEN_TYPE_OPERATOR && this.strValue == operater);
}
isIdentifier(): boolean { return (this.type == TOKEN_TYPE_IDENTIFIER); }
isKeyword(): boolean { return (this.type == TOKEN_TYPE_KEYWORD); }
isKeywordVar(): boolean { return (this.type == TOKEN_TYPE_KEYWORD && this.strValue == "var"); }
isKeywordNull(): boolean { return (this.type == TOKEN_TYPE_KEYWORD && this.strValue == "null"); }
isKeywordUndefined(): boolean {
return (this.type == TOKEN_TYPE_KEYWORD && this.strValue == "undefined");
}
isKeywordTrue(): boolean { return (this.type == TOKEN_TYPE_KEYWORD && this.strValue == "true"); }
isKeywordFalse(): boolean {
return (this.type == TOKEN_TYPE_KEYWORD && this.strValue == "false");
}
toNumber(): number {
// -1 instead of NULL ok?
return (this.type == TOKEN_TYPE_NUMBER) ? this.numValue : -1;
}
toString(): string {
var t: int = this.type;
if (t >= TOKEN_TYPE_CHARACTER && t <= TOKEN_TYPE_STRING) {
return this.strValue;
} else if (t == TOKEN_TYPE_NUMBER) {
return this.numValue.toString();
} else {
return null;
}
}
}
function newCharacterToken(index: int, code: int): Token {
return new Token(index, TOKEN_TYPE_CHARACTER, code, StringWrapper.fromCharCode(code));
}
function newIdentifierToken(index: int, text: string): Token {
return new Token(index, TOKEN_TYPE_IDENTIFIER, 0, text);
}
function newKeywordToken(index: int, text: string): Token {
return new Token(index, TOKEN_TYPE_KEYWORD, 0, text);
}
function newOperatorToken(index: int, text: string): Token {
return new Token(index, TOKEN_TYPE_OPERATOR, 0, text);
}
function newStringToken(index: int, text: string): Token {
return new Token(index, TOKEN_TYPE_STRING, 0, text);
}
function newNumberToken(index: int, n: number): Token {
return new Token(index, TOKEN_TYPE_NUMBER, n, "");
}
export var EOF: Token = new Token(-1, 0, 0, "");
export const $EOF = 0;
export const $TAB = 9;
export const $LF = 10;
export const $VTAB = 11;
export const $FF = 12;
export const $CR = 13;
export const $SPACE = 32;
export const $BANG = 33;
export const $DQ = 34;
export const $HASH = 35;
export const $$ = 36;
export const $PERCENT = 37;
export const $AMPERSAND = 38;
export const $SQ = 39;
export const $LPAREN = 40;
export const $RPAREN = 41;
export const $STAR = 42;
export const $PLUS = 43;
export const $COMMA = 44;
export const $MINUS = 45;
export const $PERIOD = 46;
export const $SLASH = 47;
export const $COLON = 58;
export const $SEMICOLON = 59;
export const $LT = 60;
export const $EQ = 61;
export const $GT = 62;
export const $QUESTION = 63;
const $0 = 48;
const $9 = 57;
const $A = 65, $E = 69, $Z = 90;
export const $LBRACKET = 91;
export const $BACKSLASH = 92;
export const $RBRACKET = 93;
const $CARET = 94;
const $_ = 95;
const $a = 97, $e = 101, $f = 102, $n = 110, $r = 114, $t = 116, $u = 117, $v = 118, $z = 122;
export const $LBRACE = 123;
export const $BAR = 124;
export const $RBRACE = 125;
const $NBSP = 160;
export class ScannerError extends BaseException {
message: string;
constructor(message) {
super();
this.message = message;
}
toString() { return this.message; }
}
class _Scanner {
length: int;
peek: int;
index: int;
constructor(public input: string) {
this.length = input.length;
this.peek = 0;
this.index = -1;
this.advance();
}
advance() {
this.peek =
++this.index >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, this.index);
}
scanToken(): Token {
var input = this.input, length = this.length, peek = this.peek, index = this.index;
// Skip whitespace.
while (peek <= $SPACE) {
if (++index >= length) {
peek = $EOF;
break;
} else {
peek = StringWrapper.charCodeAt(input, index);
}
}
this.peek = peek;
this.index = index;
if (index >= length) {
return null;
}
// Handle identifiers and numbers.
if (isIdentifierStart(peek)) return this.scanIdentifier();
if (isDigit(peek)) return this.scanNumber(index);
var start: int = index;
switch (peek) {
case $PERIOD:
this.advance();
return isDigit(this.peek) ? this.scanNumber(start) : newCharacterToken(start, $PERIOD);
case $LPAREN:
case $RPAREN:
case $LBRACE:
case $RBRACE:
case $LBRACKET:
case $RBRACKET:
case $COMMA:
case $COLON:
case $SEMICOLON:
return this.scanCharacter(start, peek);
case $SQ:
case $DQ:
return this.scanString();
case $HASH:
return this.scanOperator(start, StringWrapper.fromCharCode(peek));
case $PLUS:
case $MINUS:
case $STAR:
case $SLASH:
case $PERCENT:
case $CARET:
case $QUESTION:
return this.scanOperator(start, StringWrapper.fromCharCode(peek));
case $LT:
case $GT:
case $BANG:
case $EQ:
return this.scanComplexOperator(start, $EQ, StringWrapper.fromCharCode(peek), '=');
case $AMPERSAND:
return this.scanComplexOperator(start, $AMPERSAND, '&', '&');
case $BAR:
return this.scanComplexOperator(start, $BAR, '|', '|');
case $NBSP:
while (isWhitespace(this.peek)) this.advance();
return this.scanToken();
}
this.error(`Unexpected character [${StringWrapper.fromCharCode(peek)}]`, 0);
return null;
}
scanCharacter(start: int, code: int): Token {
assert(this.peek == code);
this.advance();
return newCharacterToken(start, code);
}
scanOperator(start: int, str: string): Token {
assert(this.peek == StringWrapper.charCodeAt(str, 0));
assert(SetWrapper.has(OPERATORS, str));
this.advance();
return newOperatorToken(start, str);
}
scanComplexOperator(start: int, code: int, one: string, two: string): Token {
assert(this.peek == StringWrapper.charCodeAt(one, 0));
this.advance();
var str: string = one;
while (this.peek == code) {
this.advance();
str += two;
}
assert(SetWrapper.has(OPERATORS, str));
return newOperatorToken(start, str);
}
scanIdentifier(): Token {
assert(isIdentifierStart(this.peek));
var start: int = this.index;
this.advance();
while (isIdentifierPart(this.peek)) this.advance();
var str: string = this.input.substring(start, this.index);
if (SetWrapper.has(KEYWORDS, str)) {
return newKeywordToken(start, str);
} else {
return newIdentifierToken(start, str);
}
}
scanNumber(start: int): Token {
assert(isDigit(this.peek));
var simple: boolean = (this.index === start);
this.advance(); // Skip initial digit.
while (true) {
if (isDigit(this.peek)) {
// Do nothing.
} else if (this.peek == $PERIOD) {
simple = false;
} else if (isExponentStart(this.peek)) {
this.advance();
if (isExponentSign(this.peek)) this.advance();
if (!isDigit(this.peek)) this.error('Invalid exponent', -1);
simple = false;
} else {
break;
}
this.advance();
}
var str: string = this.input.substring(start, this.index);
// TODO
var value: number =
simple ? NumberWrapper.parseIntAutoRadix(str) : NumberWrapper.parseFloat(str);
return newNumberToken(start, value);
}
scanString(): Token {
assert(this.peek == $SQ || this.peek == $DQ);
var start: int = this.index;
var quote: int = this.peek;
this.advance(); // Skip initial quote.
var buffer: StringJoiner;
var marker: int = this.index;
var input: string = this.input;
while (this.peek != quote) {
if (this.peek == $BACKSLASH) {
if (buffer == null) buffer = new StringJoiner();
buffer.add(input.substring(marker, this.index));
this.advance();
var unescapedCode: int;
if (this.peek == $u) {
// 4 character hex code for unicode character.
var hex: string = input.substring(this.index + 1, this.index + 5);
try {
unescapedCode = NumberWrapper.parseInt(hex, 16);
} catch (e) {
this.error(`Invalid unicode escape [\\u${hex}]`, 0);
}
for (var i: int = 0; i < 5; i++) {
this.advance();
}
} else {
unescapedCode = unescape(this.peek);
this.advance();
}
buffer.add(StringWrapper.fromCharCode(unescapedCode));
marker = this.index;
} else if (this.peek == $EOF) {
this.error('Unterminated quote', 0);
} else {
this.advance();
}
}
var last: string = input.substring(marker, this.index);
this.advance(); // Skip terminating quote.
// Compute the unescaped string value.
var unescaped: string = last;
if (buffer != null) {
buffer.add(last);
unescaped = buffer.toString();
}
return newStringToken(start, unescaped);
}
error(message: string, offset: int) {
var position: int = this.index + offset;
throw new ScannerError(
`Lexer Error: ${message} at column ${position} in expression [${this.input}]`);
}
}
function isWhitespace(code: int): boolean {
return (code >= $TAB && code <= $SPACE) || (code == $NBSP);
}
function isIdentifierStart(code: int): boolean {
return ($a <= code && code <= $z) || ($A <= code && code <= $Z) || (code == $_) || (code == $$);
}
function isIdentifierPart(code: int): boolean {
return ($a <= code && code <= $z) || ($A <= code && code <= $Z) || ($0 <= code && code <= $9) ||
(code == $_) || (code == $$);
}
function isDigit(code: int): boolean {
return $0 <= code && code <= $9;
}
function isExponentStart(code: int): boolean {
return code == $e || code == $E;
}
function isExponentSign(code: int): boolean {
return code == $MINUS || code == $PLUS;
}
function unescape(code: int): int {
switch (code) {
case $n:
return $LF;
case $f:
return $FF;
case $r:
return $CR;
case $t:
return $TAB;
case $v:
return $VTAB;
default:
return code;
}
}
var OPERATORS = SetWrapper.createFromList([
'+',
'-',
'*',
'/',
'%',
'^',
'=',
'==',
'!=',
'===',
'!==',
'<',
'>',
'<=',
'>=',
'&&',
'||',
'&',
'|',
'!',
'?',
'#'
]);
var KEYWORDS = SetWrapper.createFromList(['var', 'null', 'undefined', 'true', 'false']);

View File

@ -1,16 +1,15 @@
import {isPresent, BaseException} from 'angular2/src/facade/lang'; import {isPresent, BaseException} from 'angular2/src/facade/lang';
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
export class Locals { export class Locals {
parent:Locals; constructor(public parent: Locals, public current: Map<any, any>) {}
current:Map;
constructor(parent:Locals, current:Map) { contains(name: string): boolean {
this.parent = parent;
this.current = current;
}
contains(name:string):boolean {
if (MapWrapper.contains(this.current, name)) { if (MapWrapper.contains(this.current, name)) {
return true; return true;
} }
@ -22,7 +21,7 @@ export class Locals {
return false; return false;
} }
get(name:string) { get(name: string) {
if (MapWrapper.contains(this.current, name)) { if (MapWrapper.contains(this.current, name)) {
return MapWrapper.get(this.current, name); return MapWrapper.get(this.current, name);
} }
@ -34,7 +33,7 @@ export class Locals {
throw new BaseException(`Cannot find '${name}'`); throw new BaseException(`Cannot find '${name}'`);
} }
set(name:string, value):void { set(name: string, value): void {
// TODO(rado): consider removing this check if we can guarantee this is not // TODO(rado): consider removing this check if we can guarantee this is not
// exposed to the public API. // exposed to the public API.
// TODO: vsavkin maybe it should check only the local map // TODO: vsavkin maybe it should check only the local map
@ -45,7 +44,5 @@ export class Locals {
} }
} }
clearValues():void { clearValues(): void { MapWrapper.clearValues(this.current); }
MapWrapper.clearValues(this.current);
}
} }

View File

@ -1,8 +1,28 @@
import {Injectable} from 'angular2/src/di/annotations_impl'; import {Injectable} from 'angular2/src/di/decorators';
import {int, isBlank, isPresent, BaseException, StringWrapper, RegExpWrapper} from 'angular2/src/facade/lang'; import {
int,
isBlank,
isPresent,
BaseException,
StringWrapper,
RegExpWrapper
} from 'angular2/src/facade/lang';
import {ListWrapper, List} from 'angular2/src/facade/collection'; import {ListWrapper, List} from 'angular2/src/facade/collection';
import {Lexer, EOF, Token, $PERIOD, $COLON, $SEMICOLON, $LBRACKET, $RBRACKET, import {
$COMMA, $LBRACE, $RBRACE, $LPAREN, $RPAREN} from './lexer'; Lexer,
EOF,
Token,
$PERIOD,
$COLON,
$SEMICOLON,
$LBRACKET,
$RBRACKET,
$COMMA,
$LBRACE,
$RBRACE,
$LPAREN,
$RPAREN
} from './lexer';
import {reflector, Reflector} from 'angular2/src/reflection/reflection'; import {reflector, Reflector} from 'angular2/src/reflection/reflection';
import { import {
AST, AST,
@ -10,7 +30,6 @@ import {
ImplicitReceiver, ImplicitReceiver,
AccessMember, AccessMember,
LiteralPrimitive, LiteralPrimitive,
Expression,
Binary, Binary,
PrefixNot, PrefixNot,
Conditional, Conditional,
@ -23,10 +42,15 @@ import {
Interpolation, Interpolation,
MethodCall, MethodCall,
FunctionCall, FunctionCall,
TemplateBindings,
TemplateBinding, TemplateBinding,
ASTWithSource ASTWithSource
} from './ast'; } from './ast';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
var _implicitReceiver = new ImplicitReceiver(); var _implicitReceiver = new ImplicitReceiver();
// TODO(tbosch): Cannot make this const/final right now because of the transpiler... // TODO(tbosch): Cannot make this const/final right now because of the transpiler...
@ -34,40 +58,40 @@ var INTERPOLATION_REGEXP = RegExpWrapper.create('\\{\\{(.*?)\\}\\}');
@Injectable() @Injectable()
export class Parser { export class Parser {
_lexer:Lexer; _lexer: Lexer;
_reflector:Reflector; _reflector: Reflector;
constructor(lexer:Lexer, providedReflector:Reflector = null){ constructor(lexer: Lexer, providedReflector: Reflector = null) {
this._lexer = lexer; this._lexer = lexer;
this._reflector = isPresent(providedReflector) ? providedReflector : reflector; this._reflector = isPresent(providedReflector) ? providedReflector : reflector;
} }
parseAction(input:string, location:any):ASTWithSource { parseAction(input: string, location: any): ASTWithSource {
var tokens = this._lexer.tokenize(input); var tokens = this._lexer.tokenize(input);
var ast = new _ParseAST(input, location, tokens, this._reflector, true).parseChain(); var ast = new _ParseAST(input, location, tokens, this._reflector, true).parseChain();
return new ASTWithSource(ast, input, location); return new ASTWithSource(ast, input, location);
} }
parseBinding(input:string, location:any):ASTWithSource { parseBinding(input: string, location: any): ASTWithSource {
var tokens = this._lexer.tokenize(input); var tokens = this._lexer.tokenize(input);
var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseChain(); var ast = new _ParseAST(input, location, tokens, this._reflector, false).parseChain();
return new ASTWithSource(ast, input, location); return new ASTWithSource(ast, input, location);
} }
addPipes(bindingAst:ASTWithSource, pipes:List<String>):ASTWithSource { addPipes(bindingAst: ASTWithSource, pipes: List<string>): ASTWithSource {
if (ListWrapper.isEmpty(pipes)) return bindingAst; if (ListWrapper.isEmpty(pipes)) return bindingAst;
var res = ListWrapper.reduce(pipes, var res: AST = ListWrapper.reduce(
(result, currentPipeName) => new Pipe(result, currentPipeName, [], false), pipes, (result, currentPipeName) => new Pipe(result, currentPipeName, [], false),
bindingAst.ast); bindingAst.ast);
return new ASTWithSource(res, bindingAst.source, bindingAst.location); return new ASTWithSource(res, bindingAst.source, bindingAst.location);
} }
parseTemplateBindings(input:string, location:any):List<TemplateBinding> { parseTemplateBindings(input: string, location: any): List<TemplateBinding> {
var tokens = this._lexer.tokenize(input); var tokens = this._lexer.tokenize(input);
return new _ParseAST(input, location, tokens, this._reflector, false).parseTemplateBindings(); return new _ParseAST(input, location, tokens, this._reflector, false).parseTemplateBindings();
} }
parseInterpolation(input:string, location:any):ASTWithSource { parseInterpolation(input: string, location: any): ASTWithSource {
var parts = StringWrapper.split(input, INTERPOLATION_REGEXP); var parts = StringWrapper.split(input, INTERPOLATION_REGEXP);
if (parts.length <= 1) { if (parts.length <= 1) {
return null; return null;
@ -75,9 +99,9 @@ export class Parser {
var strings = []; var strings = [];
var expressions = []; var expressions = [];
for (var i=0; i<parts.length; i++) { for (var i = 0; i < parts.length; i++) {
var part = parts[i]; var part = parts[i];
if (i%2 === 0) { if (i % 2 === 0) {
// fixed string // fixed string
ListWrapper.push(strings, part); ListWrapper.push(strings, part);
} else { } else {
@ -89,46 +113,32 @@ export class Parser {
return new ASTWithSource(new Interpolation(strings, expressions), input, location); return new ASTWithSource(new Interpolation(strings, expressions), input, location);
} }
wrapLiteralPrimitive(input:string, location:any):ASTWithSource { wrapLiteralPrimitive(input: string, location: any): ASTWithSource {
return new ASTWithSource(new LiteralPrimitive(input), input, location); return new ASTWithSource(new LiteralPrimitive(input), input, location);
} }
} }
class _ParseAST { class _ParseAST {
input:string; index: int;
location:any; constructor(public input: string, public location: any, public tokens: List<any>,
tokens:List<Token>; public reflector: Reflector, public parseAction: boolean) {
reflector:Reflector;
parseAction:boolean;
index:int;
constructor(input:string, location:any, tokens:List, reflector:Reflector, parseAction:boolean) {
this.input = input;
this.location = location;
this.tokens = tokens;
this.index = 0; this.index = 0;
this.reflector = reflector;
this.parseAction = parseAction;
} }
peek(offset:int):Token { peek(offset: int): Token {
var i = this.index + offset; var i = this.index + offset;
return i < this.tokens.length ? this.tokens[i] : EOF; return i < this.tokens.length ? this.tokens[i] : EOF;
} }
get next():Token { get next(): Token { return this.peek(0); }
return this.peek(0);
}
get inputIndex():int { get inputIndex(): int {
return (this.index < this.tokens.length) ? this.next.index : this.input.length; return (this.index < this.tokens.length) ? this.next.index : this.input.length;
} }
advance() { advance() { this.index++; }
this.index ++;
}
optionalCharacter(code:int):boolean { optionalCharacter(code: int): boolean {
if (this.next.isCharacter(code)) { if (this.next.isCharacter(code)) {
this.advance(); this.advance();
return true; return true;
@ -137,7 +147,7 @@ class _ParseAST {
} }
} }
optionalKeywordVar():boolean { optionalKeywordVar(): boolean {
if (this.peekKeywordVar()) { if (this.peekKeywordVar()) {
this.advance(); this.advance();
return true; return true;
@ -146,17 +156,15 @@ class _ParseAST {
} }
} }
peekKeywordVar():boolean { peekKeywordVar(): boolean { return this.next.isKeywordVar() || this.next.isOperator('#'); }
return this.next.isKeywordVar() || this.next.isOperator('#');
}
expectCharacter(code:int) { expectCharacter(code: int) {
if (this.optionalCharacter(code)) return; if (this.optionalCharacter(code)) return;
this.error(`Missing expected ${StringWrapper.fromCharCode(code)}`); this.error(`Missing expected ${StringWrapper.fromCharCode(code)}`);
} }
optionalOperator(op:string):boolean { optionalOperator(op: string): boolean {
if (this.next.isOperator(op)) { if (this.next.isOperator(op)) {
this.advance(); this.advance();
return true; return true;
@ -165,12 +173,12 @@ class _ParseAST {
} }
} }
expectOperator(operator:string) { expectOperator(operator: string) {
if (this.optionalOperator(operator)) return; if (this.optionalOperator(operator)) return;
this.error(`Missing expected operator ${operator}`); this.error(`Missing expected operator ${operator}`);
} }
expectIdentifierOrKeyword():string { expectIdentifierOrKeyword(): string {
var n = this.next; var n = this.next;
if (!n.isIdentifier() && !n.isKeyword()) { if (!n.isIdentifier() && !n.isKeyword()) {
this.error(`Unexpected token ${n}, expected identifier or keyword`) this.error(`Unexpected token ${n}, expected identifier or keyword`)
@ -179,7 +187,7 @@ class _ParseAST {
return n.toString(); return n.toString();
} }
expectIdentifierOrKeywordOrString():string { expectIdentifierOrKeywordOrString(): string {
var n = this.next; var n = this.next;
if (!n.isIdentifier() && !n.isKeyword() && !n.isString()) { if (!n.isIdentifier() && !n.isKeyword() && !n.isString()) {
this.error(`Unexpected token ${n}, expected identifier, keyword, or string`) this.error(`Unexpected token ${n}, expected identifier, keyword, or string`)
@ -188,17 +196,18 @@ class _ParseAST {
return n.toString(); return n.toString();
} }
parseChain():AST { parseChain(): AST {
var exprs = []; var exprs = [];
while (this.index < this.tokens.length) { while (this.index < this.tokens.length) {
var expr = this.parsePipe(); var expr = this.parsePipe();
ListWrapper.push(exprs, expr); ListWrapper.push(exprs, expr);
if (this.optionalCharacter($SEMICOLON)) { if (this.optionalCharacter($SEMICOLON)) {
if (! this.parseAction) { if (!this.parseAction) {
this.error("Binding expression cannot contain chained expression"); this.error("Binding expression cannot contain chained expression");
} }
while (this.optionalCharacter($SEMICOLON)){} //read all semicolons while (this.optionalCharacter($SEMICOLON)) {
} // read all semicolons
} else if (this.index < this.tokens.length) { } else if (this.index < this.tokens.length) {
this.error(`Unexpected token '${this.next}'`); this.error(`Unexpected token '${this.next}'`);
} }
@ -353,7 +362,7 @@ class _ParseAST {
} }
} }
parseCallChain():AST { parseCallChain(): AST {
var result = this.parsePrimary(); var result = this.parsePrimary();
while (true) { while (true) {
if (this.optionalCharacter($PERIOD)) { if (this.optionalCharacter($PERIOD)) {
@ -410,9 +419,9 @@ class _ParseAST {
return new LiteralPrimitive(value); return new LiteralPrimitive(value);
} else if (this.next.isString()) { } else if (this.next.isString()) {
var value = this.next.toString(); var literalValue = this.next.toString();
this.advance(); this.advance();
return new LiteralPrimitive(value); return new LiteralPrimitive(literalValue);
} else if (this.index >= this.tokens.length) { } else if (this.index >= this.tokens.length) {
this.error(`Unexpected end of expression: ${this.input}`); this.error(`Unexpected end of expression: ${this.input}`);
@ -422,7 +431,7 @@ class _ParseAST {
} }
} }
parseExpressionList(terminator:int):List { parseExpressionList(terminator: int): List<any> {
var result = []; var result = [];
if (!this.next.isCharacter(terminator)) { if (!this.next.isCharacter(terminator)) {
do { do {
@ -448,7 +457,7 @@ class _ParseAST {
return new LiteralMap(keys, values); return new LiteralMap(keys, values);
} }
parseAccessMemberOrMethodCall(receiver):AST { parseAccessMemberOrMethodCall(receiver): AST {
var id = this.expectIdentifierOrKeyword(); var id = this.expectIdentifierOrKeyword();
if (this.optionalCharacter($LPAREN)) { if (this.optionalCharacter($LPAREN)) {
@ -471,7 +480,7 @@ class _ParseAST {
} }
parseInlinedPipe(result) { parseInlinedPipe(result) {
do { do {
if (this.parseAction) { if (this.parseAction) {
this.error("Cannot have a pipe in an action expression"); this.error("Cannot have a pipe in an action expression");
} }
@ -481,7 +490,7 @@ class _ParseAST {
ListWrapper.push(args, this.parseExpression()); ListWrapper.push(args, this.parseExpression());
} }
result = new Pipe(result, name, args, true); result = new Pipe(result, name, args, true);
} while(this.optionalOperator("|")); } while (this.optionalOperator("|"));
return result; return result;
} }
@ -515,7 +524,7 @@ class _ParseAST {
parseTemplateBindings() { parseTemplateBindings() {
var bindings = []; var bindings = [];
while (this.index < this.tokens.length) { while (this.index < this.tokens.length) {
var keyIsVar:boolean = this.optionalKeywordVar(); var keyIsVar: boolean = this.optionalKeywordVar();
var key = this.expectTemplateBindingKey(); var key = this.expectTemplateBindingKey();
this.optionalCharacter($COLON); this.optionalCharacter($COLON);
var name = null; var name = null;
@ -542,13 +551,13 @@ class _ParseAST {
return bindings; return bindings;
} }
error(message:string, index:int = null) { error(message: string, index: int = null) {
if (isBlank(index)) index = this.index; if (isBlank(index)) index = this.index;
var location = (index < this.tokens.length) var location = (index < this.tokens.length) ? `at column ${this.tokens[index].index + 1} in` :
? `at column ${this.tokens[index].index + 1} in` `at the end of the expression`;
: `at the end of the expression`;
throw new BaseException(`Parser Error: ${message} ${location} [${this.input}] in ${this.location}`); throw new BaseException(
`Parser Error: ${message} ${location} [${this.input}] in ${this.location}`);
} }
} }

View File

@ -3,13 +3,21 @@ import {isBlank, isPresent, CONST} from 'angular2/src/facade/lang';
import {Pipe, WrappedValue, PipeFactory} from './pipe'; import {Pipe, WrappedValue, PipeFactory} from './pipe';
import {ChangeDetectorRef} from '../change_detector_ref'; import {ChangeDetectorRef} from '../change_detector_ref';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
/** /**
* Implements async bindings to Observable. * Implements async bindings to Observable.
* *
* # Example * # Example
* *
* In this example we bind the description observable to the DOM. The async pipe will convert an observable to the * In this example we bind the description observable to the DOM. The async pipe will convert an
* latest value it emitted. It will also request a change detection check when a new value is emitted. *observable to the
* latest value it emitted. It will also request a change detection check when a new value is
*emitted.
* *
* ``` * ```
* @Component({ * @Component({
@ -28,15 +36,15 @@ import {ChangeDetectorRef} from '../change_detector_ref';
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
export class AsyncPipe extends Pipe { export class AsyncPipe extends Pipe {
_ref:ChangeDetectorRef; _ref: ChangeDetectorRef;
_latestValue:Object; _latestValue: Object;
_latestReturnedValue:Object; _latestReturnedValue: Object;
_subscription:Object; _subscription: Object;
_observable:Observable; _observable: Observable;
constructor(ref:ChangeDetectorRef) { constructor(ref: ChangeDetectorRef) {
super(); super();
this._ref = ref; this._ref = ref;
this._latestValue = null; this._latestValue = null;
@ -45,17 +53,15 @@ export class AsyncPipe extends Pipe {
this._observable = null; this._observable = null;
} }
supports(obs):boolean { supports(obs): boolean { return ObservableWrapper.isObservable(obs); }
return ObservableWrapper.isObservable(obs);
}
onDestroy():void { onDestroy(): void {
if (isPresent(this._subscription)) { if (isPresent(this._subscription)) {
this._dispose(); this._dispose();
} }
} }
transform(obs:Observable):any { transform(obs: Observable): any {
if (isBlank(this._subscription)) { if (isBlank(this._subscription)) {
this._subscribe(obs); this._subscribe(obs);
return null; return null;
@ -74,15 +80,13 @@ export class AsyncPipe extends Pipe {
} }
} }
_subscribe(obs:Observable):void { _subscribe(obs: Observable): void {
this._observable = obs; this._observable = obs;
this._subscription = ObservableWrapper.subscribe(obs, this._subscription = ObservableWrapper.subscribe(obs, value => {this._updateLatestValue(value)},
value => this._updateLatestValue(value), e => { throw e; });
e => {throw e;}
);
} }
_dispose():void { _dispose(): void {
ObservableWrapper.dispose(this._subscription); ObservableWrapper.dispose(this._subscription);
this._latestValue = null; this._latestValue = null;
this._latestReturnedValue = null; this._latestReturnedValue = null;
@ -90,7 +94,7 @@ export class AsyncPipe extends Pipe {
this._observable = null; this._observable = null;
} }
_updateLatestValue(value:Object) { _updateLatestValue(value: Object) {
this._latestValue = value; this._latestValue = value;
this._ref.requestCheck(); this._ref.requestCheck();
} }
@ -101,17 +105,11 @@ export class AsyncPipe extends Pipe {
* *
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
@CONST()
export class AsyncPipeFactory extends PipeFactory { export class AsyncPipeFactory extends PipeFactory {
@CONST() constructor() { super(); }
constructor() {
super();
}
supports(obs):boolean { supports(obs): boolean { return ObservableWrapper.isObservable(obs); }
return ObservableWrapper.isObservable(obs);
}
create(cdRef):Pipe { create(cdRef): Pipe { return new AsyncPipe(cdRef); }
return new AsyncPipe(cdRef);
}
} }

View File

@ -17,38 +17,37 @@ import {
import {WrappedValue, Pipe, PipeFactory} from './pipe'; import {WrappedValue, Pipe, PipeFactory} from './pipe';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
@CONST()
export class IterableChangesFactory extends PipeFactory { export class IterableChangesFactory extends PipeFactory {
@CONST() constructor() { super(); }
constructor() {
super();
}
supports(obj):boolean { supports(obj): boolean { return IterableChanges.supportsObj(obj); }
return IterableChanges.supportsObj(obj);
}
create(cdRef):Pipe { create(cdRef): Pipe { return new IterableChanges(); }
return new IterableChanges();
}
} }
/** /**
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
export class IterableChanges extends Pipe { export class IterableChanges extends Pipe {
_collection; private _collection;
_length:int; private _length: int;
_linkedRecords:_DuplicateMap; private _linkedRecords: _DuplicateMap;
_unlinkedRecords:_DuplicateMap; private _unlinkedRecords: _DuplicateMap;
_previousItHead:CollectionChangeRecord; private _previousItHead: CollectionChangeRecord;
_itHead:CollectionChangeRecord; private _itHead: CollectionChangeRecord;
_itTail:CollectionChangeRecord; private _itTail: CollectionChangeRecord;
_additionsHead:CollectionChangeRecord; private _additionsHead: CollectionChangeRecord;
_additionsTail:CollectionChangeRecord; private _additionsTail: CollectionChangeRecord;
_movesHead:CollectionChangeRecord; private _movesHead: CollectionChangeRecord;
_movesTail:CollectionChangeRecord; private _movesTail: CollectionChangeRecord;
_removalsHead:CollectionChangeRecord; private _removalsHead: CollectionChangeRecord;
_removalsTail:CollectionChangeRecord; private _removalsTail: CollectionChangeRecord;
constructor() { constructor() {
super(); super();
@ -70,58 +69,50 @@ export class IterableChanges extends Pipe {
this._removalsTail = null; this._removalsTail = null;
} }
static supportsObj(obj):boolean { static supportsObj(obj): boolean { return isListLikeIterable(obj); }
return isListLikeIterable(obj);
}
supports(obj):boolean { supports(obj): boolean { return IterableChanges.supportsObj(obj); }
return IterableChanges.supportsObj(obj);
}
get collection() { get collection() { return this._collection; }
return this._collection;
}
get length():int { get length(): int { return this._length; }
return this._length;
}
forEachItem(fn:Function) { forEachItem(fn: Function) {
var record:CollectionChangeRecord; var record: CollectionChangeRecord;
for (record = this._itHead; record !== null; record = record._next) { for (record = this._itHead; record !== null; record = record._next) {
fn(record); fn(record);
} }
} }
forEachPreviousItem(fn:Function) { forEachPreviousItem(fn: Function) {
var record:CollectionChangeRecord; var record: CollectionChangeRecord;
for (record = this._previousItHead; record !== null; record = record._nextPrevious) { for (record = this._previousItHead; record !== null; record = record._nextPrevious) {
fn(record); fn(record);
} }
} }
forEachAddedItem(fn:Function){ forEachAddedItem(fn: Function) {
var record:CollectionChangeRecord; var record: CollectionChangeRecord;
for (record = this._additionsHead; record !== null; record = record._nextAdded) { for (record = this._additionsHead; record !== null; record = record._nextAdded) {
fn(record); fn(record);
} }
} }
forEachMovedItem(fn:Function) { forEachMovedItem(fn: Function) {
var record:CollectionChangeRecord; var record: CollectionChangeRecord;
for (record = this._movesHead; record !== null; record = record._nextMoved) { for (record = this._movesHead; record !== null; record = record._nextMoved) {
fn(record); fn(record);
} }
} }
forEachRemovedItem(fn:Function){ forEachRemovedItem(fn: Function) {
var record:CollectionChangeRecord; var record: CollectionChangeRecord;
for (record = this._removalsHead; record !== null; record = record._nextRemoved) { for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
fn(record); fn(record);
} }
} }
transform(collection){ transform(collection): any {
if (this.check(collection)) { if (this.check(collection)) {
return WrappedValue.wrap(this); return WrappedValue.wrap(this);
} else { } else {
@ -130,12 +121,12 @@ export class IterableChanges extends Pipe {
} }
// todo(vicb): optim for UnmodifiableListView (frozen arrays) // todo(vicb): optim for UnmodifiableListView (frozen arrays)
check(collection):boolean { check(collection): boolean {
this._reset(); this._reset();
var record:CollectionChangeRecord = this._itHead; var record: CollectionChangeRecord = this._itHead;
var mayBeDirty:boolean = false; var mayBeDirty: boolean = false;
var index:int; var index: int;
var item; var item;
if (ListWrapper.isList(collection)) { if (ListWrapper.isList(collection)) {
@ -175,10 +166,8 @@ export class IterableChanges extends Pipe {
} }
// CollectionChanges is considered dirty if it has any additions, moves or removals. // CollectionChanges is considered dirty if it has any additions, moves or removals.
get isDirty():boolean { get isDirty(): boolean {
return this._additionsHead !== null || return this._additionsHead !== null || this._movesHead !== null || this._removalsHead !== null;
this._movesHead !== null ||
this._removalsHead !== null;
} }
/** /**
@ -189,8 +178,8 @@ export class IterableChanges extends Pipe {
*/ */
_reset() { _reset() {
if (this.isDirty) { if (this.isDirty) {
var record:CollectionChangeRecord; var record: CollectionChangeRecord;
var nextRecord:CollectionChangeRecord; var nextRecord: CollectionChangeRecord;
for (record = this._previousItHead = this._itHead; record !== null; record = record._next) { for (record = this._previousItHead = this._itHead; record !== null; record = record._next) {
record._nextPrevious = record._next; record._nextPrevious = record._next;
@ -221,9 +210,9 @@ export class IterableChanges extends Pipe {
* - `item` is the current item in the collection * - `item` is the current item in the collection
* - `index` is the position of the item in the collection * - `index` is the position of the item in the collection
*/ */
_mismatch(record:CollectionChangeRecord, item, index:int):CollectionChangeRecord { _mismatch(record: CollectionChangeRecord, item, index: int): CollectionChangeRecord {
// The previous record after which we will append the current one. // The previous record after which we will append the current one.
var previousRecord:CollectionChangeRecord; var previousRecord: CollectionChangeRecord;
if (record === null) { if (record === null) {
previousRecord = this._itTail; previousRecord = this._itTail;
@ -277,9 +266,9 @@ export class IterableChanges extends Pipe {
* better way to think of it is as insert of 'b' rather then switch 'a' with 'b' and then add 'a' * better way to think of it is as insert of 'b' rather then switch 'a' with 'b' and then add 'a'
* at the end. * at the end.
*/ */
_verifyReinsertion(record:CollectionChangeRecord, item, index:int):CollectionChangeRecord { _verifyReinsertion(record: CollectionChangeRecord, item, index: int): CollectionChangeRecord {
var reinsertRecord:CollectionChangeRecord = this._unlinkedRecords === null ? var reinsertRecord: CollectionChangeRecord =
null : this._unlinkedRecords.get(item); this._unlinkedRecords === null ? null : this._unlinkedRecords.get(item);
if (reinsertRecord !== null) { if (reinsertRecord !== null) {
record = this._reinsertAfter(reinsertRecord, record._prev, index); record = this._reinsertAfter(reinsertRecord, record._prev, index);
} else if (record.currentIndex != index) { } else if (record.currentIndex != index) {
@ -294,10 +283,10 @@ export class IterableChanges extends Pipe {
* *
* - `record` The first excess {@link CollectionChangeRecord}. * - `record` The first excess {@link CollectionChangeRecord}.
*/ */
_truncate(record:CollectionChangeRecord) { _truncate(record: CollectionChangeRecord) {
// Anything after that needs to be removed; // Anything after that needs to be removed;
while (record !== null) { while (record !== null) {
var nextRecord:CollectionChangeRecord = record._next; var nextRecord: CollectionChangeRecord = record._next;
this._addToRemovals(this._unlink(record)); this._addToRemovals(this._unlink(record));
record = nextRecord; record = nextRecord;
} }
@ -319,8 +308,8 @@ export class IterableChanges extends Pipe {
} }
} }
_reinsertAfter(record:CollectionChangeRecord, prevRecord:CollectionChangeRecord, _reinsertAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
index:int):CollectionChangeRecord { index: int): CollectionChangeRecord {
if (this._unlinkedRecords !== null) { if (this._unlinkedRecords !== null) {
this._unlinkedRecords.remove(record); this._unlinkedRecords.remove(record);
} }
@ -343,42 +332,42 @@ export class IterableChanges extends Pipe {
return record; return record;
} }
_moveAfter(record:CollectionChangeRecord, prevRecord:CollectionChangeRecord, _moveAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
index:int):CollectionChangeRecord { index: int): CollectionChangeRecord {
this._unlink(record); this._unlink(record);
this._insertAfter(record, prevRecord, index); this._insertAfter(record, prevRecord, index);
this._addToMoves(record, index); this._addToMoves(record, index);
return record; return record;
} }
_addAfter(record:CollectionChangeRecord, prevRecord:CollectionChangeRecord, _addAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
index:int):CollectionChangeRecord { index: int): CollectionChangeRecord {
this._insertAfter(record, prevRecord, index); this._insertAfter(record, prevRecord, index);
if (this._additionsTail === null) { if (this._additionsTail === null) {
// todo(vicb) // todo(vicb)
//assert(this._additionsHead === null); // assert(this._additionsHead === null);
this._additionsTail = this._additionsHead = record; this._additionsTail = this._additionsHead = record;
} else { } else {
// todo(vicb) // todo(vicb)
//assert(_additionsTail._nextAdded === null); // assert(_additionsTail._nextAdded === null);
//assert(record._nextAdded === null); // assert(record._nextAdded === null);
this._additionsTail = this._additionsTail._nextAdded = record; this._additionsTail = this._additionsTail._nextAdded = record;
} }
return record; return record;
} }
_insertAfter(record:CollectionChangeRecord, prevRecord:CollectionChangeRecord, _insertAfter(record: CollectionChangeRecord, prevRecord: CollectionChangeRecord,
index:int):CollectionChangeRecord { index: int): CollectionChangeRecord {
// todo(vicb) // todo(vicb)
//assert(record != prevRecord); // assert(record != prevRecord);
//assert(record._next === null); // assert(record._next === null);
//assert(record._prev === null); // assert(record._prev === null);
var next:CollectionChangeRecord = prevRecord === null ? this._itHead :prevRecord._next; var next: CollectionChangeRecord = prevRecord === null ? this._itHead : prevRecord._next;
// todo(vicb) // todo(vicb)
//assert(next != record); // assert(next != record);
//assert(prevRecord != record); // assert(prevRecord != record);
record._next = next; record._next = next;
record._prev = prevRecord; record._prev = prevRecord;
if (next === null) { if (next === null) {
@ -401,11 +390,11 @@ export class IterableChanges extends Pipe {
return record; return record;
} }
_remove(record:CollectionChangeRecord):CollectionChangeRecord { _remove(record: CollectionChangeRecord): CollectionChangeRecord {
return this._addToRemovals(this._unlink(record)); return this._addToRemovals(this._unlink(record));
} }
_unlink(record:CollectionChangeRecord):CollectionChangeRecord { _unlink(record: CollectionChangeRecord): CollectionChangeRecord {
if (this._linkedRecords !== null) { if (this._linkedRecords !== null) {
this._linkedRecords.remove(record); this._linkedRecords.remove(record);
} }
@ -414,8 +403,8 @@ export class IterableChanges extends Pipe {
var next = record._next; var next = record._next;
// todo(vicb) // todo(vicb)
//assert((record._prev = null) === null); // assert((record._prev = null) === null);
//assert((record._next = null) === null); // assert((record._next = null) === null);
if (prev === null) { if (prev === null) {
this._itHead = next; this._itHead = next;
@ -431,9 +420,9 @@ export class IterableChanges extends Pipe {
return record; return record;
} }
_addToMoves(record:CollectionChangeRecord, toIndex:int):CollectionChangeRecord { _addToMoves(record: CollectionChangeRecord, toIndex: int): CollectionChangeRecord {
// todo(vicb) // todo(vicb)
//assert(record._nextMoved === null); // assert(record._nextMoved === null);
if (record.previousIndex === toIndex) { if (record.previousIndex === toIndex) {
return record; return record;
@ -441,18 +430,18 @@ export class IterableChanges extends Pipe {
if (this._movesTail === null) { if (this._movesTail === null) {
// todo(vicb) // todo(vicb)
//assert(_movesHead === null); // assert(_movesHead === null);
this._movesTail = this._movesHead = record; this._movesTail = this._movesHead = record;
} else { } else {
// todo(vicb) // todo(vicb)
//assert(_movesTail._nextMoved === null); // assert(_movesTail._nextMoved === null);
this._movesTail = this._movesTail._nextMoved = record; this._movesTail = this._movesTail._nextMoved = record;
} }
return record; return record;
} }
_addToRemovals(record:CollectionChangeRecord):CollectionChangeRecord { _addToRemovals(record: CollectionChangeRecord): CollectionChangeRecord {
if (this._unlinkedRecords === null) { if (this._unlinkedRecords === null) {
this._unlinkedRecords = new _DuplicateMap(); this._unlinkedRecords = new _DuplicateMap();
} }
@ -462,21 +451,21 @@ export class IterableChanges extends Pipe {
if (this._removalsTail === null) { if (this._removalsTail === null) {
// todo(vicb) // todo(vicb)
//assert(_removalsHead === null); // assert(_removalsHead === null);
this._removalsTail = this._removalsHead = record; this._removalsTail = this._removalsHead = record;
record._prevRemoved = null; record._prevRemoved = null;
} else { } else {
// todo(vicb) // todo(vicb)
//assert(_removalsTail._nextRemoved === null); // assert(_removalsTail._nextRemoved === null);
//assert(record._nextRemoved === null); // assert(record._nextRemoved === null);
record._prevRemoved = this._removalsTail; record._prevRemoved = this._removalsTail;
this._removalsTail = this._removalsTail._nextRemoved = record; this._removalsTail = this._removalsTail._nextRemoved = record;
} }
return record; return record;
} }
toString():string { toString(): string {
var record:CollectionChangeRecord; var record: CollectionChangeRecord;
var list = []; var list = [];
for (record = this._itHead; record !== null; record = record._next) { for (record = this._itHead; record !== null; record = record._next) {
@ -502,10 +491,8 @@ export class IterableChanges extends Pipe {
ListWrapper.push(removals, record); ListWrapper.push(removals, record);
} }
return "collection: " + list.join(', ') + "\n" + return "collection: " + list.join(', ') + "\n" + "previous: " + previous.join(', ') + "\n" +
"previous: " + previous.join(', ') + "\n" + "additions: " + additions.join(', ') + "\n" + "moves: " + moves.join(', ') + "\n" +
"additions: " + additions.join(', ') + "\n" +
"moves: " + moves.join(', ') + "\n" +
"removals: " + removals.join(', ') + "\n"; "removals: " + removals.join(', ') + "\n";
} }
} }
@ -513,17 +500,20 @@ export class IterableChanges extends Pipe {
/** /**
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
export class CollectionChangeRecord { export class CollectionChangeRecord {
currentIndex:int; currentIndex: int;
previousIndex:int; previousIndex: int;
item; item;
_nextPrevious:CollectionChangeRecord; _nextPrevious: CollectionChangeRecord;
_prev:CollectionChangeRecord; _next:CollectionChangeRecord; _prev: CollectionChangeRecord;
_prevDup:CollectionChangeRecord; _nextDup:CollectionChangeRecord; _next: CollectionChangeRecord;
_prevRemoved:CollectionChangeRecord; _nextRemoved:CollectionChangeRecord; _prevDup: CollectionChangeRecord;
_nextAdded:CollectionChangeRecord; _nextDup: CollectionChangeRecord;
_nextMoved:CollectionChangeRecord; _prevRemoved: CollectionChangeRecord;
_nextRemoved: CollectionChangeRecord;
_nextAdded: CollectionChangeRecord;
_nextMoved: CollectionChangeRecord;
constructor(item) { constructor(item) {
this.currentIndex = null; this.currentIndex = null;
@ -541,18 +531,18 @@ export class CollectionChangeRecord {
this._nextMoved = null; this._nextMoved = null;
} }
toString():string { toString(): string {
return this.previousIndex === this.currentIndex ? return this.previousIndex === this.currentIndex ?
stringify(this.item) : stringify(this.item) :
stringify(this.item) + '[' + stringify(this.previousIndex) + '->' + stringify(this.item) + '[' + stringify(this.previousIndex) + '->' +
stringify(this.currentIndex) + ']'; stringify(this.currentIndex) + ']';
} }
} }
// A linked list of CollectionChangeRecords with the same CollectionChangeRecord.item // A linked list of CollectionChangeRecords with the same CollectionChangeRecord.item
class _DuplicateItemRecordList { class _DuplicateItemRecordList {
_head:CollectionChangeRecord; _head: CollectionChangeRecord;
_tail:CollectionChangeRecord; _tail: CollectionChangeRecord;
constructor() { constructor() {
this._head = null; this._head = null;
@ -564,14 +554,14 @@ class _DuplicateItemRecordList {
* *
* Note: by design all records in the list of duplicates hold the same value in record.item. * Note: by design all records in the list of duplicates hold the same value in record.item.
*/ */
add(record:CollectionChangeRecord) { add(record: CollectionChangeRecord) {
if (this._head === null) { if (this._head === null) {
this._head = this._tail = record; this._head = this._tail = record;
record._nextDup = null; record._nextDup = null;
record._prevDup = null; record._prevDup = null;
} else { } else {
// todo(vicb) // todo(vicb)
//assert(record.item == _head.item || // assert(record.item == _head.item ||
// record.item is num && record.item.isNaN && _head.item is num && _head.item.isNaN); // record.item is num && record.item.isNaN && _head.item is num && _head.item.isNaN);
this._tail._nextDup = record; this._tail._nextDup = record;
record._prevDup = this._tail; record._prevDup = this._tail;
@ -582,14 +572,14 @@ class _DuplicateItemRecordList {
// Returns a CollectionChangeRecord having CollectionChangeRecord.item == item and // Returns a CollectionChangeRecord having CollectionChangeRecord.item == item and
// CollectionChangeRecord.currentIndex >= afterIndex // CollectionChangeRecord.currentIndex >= afterIndex
get(item, afterIndex:int):CollectionChangeRecord { get(item, afterIndex: int): CollectionChangeRecord {
var record:CollectionChangeRecord; var record: CollectionChangeRecord;
for (record = this._head; record !== null; record = record._nextDup) { for (record = this._head; record !== null; record = record._nextDup) {
if ((afterIndex === null || afterIndex < record.currentIndex) && if ((afterIndex === null || afterIndex < record.currentIndex) &&
looseIdentical(record.item, item)) { looseIdentical(record.item, item)) {
return record; return record;
}
} }
}
return null; return null;
} }
@ -598,9 +588,9 @@ class _DuplicateItemRecordList {
* *
* Returns whether the list of duplicates is empty. * Returns whether the list of duplicates is empty.
*/ */
remove(record:CollectionChangeRecord):boolean { remove(record: CollectionChangeRecord): boolean {
// todo(vicb) // todo(vicb)
//assert(() { // assert(() {
// // verify that the record being removed is in the list. // // verify that the record being removed is in the list.
// for (CollectionChangeRecord cursor = _head; cursor != null; cursor = cursor._nextDup) { // for (CollectionChangeRecord cursor = _head; cursor != null; cursor = cursor._nextDup) {
// if (identical(cursor, record)) return true; // if (identical(cursor, record)) return true;
@ -608,8 +598,8 @@ class _DuplicateItemRecordList {
// return false; // return false;
//}); //});
var prev:CollectionChangeRecord = record._prevDup; var prev: CollectionChangeRecord = record._prevDup;
var next:CollectionChangeRecord = record._nextDup; var next: CollectionChangeRecord = record._nextDup;
if (prev === null) { if (prev === null) {
this._head = next; this._head = next;
} else { } else {
@ -625,12 +615,10 @@ class _DuplicateItemRecordList {
} }
class _DuplicateMap { class _DuplicateMap {
map:Map; map: Map<any, any>;
constructor() { constructor() { this.map = MapWrapper.create(); }
this.map = MapWrapper.create();
}
put(record:CollectionChangeRecord) { put(record: CollectionChangeRecord) {
// todo(vicb) handle corner cases // todo(vicb) handle corner cases
var key = getMapKey(record.item); var key = getMapKey(record.item);
@ -649,7 +637,7 @@ class _DuplicateMap {
* Use case: `[a, b, c, a, a]` if we are at index `3` which is the second `a` then asking if we * Use case: `[a, b, c, a, a]` if we are at index `3` which is the second `a` then asking if we
* have any more `a`s needs to return the last `a` not the first or second. * have any more `a`s needs to return the last `a` not the first or second.
*/ */
get(value, afterIndex = null):CollectionChangeRecord { get(value, afterIndex = null): CollectionChangeRecord {
var key = getMapKey(value); var key = getMapKey(value);
var recordList = MapWrapper.get(this.map, key); var recordList = MapWrapper.get(this.map, key);
@ -661,11 +649,11 @@ class _DuplicateMap {
* *
* The list of duplicates also is removed from the map if it gets empty. * The list of duplicates also is removed from the map if it gets empty.
*/ */
remove(record:CollectionChangeRecord):CollectionChangeRecord { remove(record: CollectionChangeRecord): CollectionChangeRecord {
var key = getMapKey(record.item); var key = getMapKey(record.item);
// todo(vicb) // todo(vicb)
//assert(this.map.containsKey(key)); // assert(this.map.containsKey(key));
var recordList:_DuplicateItemRecordList = MapWrapper.get(this.map, key); var recordList: _DuplicateItemRecordList = MapWrapper.get(this.map, key);
// Remove the list of duplicates when it gets empty // Remove the list of duplicates when it gets empty
if (recordList.remove(record)) { if (recordList.remove(record)) {
MapWrapper.delete(this.map, key); MapWrapper.delete(this.map, key);
@ -673,15 +661,9 @@ class _DuplicateMap {
return record; return record;
} }
get isEmpty():boolean { get isEmpty(): boolean { return MapWrapper.size(this.map) === 0; }
return MapWrapper.size(this.map) === 0;
}
clear() { clear() { MapWrapper.clear(this.map); }
MapWrapper.clear(this.map);
}
toString():string { toString(): string { return '_DuplicateMap(' + stringify(this.map) + ')'; }
return '_DuplicateMap(' + stringify(this.map) + ')';
}
} }

View File

@ -3,38 +3,36 @@ import {stringify, looseIdentical, isJsObject, CONST} from 'angular2/src/facade/
import {WrappedValue, Pipe, PipeFactory} from './pipe'; import {WrappedValue, Pipe, PipeFactory} from './pipe';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
/** /**
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
@CONST()
export class KeyValueChangesFactory extends PipeFactory { export class KeyValueChangesFactory extends PipeFactory {
@CONST() constructor() { super(); }
constructor() {
super();
}
supports(obj):boolean { supports(obj): boolean { return KeyValueChanges.supportsObj(obj); }
return KeyValueChanges.supportsObj(obj);
}
create(cdRef):Pipe { create(cdRef): Pipe { return new KeyValueChanges(); }
return new KeyValueChanges();
}
} }
/** /**
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
export class KeyValueChanges extends Pipe { export class KeyValueChanges extends Pipe {
_records:Map; private _records: Map<any, any>;
private _mapHead: KVChangeRecord;
_mapHead:KVChangeRecord; private _previousMapHead: KVChangeRecord;
_previousMapHead:KVChangeRecord; private _changesHead: KVChangeRecord;
_changesHead:KVChangeRecord; private _changesTail: KVChangeRecord;
_changesTail:KVChangeRecord; private _additionsHead: KVChangeRecord;
_additionsHead:KVChangeRecord; private _additionsTail: KVChangeRecord;
_additionsTail:KVChangeRecord; private _removalsHead: KVChangeRecord;
_removalsHead:KVChangeRecord; private _removalsTail: KVChangeRecord;
_removalsTail:KVChangeRecord;
constructor() { constructor() {
super(); super();
@ -49,15 +47,11 @@ export class KeyValueChanges extends Pipe {
this._removalsTail = null; this._removalsTail = null;
} }
static supportsObj(obj):boolean { static supportsObj(obj): boolean { return obj instanceof Map || isJsObject(obj); }
return obj instanceof Map || isJsObject(obj);
}
supports(obj):boolean { supports(obj): boolean { return KeyValueChanges.supportsObj(obj); }
return KeyValueChanges.supportsObj(obj);
}
transform(map){ transform(map): any {
if (this.check(map)) { if (this.check(map)) {
return WrappedValue.wrap(this); return WrappedValue.wrap(this);
} else { } else {
@ -65,54 +59,53 @@ export class KeyValueChanges extends Pipe {
} }
} }
get isDirty():boolean { get isDirty(): boolean {
return this._additionsHead !== null || return this._additionsHead !== null || this._changesHead !== null ||
this._changesHead !== null ||
this._removalsHead !== null; this._removalsHead !== null;
} }
forEachItem(fn:Function) { forEachItem(fn: Function) {
var record:KVChangeRecord; var record: KVChangeRecord;
for (record = this._mapHead; record !== null; record = record._next) { for (record = this._mapHead; record !== null; record = record._next) {
fn(record); fn(record);
} }
} }
forEachPreviousItem(fn:Function) { forEachPreviousItem(fn: Function) {
var record:KVChangeRecord; var record: KVChangeRecord;
for (record = this._previousMapHead; record !== null; record = record._nextPrevious) { for (record = this._previousMapHead; record !== null; record = record._nextPrevious) {
fn(record); fn(record);
} }
} }
forEachChangedItem(fn:Function) { forEachChangedItem(fn: Function) {
var record:KVChangeRecord; var record: KVChangeRecord;
for (record = this._changesHead; record !== null; record = record._nextChanged) { for (record = this._changesHead; record !== null; record = record._nextChanged) {
fn(record); fn(record);
} }
} }
forEachAddedItem(fn:Function){ forEachAddedItem(fn: Function) {
var record:KVChangeRecord; var record: KVChangeRecord;
for (record = this._additionsHead; record !== null; record = record._nextAdded) { for (record = this._additionsHead; record !== null; record = record._nextAdded) {
fn(record); fn(record);
} }
} }
forEachRemovedItem(fn:Function){ forEachRemovedItem(fn: Function) {
var record:KVChangeRecord; var record: KVChangeRecord;
for (record = this._removalsHead; record !== null; record = record._nextRemoved) { for (record = this._removalsHead; record !== null; record = record._nextRemoved) {
fn(record); fn(record);
} }
} }
check(map):boolean { check(map): boolean {
this._reset(); this._reset();
var records = this._records; var records = this._records;
var oldSeqRecord:KVChangeRecord = this._mapHead; var oldSeqRecord: KVChangeRecord = this._mapHead;
var lastOldSeqRecord:KVChangeRecord = null; var lastOldSeqRecord: KVChangeRecord = null;
var lastNewSeqRecord:KVChangeRecord = null; var lastNewSeqRecord: KVChangeRecord = null;
var seqChanged:boolean = false; var seqChanged: boolean = false;
this._forEach(map, (value, key) => { this._forEach(map, (value, key) => {
var newSeqRecord; var newSeqRecord;
@ -160,11 +153,9 @@ export class KeyValueChanges extends Pipe {
_reset() { _reset() {
if (this.isDirty) { if (this.isDirty) {
var record:KVChangeRecord; var record: KVChangeRecord;
// Record the state of the mapping // Record the state of the mapping
for (record = this._previousMapHead = this._mapHead; for (record = this._previousMapHead = this._mapHead; record !== null; record = record._next) {
record !== null;
record = record._next) {
record._nextPrevious = record._next; record._nextPrevious = record._next;
} }
@ -177,7 +168,7 @@ export class KeyValueChanges extends Pipe {
} }
// todo(vicb) once assert is supported // todo(vicb) once assert is supported
//assert(() { // assert(() {
// var r = _changesHead; // var r = _changesHead;
// while (r != null) { // while (r != null) {
// var nextRecord = r._nextChanged; // var nextRecord = r._nextChanged;
@ -207,7 +198,7 @@ export class KeyValueChanges extends Pipe {
} }
} }
_truncate(lastRecord:KVChangeRecord, record:KVChangeRecord) { _truncate(lastRecord: KVChangeRecord, record: KVChangeRecord) {
while (record !== null) { while (record !== null) {
if (lastRecord === null) { if (lastRecord === null) {
this._mapHead = null; this._mapHead = null;
@ -216,7 +207,7 @@ export class KeyValueChanges extends Pipe {
} }
var nextRecord = record._next; var nextRecord = record._next;
// todo(vicb) assert // todo(vicb) assert
//assert((() { // assert((() {
// record._next = null; // record._next = null;
// return true; // return true;
//})); //}));
@ -225,26 +216,25 @@ export class KeyValueChanges extends Pipe {
record = nextRecord; record = nextRecord;
} }
for (var rec:KVChangeRecord = this._removalsHead; rec !== null; rec = rec._nextRemoved) { for (var rec: KVChangeRecord = this._removalsHead; rec !== null; rec = rec._nextRemoved) {
rec.previousValue = rec.currentValue; rec.previousValue = rec.currentValue;
rec.currentValue = null; rec.currentValue = null;
MapWrapper.delete(this._records, rec.key); MapWrapper.delete(this._records, rec.key);
} }
} }
_isInRemovals(record:KVChangeRecord) { _isInRemovals(record: KVChangeRecord) {
return record === this._removalsHead || return record === this._removalsHead || record._nextRemoved !== null ||
record._nextRemoved !== null ||
record._prevRemoved !== null; record._prevRemoved !== null;
} }
_addToRemovals(record:KVChangeRecord) { _addToRemovals(record: KVChangeRecord) {
// todo(vicb) assert // todo(vicb) assert
//assert(record._next == null); // assert(record._next == null);
//assert(record._nextAdded == null); // assert(record._nextAdded == null);
//assert(record._nextChanged == null); // assert(record._nextChanged == null);
//assert(record._nextRemoved == null); // assert(record._nextRemoved == null);
//assert(record._prevRemoved == null); // assert(record._prevRemoved == null);
if (this._removalsHead === null) { if (this._removalsHead === null) {
this._removalsHead = this._removalsTail = record; this._removalsHead = this._removalsTail = record;
} else { } else {
@ -254,7 +244,7 @@ export class KeyValueChanges extends Pipe {
} }
} }
_removeFromSeq(prev:KVChangeRecord, record:KVChangeRecord) { _removeFromSeq(prev: KVChangeRecord, record: KVChangeRecord) {
var next = record._next; var next = record._next;
if (prev === null) { if (prev === null) {
this._mapHead = next; this._mapHead = next;
@ -262,17 +252,17 @@ export class KeyValueChanges extends Pipe {
prev._next = next; prev._next = next;
} }
// todo(vicb) assert // todo(vicb) assert
//assert((() { // assert((() {
// record._next = null; // record._next = null;
// return true; // return true;
//})()); //})());
} }
_removeFromRemovals(record:KVChangeRecord) { _removeFromRemovals(record: KVChangeRecord) {
// todo(vicb) assert // todo(vicb) assert
//assert(record._next == null); // assert(record._next == null);
//assert(record._nextAdded == null); // assert(record._nextAdded == null);
//assert(record._nextChanged == null); // assert(record._nextChanged == null);
var prev = record._prevRemoved; var prev = record._prevRemoved;
var next = record._nextRemoved; var next = record._nextRemoved;
@ -289,13 +279,13 @@ export class KeyValueChanges extends Pipe {
record._prevRemoved = record._nextRemoved = null; record._prevRemoved = record._nextRemoved = null;
} }
_addToAdditions(record:KVChangeRecord) { _addToAdditions(record: KVChangeRecord) {
// todo(vicb): assert // todo(vicb): assert
//assert(record._next == null); // assert(record._next == null);
//assert(record._nextAdded == null); // assert(record._nextAdded == null);
//assert(record._nextChanged == null); // assert(record._nextChanged == null);
//assert(record._nextRemoved == null); // assert(record._nextRemoved == null);
//assert(record._prevRemoved == null); // assert(record._prevRemoved == null);
if (this._additionsHead === null) { if (this._additionsHead === null) {
this._additionsHead = this._additionsTail = record; this._additionsHead = this._additionsTail = record;
} else { } else {
@ -304,12 +294,12 @@ export class KeyValueChanges extends Pipe {
} }
} }
_addToChanges(record:KVChangeRecord) { _addToChanges(record: KVChangeRecord) {
// todo(vicb) assert // todo(vicb) assert
//assert(record._nextAdded == null); // assert(record._nextAdded == null);
//assert(record._nextChanged == null); // assert(record._nextChanged == null);
//assert(record._nextRemoved == null); // assert(record._nextRemoved == null);
//assert(record._prevRemoved == null); // assert(record._prevRemoved == null);
if (this._changesHead === null) { if (this._changesHead === null) {
this._changesHead = this._changesTail = record; this._changesHead = this._changesTail = record;
} else { } else {
@ -318,13 +308,13 @@ export class KeyValueChanges extends Pipe {
} }
} }
toString():string { toString(): string {
var items = []; var items = [];
var previous = []; var previous = [];
var changes = []; var changes = [];
var additions = []; var additions = [];
var removals = []; var removals = [];
var record:KVChangeRecord; var record: KVChangeRecord;
for (record = this._mapHead; record !== null; record = record._next) { for (record = this._mapHead; record !== null; record = record._next) {
ListWrapper.push(items, stringify(record)); ListWrapper.push(items, stringify(record));
@ -342,14 +332,12 @@ export class KeyValueChanges extends Pipe {
ListWrapper.push(removals, stringify(record)); ListWrapper.push(removals, stringify(record));
} }
return "map: " + items.join(', ') + "\n" + return "map: " + items.join(', ') + "\n" + "previous: " + previous.join(', ') + "\n" +
"previous: " + previous.join(', ') + "\n" + "additions: " + additions.join(', ') + "\n" + "changes: " + changes.join(', ') + "\n" +
"additions: " + additions.join(', ') + "\n" +
"changes: " + changes.join(', ') + "\n" +
"removals: " + removals.join(', ') + "\n"; "removals: " + removals.join(', ') + "\n";
} }
_forEach(obj, fn:Function) { _forEach(obj, fn: Function) {
if (obj instanceof Map) { if (obj instanceof Map) {
MapWrapper.forEach(obj, fn); MapWrapper.forEach(obj, fn);
} else { } else {
@ -368,12 +356,12 @@ export class KVChangeRecord {
previousValue; previousValue;
currentValue; currentValue;
_nextPrevious:KVChangeRecord; _nextPrevious: KVChangeRecord;
_next:KVChangeRecord; _next: KVChangeRecord;
_nextAdded:KVChangeRecord; _nextAdded: KVChangeRecord;
_nextRemoved:KVChangeRecord; _nextRemoved: KVChangeRecord;
_prevRemoved:KVChangeRecord; _prevRemoved: KVChangeRecord;
_nextChanged:KVChangeRecord; _nextChanged: KVChangeRecord;
constructor(key) { constructor(key) {
this.key = key; this.key = key;
@ -388,10 +376,10 @@ export class KVChangeRecord {
this._nextChanged = null; this._nextChanged = null;
} }
toString():string { toString(): string {
return looseIdentical(this.previousValue, this.currentValue) ? return looseIdentical(this.previousValue, this.currentValue) ?
stringify(this.key) : stringify(this.key) :
(stringify(this.key) + '[' + stringify(this.previousValue) + '->' + (stringify(this.key) + '[' + stringify(this.previousValue) + '->' +
stringify(this.currentValue) + ']'); stringify(this.currentValue) + ']');
} }
} }

View File

@ -1,48 +0,0 @@
import {isBlank, CONST} from 'angular2/src/facade/lang';
import {Pipe, WrappedValue, PipeFactory} from './pipe';
/**
* @exportedAs angular2/pipes
*/
export class NullPipeFactory extends PipeFactory {
@CONST()
constructor() {
super();
}
supports(obj):boolean {
return NullPipe.supportsObj(obj);
}
create(cdRef):Pipe {
return new NullPipe();
}
}
/**
* @exportedAs angular2/pipes
*/
export class NullPipe extends Pipe {
called:boolean;
constructor() {
super();
this.called = false;
}
static supportsObj(obj):boolean {
return isBlank(obj);
}
supports(obj) {
return NullPipe.supportsObj(obj);
}
transform(value) {
if (! this.called) {
this.called = true;
return WrappedValue.wrap(null);
} else {
return null;
}
}
}

View File

@ -0,0 +1,43 @@
import {isBlank, CONST} from 'angular2/src/facade/lang';
import {Pipe, WrappedValue, PipeFactory} from './pipe';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
/**
* @exportedAs angular2/pipes
*/
@CONST()
export class NullPipeFactory extends PipeFactory {
constructor() { super(); }
supports(obj): boolean { return NullPipe.supportsObj(obj); }
create(cdRef): Pipe { return new NullPipe(); }
}
/**
* @exportedAs angular2/pipes
*/
export class NullPipe extends Pipe {
called: boolean;
constructor() {
super();
this.called = false;
}
static supportsObj(obj): boolean { return isBlank(obj); }
supports(obj) { return NullPipe.supportsObj(obj); }
transform(value) {
if (!this.called) {
this.called = true;
return WrappedValue.wrap(null);
} else {
return null;
}
}
}

View File

@ -1,20 +1,22 @@
import {ABSTRACT, BaseException, CONST} from 'angular2/src/facade/lang'; import {ABSTRACT, BaseException, CONST} from 'angular2/src/facade/lang';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
/** /**
* Indicates that the result of a {@link Pipe} transformation has changed even though the reference has not changed. * Indicates that the result of a {@link Pipe} transformation has changed even though the reference
*has not changed.
* *
* The wrapped value will be unwrapped by change detection, and the unwrapped value will be stored. * The wrapped value will be unwrapped by change detection, and the unwrapped value will be stored.
* *
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
export class WrappedValue { export class WrappedValue {
wrapped:any; constructor(public wrapped: any) {}
constructor(wrapped:any) { static wrap(value: any): WrappedValue {
this.wrapped = wrapped;
}
static wrap(value:any):WrappedValue {
var w = _wrappedValues[_wrappedIndex++ % 5]; var w = _wrappedValues[_wrappedIndex++ % 5];
w.wrapped = value; w.wrapped = value;
return w; return w;
@ -53,26 +55,25 @@ var _wrappedIndex = 0;
* @exportedAs angular2/pipes * @exportedAs angular2/pipes
*/ */
export class Pipe { export class Pipe {
supports(obj):boolean {return false;} supports(obj): boolean { return false; }
onDestroy() {} onDestroy() {}
transform(value:any):any {return null;} transform(value: any): any { return null; }
} }
@ABSTRACT() // TODO: vsavkin: make it an interface
@CONST()
export class PipeFactory { export class PipeFactory {
@CONST() supports(obs): boolean {
constructor() { _abstract();
return false;
} }
supports(obs):boolean { create(cdRef): Pipe {
return _abstract(); _abstract();
} return null;
create(cdRef):Pipe {
return _abstract();
} }
} }
function _abstract() { function _abstract() {
return new BaseException('This method is abstract'); throw new BaseException('This method is abstract');
} }

View File

@ -1,25 +1,25 @@
import {List, ListWrapper} from 'angular2/src/facade/collection'; import {List, ListWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent, BaseException, CONST} from 'angular2/src/facade/lang'; import {isBlank, isPresent, BaseException, CONST} from 'angular2/src/facade/lang';
import {Pipe} from './pipe'; import {Pipe} from './pipe';
import {Injectable} from 'angular2/src/di/annotations_impl'; import {Injectable} from 'angular2/src/di/decorators';
import {ChangeDetectorRef} from '../change_detector_ref'; import {ChangeDetectorRef} from '../change_detector_ref';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
@Injectable() @Injectable()
export class PipeRegistry { export class PipeRegistry {
config; constructor(public config) {}
constructor(config){ get(type: string, obj, cdRef: ChangeDetectorRef): Pipe {
this.config = config;
}
get(type:string, obj, cdRef:ChangeDetectorRef):Pipe {
var listOfConfigs = this.config[type]; var listOfConfigs = this.config[type];
if (isBlank(listOfConfigs)) { if (isBlank(listOfConfigs)) {
throw new BaseException(`Cannot find '${type}' pipe supporting object '${obj}'`); throw new BaseException(`Cannot find '${type}' pipe supporting object '${obj}'`);
} }
var matchingConfig = ListWrapper.find(listOfConfigs, var matchingConfig = ListWrapper.find(listOfConfigs, (pipeConfig) => pipeConfig.supports(obj));
(pipeConfig) => pipeConfig.supports(obj));
if (isBlank(matchingConfig)) { if (isBlank(matchingConfig)) {
throw new BaseException(`Cannot find '${type}' pipe supporting object '${obj}'`); throw new BaseException(`Cannot find '${type}' pipe supporting object '${obj}'`);

View File

@ -1,368 +0,0 @@
import {isPresent, isBlank, BaseException, Type, isString} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {
AccessMember,
Assignment,
AST,
ASTWithSource,
AstVisitor,
Binary,
Chain,
Conditional,
Pipe,
FunctionCall,
ImplicitReceiver,
Interpolation,
KeyedAccess,
LiteralArray,
LiteralMap,
LiteralPrimitive,
MethodCall,
PrefixNot
} from './parser/ast';
import {ChangeDispatcher, ChangeDetector, ProtoChangeDetector} from './interfaces';
import {ChangeDetectionUtil} from './change_detection_util';
import {DynamicChangeDetector} from './dynamic_change_detector';
import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';
import {PipeRegistry} from './pipes/pipe_registry';
import {BindingRecord} from './binding_record';
import {DirectiveRecord, DirectiveIndex} from './directive_record';
import {coalesce} from './coalesce';
import {
ProtoRecord,
RECORD_TYPE_SELF,
RECORD_TYPE_PROPERTY,
RECORD_TYPE_LOCAL,
RECORD_TYPE_INVOKE_METHOD,
RECORD_TYPE_CONST,
RECORD_TYPE_INVOKE_CLOSURE,
RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_PIPE,
RECORD_TYPE_BINDING_PIPE,
RECORD_TYPE_INTERPOLATE
} from './proto_record';
export class DynamicProtoChangeDetector extends ProtoChangeDetector {
_pipeRegistry:PipeRegistry;
_records:List<ProtoRecord>;
_bindingRecords:List<BindingRecord>;
_variableBindings:List<string>;
_directiveRecords:List<DirectiveRecord>;
_changeControlStrategy:string;
constructor(pipeRegistry:PipeRegistry, bindingRecords:List, variableBindings:List, directiveRecords:List, changeControlStrategy:string) {
super();
this._pipeRegistry = pipeRegistry;
this._bindingRecords = bindingRecords;
this._variableBindings = variableBindings;
this._directiveRecords = directiveRecords;
this._changeControlStrategy = changeControlStrategy;
}
instantiate(dispatcher:any) {
this._createRecordsIfNecessary();
return new DynamicChangeDetector(this._changeControlStrategy, dispatcher,
this._pipeRegistry, this._records, this._directiveRecords);
}
_createRecordsIfNecessary() {
if (isBlank(this._records)) {
var recordBuilder = new ProtoRecordBuilder();
ListWrapper.forEach(this._bindingRecords, (b) => {
recordBuilder.addAst(b, this._variableBindings);
});
this._records = coalesce(recordBuilder.records);
}
}
}
var _jitProtoChangeDetectorClassCounter:number = 0;
export class JitProtoChangeDetector extends ProtoChangeDetector {
_factory:Function;
_pipeRegistry;
_bindingRecords:List<BindingRecord>;
_variableBindings:List<string>;
_directiveRecords:List<DirectiveRecord>;
_changeControlStrategy:string;
constructor(pipeRegistry, bindingRecords:List, variableBindings:List, directiveRecords:List, changeControlStrategy:string) {
super();
this._pipeRegistry = pipeRegistry;
this._factory = null;
this._bindingRecords = bindingRecords;
this._variableBindings = variableBindings;
this._directiveRecords = directiveRecords;
this._changeControlStrategy = changeControlStrategy;
}
instantiate(dispatcher:any) {
this._createFactoryIfNecessary();
return this._factory(dispatcher, this._pipeRegistry);
}
_createFactoryIfNecessary() {
if (isBlank(this._factory)) {
var recordBuilder = new ProtoRecordBuilder();
ListWrapper.forEach(this._bindingRecords, (b) => {
recordBuilder.addAst(b, this._variableBindings);
});
var c = _jitProtoChangeDetectorClassCounter++;
var records = coalesce(recordBuilder.records);
var typeName = `ChangeDetector${c}`;
this._factory = new ChangeDetectorJITGenerator(typeName, this._changeControlStrategy, records,
this._directiveRecords).generate();
}
}
}
class ProtoRecordBuilder {
records:List<ProtoRecord>;
constructor() {
this.records = [];
}
addAst(b:BindingRecord, variableBindings:List = null) {
var last = ListWrapper.last(this.records);
if (isPresent(last) && last.bindingRecord.directiveRecord == b.directiveRecord) {
last.lastInDirective = false;
}
var pr = _ConvertAstIntoProtoRecords.convert(b, this.records.length, variableBindings);
if (! ListWrapper.isEmpty(pr)) {
var last = ListWrapper.last(pr);
last.lastInBinding = true;
last.lastInDirective = true;
this.records = ListWrapper.concat(this.records, pr);
}
}
}
class _ConvertAstIntoProtoRecords {
protoRecords:List;
bindingRecord:BindingRecord;
variableBindings:List;
contextIndex:number;
expressionAsString:string;
constructor(bindingRecord:BindingRecord, contextIndex:number, expressionAsString:string, variableBindings:List) {
this.protoRecords = [];
this.bindingRecord = bindingRecord;
this.contextIndex = contextIndex;
this.expressionAsString = expressionAsString;
this.variableBindings = variableBindings;
}
static convert(b:BindingRecord, contextIndex:number, variableBindings:List) {
var c = new _ConvertAstIntoProtoRecords(b, contextIndex, b.ast.toString(), variableBindings);
b.ast.visit(c);
return c.protoRecords;
}
visitImplicitReceiver(ast:ImplicitReceiver) {
return this.bindingRecord.implicitReceiver;
}
visitInterpolation(ast:Interpolation) {
var args = this._visitAll(ast.expressions);
return this._addRecord(RECORD_TYPE_INTERPOLATE, "interpolate", _interpolationFn(ast.strings),
args, ast.strings, 0);
}
visitLiteralPrimitive(ast:LiteralPrimitive) {
return this._addRecord(RECORD_TYPE_CONST, "literal", ast.value, [], null, 0);
}
visitAccessMember(ast:AccessMember) {
var receiver = ast.receiver.visit(this);
if (isPresent(this.variableBindings) &&
ListWrapper.contains(this.variableBindings, ast.name) &&
ast.receiver instanceof ImplicitReceiver) {
return this._addRecord(RECORD_TYPE_LOCAL, ast.name, ast.name, [], null, receiver);
} else {
return this._addRecord(RECORD_TYPE_PROPERTY, ast.name, ast.getter, [], null, receiver);
}
}
visitMethodCall(ast:MethodCall) {;
var receiver = ast.receiver.visit(this);
var args = this._visitAll(ast.args);
if (isPresent(this.variableBindings) && ListWrapper.contains(this.variableBindings, ast.name)) {
var target = this._addRecord(RECORD_TYPE_LOCAL, ast.name, ast.name, [], null, receiver);
return this._addRecord(RECORD_TYPE_INVOKE_CLOSURE, "closure", null, args, null, target);
} else {
return this._addRecord(RECORD_TYPE_INVOKE_METHOD, ast.name, ast.fn, args, null, receiver);
}
}
visitFunctionCall(ast:FunctionCall) {
var target = ast.target.visit(this);
var args = this._visitAll(ast.args);
return this._addRecord(RECORD_TYPE_INVOKE_CLOSURE, "closure", null, args, null, target);
}
visitLiteralArray(ast:LiteralArray) {
var primitiveName = `arrayFn${ast.expressions.length}`;
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, primitiveName, _arrayFn(ast.expressions.length),
this._visitAll(ast.expressions), null, 0);
}
visitLiteralMap(ast:LiteralMap) {
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, _mapPrimitiveName(ast.keys),
ChangeDetectionUtil.mapFn(ast.keys), this._visitAll(ast.values), null, 0);
}
visitBinary(ast:Binary) {
var left = ast.left.visit(this);
var right = ast.right.visit(this);
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, _operationToPrimitiveName(ast.operation),
_operationToFunction(ast.operation), [left, right], null, 0);
}
visitPrefixNot(ast:PrefixNot) {
var exp = ast.expression.visit(this)
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, "operation_negate",
ChangeDetectionUtil.operation_negate, [exp], null, 0);
}
visitConditional(ast:Conditional) {
var c = ast.condition.visit(this);
var t = ast.trueExp.visit(this);
var f = ast.falseExp.visit(this);
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, "cond",
ChangeDetectionUtil.cond, [c,t,f], null, 0);
}
visitPipe(ast:Pipe) {
var value = ast.exp.visit(this);
var type = ast.inBinding ? RECORD_TYPE_BINDING_PIPE : RECORD_TYPE_PIPE;
return this._addRecord(type, ast.name, ast.name, [], null, value);
}
visitKeyedAccess(ast:KeyedAccess) {
var obj = ast.obj.visit(this);
var key = ast.key.visit(this);
return this._addRecord(RECORD_TYPE_KEYED_ACCESS, "keyedAccess",
ChangeDetectionUtil.keyedAccess, [key], null, obj);
}
_visitAll(asts:List) {
var res = ListWrapper.createFixedSize(asts.length);
for (var i = 0; i < asts.length; ++i) {
res[i] = asts[i].visit(this);
}
return res;
}
_addRecord(type, name, funcOrValue, args, fixedArgs, context) {
var selfIndex = ++ this.contextIndex;
if (context instanceof DirectiveIndex) {
ListWrapper.push(this.protoRecords,
new ProtoRecord(type, name, funcOrValue, args, fixedArgs, -1, context, selfIndex,
this.bindingRecord, this.expressionAsString, false, false));
} else {
ListWrapper.push(this.protoRecords,
new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, null, selfIndex,
this.bindingRecord, this.expressionAsString, false, false));
}
return selfIndex;
}
}
function _arrayFn(length:number):Function {
switch (length) {
case 0: return ChangeDetectionUtil.arrayFn0;
case 1: return ChangeDetectionUtil.arrayFn1;
case 2: return ChangeDetectionUtil.arrayFn2;
case 3: return ChangeDetectionUtil.arrayFn3;
case 4: return ChangeDetectionUtil.arrayFn4;
case 5: return ChangeDetectionUtil.arrayFn5;
case 6: return ChangeDetectionUtil.arrayFn6;
case 7: return ChangeDetectionUtil.arrayFn7;
case 8: return ChangeDetectionUtil.arrayFn8;
case 9: return ChangeDetectionUtil.arrayFn9;
default: throw new BaseException(`Does not support literal maps with more than 9 elements`);
}
}
function _mapPrimitiveName(keys:List) {
var stringifiedKeys = ListWrapper.join(
ListWrapper.map(keys, (k) => isString(k) ? `"${k}"` : `${k}`),
", ");
return `mapFn([${stringifiedKeys}])`;
}
function _operationToPrimitiveName(operation:string):string {
switch(operation) {
case '+' : return "operation_add";
case '-' : return "operation_subtract";
case '*' : return "operation_multiply";
case '/' : return "operation_divide";
case '%' : return "operation_remainder";
case '==' : return "operation_equals";
case '!=' : return "operation_not_equals";
case '<' : return "operation_less_then";
case '>' : return "operation_greater_then";
case '<=' : return "operation_less_or_equals_then";
case '>=' : return "operation_greater_or_equals_then";
case '&&' : return "operation_logical_and";
case '||' : return "operation_logical_or";
default: throw new BaseException(`Unsupported operation ${operation}`);
}
}
function _operationToFunction(operation:string):Function {
switch(operation) {
case '+' : return ChangeDetectionUtil.operation_add;
case '-' : return ChangeDetectionUtil.operation_subtract;
case '*' : return ChangeDetectionUtil.operation_multiply;
case '/' : return ChangeDetectionUtil.operation_divide;
case '%' : return ChangeDetectionUtil.operation_remainder;
case '==' : return ChangeDetectionUtil.operation_equals;
case '!=' : return ChangeDetectionUtil.operation_not_equals;
case '<' : return ChangeDetectionUtil.operation_less_then;
case '>' : return ChangeDetectionUtil.operation_greater_then;
case '<=' : return ChangeDetectionUtil.operation_less_or_equals_then;
case '>=' : return ChangeDetectionUtil.operation_greater_or_equals_then;
case '&&' : return ChangeDetectionUtil.operation_logical_and;
case '||' : return ChangeDetectionUtil.operation_logical_or;
default: throw new BaseException(`Unsupported operation ${operation}`);
}
}
function s(v) {
return isPresent(v) ? `${v}` : '';
}
function _interpolationFn(strings:List) {
var length = strings.length;
var c0 = length > 0 ? strings[0] : null;
var c1 = length > 1 ? strings[1] : null;
var c2 = length > 2 ? strings[2] : null;
var c3 = length > 3 ? strings[3] : null;
var c4 = length > 4 ? strings[4] : null;
var c5 = length > 5 ? strings[5] : null;
var c6 = length > 6 ? strings[6] : null;
var c7 = length > 7 ? strings[7] : null;
var c8 = length > 8 ? strings[8] : null;
var c9 = length > 9 ? strings[9] : null;
switch (length - 1) {
case 1: return (a1) => c0 + s(a1) + c1;
case 2: return (a1, a2) => c0 + s(a1) + c1 + s(a2) + c2;
case 3: return (a1, a2, a3) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3;
case 4: return (a1, a2, a3, a4) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4;
case 5: return (a1, a2, a3, a4, a5) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5;
case 6: return (a1, a2, a3, a4, a5, a6) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6;
case 7: return (a1, a2, a3, a4, a5, a6, a7) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7;
case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7 + s(a8) + c8;
case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7 + s(a8) + c8 + s(a9) + c9;
default: throw new BaseException(`Does not support more than 9 expressions`);
}
}

View File

@ -0,0 +1,406 @@
import {isPresent, isBlank, BaseException, Type, isString} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {
AccessMember,
Assignment,
AST,
ASTWithSource,
AstVisitor,
Binary,
Chain,
Conditional,
Pipe,
FunctionCall,
ImplicitReceiver,
Interpolation,
KeyedAccess,
LiteralArray,
LiteralMap,
LiteralPrimitive,
MethodCall,
PrefixNot
} from './parser/ast';
import {ChangeDispatcher, ChangeDetector, ProtoChangeDetector} from './interfaces';
import {ChangeDetectionUtil} from './change_detection_util';
import {DynamicChangeDetector} from './dynamic_change_detector';
import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';
import {PipeRegistry} from './pipes/pipe_registry';
import {BindingRecord} from './binding_record';
import {DirectiveRecord, DirectiveIndex} from './directive_record';
import {coalesce} from './coalesce';
import {
ProtoRecord,
RECORD_TYPE_SELF,
RECORD_TYPE_PROPERTY,
RECORD_TYPE_LOCAL,
RECORD_TYPE_INVOKE_METHOD,
RECORD_TYPE_CONST,
RECORD_TYPE_INVOKE_CLOSURE,
RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_PIPE,
RECORD_TYPE_BINDING_PIPE,
RECORD_TYPE_INTERPOLATE
} from './proto_record';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
export class DynamicProtoChangeDetector extends ProtoChangeDetector {
_records: List<ProtoRecord>;
constructor(private _pipeRegistry: PipeRegistry, private _bindingRecords: List<any>,
private _variableBindings: List<any>, private _directiveRecords: List<any>,
private _changeControlStrategy: string) {
super();
}
instantiate(dispatcher: any) {
this._createRecordsIfNecessary();
return new DynamicChangeDetector(this._changeControlStrategy, dispatcher, this._pipeRegistry,
this._records, this._directiveRecords);
}
_createRecordsIfNecessary() {
if (isBlank(this._records)) {
var recordBuilder = new ProtoRecordBuilder();
ListWrapper.forEach(this._bindingRecords,
(b) => { recordBuilder.addAst(b, this._variableBindings); });
this._records = coalesce(recordBuilder.records);
}
}
}
var _jitProtoChangeDetectorClassCounter: number = 0;
export class JitProtoChangeDetector extends ProtoChangeDetector {
_factory: Function;
constructor(private _pipeRegistry, private _bindingRecords: List<any>,
private _variableBindings: List<any>, private _directiveRecords: List<any>,
private _changeControlStrategy: string) {
super();
this._factory = null;
}
instantiate(dispatcher: any) {
this._createFactoryIfNecessary();
return this._factory(dispatcher, this._pipeRegistry);
}
_createFactoryIfNecessary() {
if (isBlank(this._factory)) {
var recordBuilder = new ProtoRecordBuilder();
ListWrapper.forEach(this._bindingRecords,
(b) => { recordBuilder.addAst(b, this._variableBindings); });
var c = _jitProtoChangeDetectorClassCounter++;
var records = coalesce(recordBuilder.records);
var typeName = `ChangeDetector${c}`;
this._factory = new ChangeDetectorJITGenerator(typeName, this._changeControlStrategy, records,
this._directiveRecords)
.generate();
}
}
}
class ProtoRecordBuilder {
records: List<ProtoRecord>;
constructor() { this.records = []; }
addAst(b: BindingRecord, variableBindings: List < any >= null) {
var last = ListWrapper.last(this.records);
if (isPresent(last) && last.bindingRecord.directiveRecord == b.directiveRecord) {
last.lastInDirective = false;
}
var pr = _ConvertAstIntoProtoRecords.convert(b, this.records.length, variableBindings);
if (!ListWrapper.isEmpty(pr)) {
var last = ListWrapper.last(pr);
last.lastInBinding = true;
last.lastInDirective = true;
this.records = ListWrapper.concat(this.records, pr);
}
}
}
class _ConvertAstIntoProtoRecords {
protoRecords: List<any>;
constructor(private bindingRecord: BindingRecord, private contextIndex: number,
private expressionAsString: string, private variableBindings: List<any>) {
this.protoRecords = [];
}
static convert(b: BindingRecord, contextIndex: number, variableBindings: List<any>) {
var c = new _ConvertAstIntoProtoRecords(b, contextIndex, b.ast.toString(), variableBindings);
b.ast.visit(c);
return c.protoRecords;
}
visitImplicitReceiver(ast: ImplicitReceiver) { return this.bindingRecord.implicitReceiver; }
visitInterpolation(ast: Interpolation) {
var args = this._visitAll(ast.expressions);
return this._addRecord(RECORD_TYPE_INTERPOLATE, "interpolate", _interpolationFn(ast.strings),
args, ast.strings, 0);
}
visitLiteralPrimitive(ast: LiteralPrimitive) {
return this._addRecord(RECORD_TYPE_CONST, "literal", ast.value, [], null, 0);
}
visitAccessMember(ast: AccessMember) {
var receiver = ast.receiver.visit(this);
if (isPresent(this.variableBindings) && ListWrapper.contains(this.variableBindings, ast.name) &&
ast.receiver instanceof
ImplicitReceiver) {
return this._addRecord(RECORD_TYPE_LOCAL, ast.name, ast.name, [], null, receiver);
} else {
return this._addRecord(RECORD_TYPE_PROPERTY, ast.name, ast.getter, [], null, receiver);
}
}
visitMethodCall(ast: MethodCall) {
;
var receiver = ast.receiver.visit(this);
var args = this._visitAll(ast.args);
if (isPresent(this.variableBindings) && ListWrapper.contains(this.variableBindings, ast.name)) {
var target = this._addRecord(RECORD_TYPE_LOCAL, ast.name, ast.name, [], null, receiver);
return this._addRecord(RECORD_TYPE_INVOKE_CLOSURE, "closure", null, args, null, target);
} else {
return this._addRecord(RECORD_TYPE_INVOKE_METHOD, ast.name, ast.fn, args, null, receiver);
}
}
visitFunctionCall(ast: FunctionCall) {
var target = ast.target.visit(this);
var args = this._visitAll(ast.args);
return this._addRecord(RECORD_TYPE_INVOKE_CLOSURE, "closure", null, args, null, target);
}
visitLiteralArray(ast: LiteralArray) {
var primitiveName = `arrayFn${ast.expressions.length}`;
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, primitiveName,
_arrayFn(ast.expressions.length), this._visitAll(ast.expressions), null,
0);
}
visitLiteralMap(ast: LiteralMap) {
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, _mapPrimitiveName(ast.keys),
ChangeDetectionUtil.mapFn(ast.keys), this._visitAll(ast.values), null,
0);
}
visitBinary(ast: Binary) {
var left = ast.left.visit(this);
var right = ast.right.visit(this);
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, _operationToPrimitiveName(ast.operation),
_operationToFunction(ast.operation), [left, right], null, 0);
}
visitPrefixNot(ast: PrefixNot) {
var exp = ast.expression.visit(this);
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, "operation_negate",
ChangeDetectionUtil.operation_negate, [exp], null, 0);
}
visitConditional(ast: Conditional) {
var c = ast.condition.visit(this);
var t = ast.trueExp.visit(this);
var f = ast.falseExp.visit(this);
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, "cond", ChangeDetectionUtil.cond, [c, t, f],
null, 0);
}
visitPipe(ast: Pipe) {
var value = ast.exp.visit(this);
var type = ast.inBinding ? RECORD_TYPE_BINDING_PIPE : RECORD_TYPE_PIPE;
return this._addRecord(type, ast.name, ast.name, [], null, value);
}
visitKeyedAccess(ast: KeyedAccess) {
var obj = ast.obj.visit(this);
var key = ast.key.visit(this);
return this._addRecord(RECORD_TYPE_KEYED_ACCESS, "keyedAccess", ChangeDetectionUtil.keyedAccess,
[key], null, obj);
}
_visitAll(asts: List<any>) {
var res = ListWrapper.createFixedSize(asts.length);
for (var i = 0; i < asts.length; ++i) {
res[i] = asts[i].visit(this);
}
return res;
}
_addRecord(type, name, funcOrValue, args, fixedArgs, context) {
var selfIndex = ++this.contextIndex;
if (context instanceof DirectiveIndex) {
ListWrapper.push(
this.protoRecords,
new ProtoRecord(type, name, funcOrValue, args, fixedArgs, -1, context, selfIndex,
this.bindingRecord, this.expressionAsString, false, false));
} else {
ListWrapper.push(
this.protoRecords,
new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, null, selfIndex,
this.bindingRecord, this.expressionAsString, false, false));
}
return selfIndex;
}
}
function _arrayFn(length: number): Function {
switch (length) {
case 0:
return ChangeDetectionUtil.arrayFn0;
case 1:
return ChangeDetectionUtil.arrayFn1;
case 2:
return ChangeDetectionUtil.arrayFn2;
case 3:
return ChangeDetectionUtil.arrayFn3;
case 4:
return ChangeDetectionUtil.arrayFn4;
case 5:
return ChangeDetectionUtil.arrayFn5;
case 6:
return ChangeDetectionUtil.arrayFn6;
case 7:
return ChangeDetectionUtil.arrayFn7;
case 8:
return ChangeDetectionUtil.arrayFn8;
case 9:
return ChangeDetectionUtil.arrayFn9;
default:
throw new BaseException(`Does not support literal maps with more than 9 elements`);
}
}
function _mapPrimitiveName(keys: List<any>) {
var stringifiedKeys =
ListWrapper.join(ListWrapper.map(keys, (k) => isString(k) ? `"${k}"` : `${k}`), ", ");
return `mapFn([${stringifiedKeys}])`;
}
function _operationToPrimitiveName(operation: string): string {
switch (operation) {
case '+':
return "operation_add";
case '-':
return "operation_subtract";
case '*':
return "operation_multiply";
case '/':
return "operation_divide";
case '%':
return "operation_remainder";
case '==':
return "operation_equals";
case '!=':
return "operation_not_equals";
case '<':
return "operation_less_then";
case '>':
return "operation_greater_then";
case '<=':
return "operation_less_or_equals_then";
case '>=':
return "operation_greater_or_equals_then";
case '&&':
return "operation_logical_and";
case '||':
return "operation_logical_or";
default:
throw new BaseException(`Unsupported operation ${operation}`);
}
}
function _operationToFunction(operation: string): Function {
switch (operation) {
case '+':
return ChangeDetectionUtil.operation_add;
case '-':
return ChangeDetectionUtil.operation_subtract;
case '*':
return ChangeDetectionUtil.operation_multiply;
case '/':
return ChangeDetectionUtil.operation_divide;
case '%':
return ChangeDetectionUtil.operation_remainder;
case '==':
return ChangeDetectionUtil.operation_equals;
case '!=':
return ChangeDetectionUtil.operation_not_equals;
case '<':
return ChangeDetectionUtil.operation_less_then;
case '>':
return ChangeDetectionUtil.operation_greater_then;
case '<=':
return ChangeDetectionUtil.operation_less_or_equals_then;
case '>=':
return ChangeDetectionUtil.operation_greater_or_equals_then;
case '&&':
return ChangeDetectionUtil.operation_logical_and;
case '||':
return ChangeDetectionUtil.operation_logical_or;
default:
throw new BaseException(`Unsupported operation ${operation}`);
}
}
function s(v) {
return isPresent(v) ? `${v}` : '';
}
function _interpolationFn(strings: List<any>) {
var length = strings.length;
var c0 = length > 0 ? strings[0] : null;
var c1 = length > 1 ? strings[1] : null;
var c2 = length > 2 ? strings[2] : null;
var c3 = length > 3 ? strings[3] : null;
var c4 = length > 4 ? strings[4] : null;
var c5 = length > 5 ? strings[5] : null;
var c6 = length > 6 ? strings[6] : null;
var c7 = length > 7 ? strings[7] : null;
var c8 = length > 8 ? strings[8] : null;
var c9 = length > 9 ? strings[9] : null;
switch (length - 1) {
case 1:
return (a1) => c0 + s(a1) + c1;
case 2:
return (a1, a2) => c0 + s(a1) + c1 + s(a2) + c2;
case 3:
return (a1, a2, a3) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3;
case 4:
return (a1, a2, a3, a4) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4;
case 5:
return (a1, a2, a3, a4, a5) =>
c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5;
case 6:
return (a1, a2, a3, a4, a5, a6) =>
c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6;
case 7:
return (a1, a2, a3, a4, a5, a6, a7) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) +
c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7;
case 8:
return (a1, a2, a3, a4, a5, a6, a7, a8) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) +
c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7 + s(a8) +
c8;
case 9:
return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 +
s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) +
c7 + s(a8) + c8 + s(a9) + c9;
default:
throw new BaseException(`Does not support more than 9 expressions`);
}
}

View File

@ -1,66 +0,0 @@
import {List} from 'angular2/src/facade/collection';
import {BindingRecord} from './binding_record';
import {DirectiveIndex} from './directive_record';
export const RECORD_TYPE_SELF = 0;
export const RECORD_TYPE_CONST = 1;
export const RECORD_TYPE_PRIMITIVE_OP = 2;
export const RECORD_TYPE_PROPERTY = 3;
export const RECORD_TYPE_LOCAL = 4;
export const RECORD_TYPE_INVOKE_METHOD = 5;
export const RECORD_TYPE_INVOKE_CLOSURE = 6;
export const RECORD_TYPE_KEYED_ACCESS = 7;
export const RECORD_TYPE_PIPE = 8;
export const RECORD_TYPE_BINDING_PIPE = 9;
export const RECORD_TYPE_INTERPOLATE = 10;
export class ProtoRecord {
mode:number;
name:string;
funcOrValue:any;
args:List;
fixedArgs:List;
contextIndex:number;
directiveIndex:DirectiveIndex;
selfIndex:number;
bindingRecord:BindingRecord;
lastInBinding:boolean;
lastInDirective:boolean;
expressionAsString:string;
constructor(mode:number,
name:string,
funcOrValue,
args:List,
fixedArgs:List,
contextIndex:number,
directiveIndex:DirectiveIndex,
selfIndex:number,
bindingRecord:BindingRecord,
expressionAsString:string,
lastInBinding:boolean,
lastInDirective:boolean) {
this.mode = mode;
this.name = name;
this.funcOrValue = funcOrValue;
this.args = args;
this.fixedArgs = fixedArgs;
this.contextIndex = contextIndex;
this.directiveIndex = directiveIndex;
this.selfIndex = selfIndex;
this.bindingRecord = bindingRecord;
this.lastInBinding = lastInBinding;
this.lastInDirective = lastInDirective;
this.expressionAsString = expressionAsString;
}
isPureFunction():boolean {
return this.mode === RECORD_TYPE_INTERPOLATE ||
this.mode === RECORD_TYPE_PRIMITIVE_OP;
}
}

View File

@ -0,0 +1,32 @@
import {List} from 'angular2/src/facade/collection';
import {BindingRecord} from './binding_record';
import {DirectiveIndex} from './directive_record';
// HACK: workaround for Traceur behavior.
// It expects all transpiled modules to contain this marker.
// TODO: remove this when we no longer use traceur
export var __esModule = true;
export const RECORD_TYPE_SELF = 0;
export const RECORD_TYPE_CONST = 1;
export const RECORD_TYPE_PRIMITIVE_OP = 2;
export const RECORD_TYPE_PROPERTY = 3;
export const RECORD_TYPE_LOCAL = 4;
export const RECORD_TYPE_INVOKE_METHOD = 5;
export const RECORD_TYPE_INVOKE_CLOSURE = 6;
export const RECORD_TYPE_KEYED_ACCESS = 7;
export const RECORD_TYPE_PIPE = 8;
export const RECORD_TYPE_BINDING_PIPE = 9;
export const RECORD_TYPE_INTERPOLATE = 10;
export class ProtoRecord {
constructor(public mode: number, public name: string, public funcOrValue, public args: List<any>,
public fixedArgs: List<any>, public contextIndex: number,
public directiveIndex: DirectiveIndex, public selfIndex: number,
public bindingRecord: BindingRecord, public expressionAsString: string,
public lastInBinding: boolean, public lastInDirective: boolean) {}
isPureFunction(): boolean {
return this.mode === RECORD_TYPE_INTERPOLATE || this.mode === RECORD_TYPE_PRIMITIVE_OP;
}
}

View File

@ -449,7 +449,9 @@ function _constructDependencies(factoryFunction: Function, dependencies: List<an
function _dependenciesFor(typeOrFunc): List<any> { function _dependenciesFor(typeOrFunc): List<any> {
var params = reflector.parameters(typeOrFunc); var params = reflector.parameters(typeOrFunc);
if (isBlank(params)) return []; if (isBlank(params)) return [];
if (ListWrapper.any(params, (p) => isBlank(p))) throw new NoAnnotationError(typeOrFunc); if (ListWrapper.any(params, (p) => isBlank(p))) {
throw new NoAnnotationError(typeOrFunc);
}
return ListWrapper.map(params, (p) => _extractToken(typeOrFunc, p)); return ListWrapper.map(params, (p) => _extractToken(typeOrFunc, p));
} }

View File

@ -1,3 +1,5 @@
library angular2.di.decorators; library angular2.di.decorators;
/* This file is empty because, Dart does not have decorators. */ /* This file is empty because, Dart does not have decorators. */
export 'annotations.dart';

View File

@ -66,12 +66,12 @@ function _isWaiting(obj): boolean {
* @exportedAs angular2/di * @exportedAs angular2/di
*/ */
export class Injector { export class Injector {
_bindings: List<any>; private _bindings: List<any>;
_instances: List<any>; private _instances: List<any>;
_parent: Injector; private _parent: Injector;
_defaultBindings: boolean; private _defaultBindings: boolean;
_asyncStrategy: _AsyncInjectorStrategy; private _asyncStrategy: _AsyncInjectorStrategy;
_syncStrategy: _SyncInjectorStrategy; private _syncStrategy: _SyncInjectorStrategy;
/** /**
* Turns a list of binding definitions into an internal resolved list of resolved bindings. * Turns a list of binding definitions into an internal resolved list of resolved bindings.

View File

@ -52,7 +52,7 @@ export class PromiseWrapper {
export class ObservableWrapper { export class ObservableWrapper {
static subscribe(emitter: EventEmitter, onNext, onThrow = null, onReturn = null) { static subscribe(emitter: Observable, onNext, onThrow = null, onReturn = null): Object {
return emitter.observer({next: onNext, throw: onThrow, return: onReturn}); return emitter.observer({next: onNext, throw: onThrow, return: onReturn});
} }
@ -69,7 +69,7 @@ export class ObservableWrapper {
// TODO: vsavkin change to interface // TODO: vsavkin change to interface
export class Observable { export class Observable {
observer(generator: any) {} observer(generator: any): Object { return null; }
} }
/** /**

View File

@ -117,9 +117,9 @@ export class ListWrapper {
static indexOf(array: List<any>, value, startIndex = -1) { static indexOf(array: List<any>, value, startIndex = -1) {
return array.indexOf(value, startIndex); return array.indexOf(value, startIndex);
} }
static reduce<T>(list: List<T>, static reduce<T, E>(list: List<T>,
fn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, fn: (accumValue: E, currentValue: T, currentIndex: number, array: T[]) => E,
init: T) { init: E) {
return list.reduce(fn, init); return list.reduce(fn, init);
} }
static filter(array, pred: Function) { return array.filter(pred); } static filter(array, pred: Function) { return array.filter(pred); }

View File

@ -5,6 +5,9 @@ import 'types.dart';
import 'dart:mirrors'; import 'dart:mirrors';
class ReflectionCapabilities { class ReflectionCapabilities {
ReflectionCapabilities([metadataReader]) {
}
Function factory(Type type) { Function factory(Type type) {
ClassMirror classMirror = reflectType(type); ClassMirror classMirror = reflectType(type);
MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; MethodMirror ctor = classMirror.declarations[classMirror.simpleName];

View File

@ -8,37 +8,39 @@ import {GetterFn, SetterFn, MethodFn} from './types';
export var __esModule = true; export var __esModule = true;
export class ReflectionCapabilities { export class ReflectionCapabilities {
factory(type: Type): Function { private _reflect: any;
switch (type.length) {
constructor(reflect?: any) { this._reflect = isPresent(reflect) ? reflect : global.Reflect; }
factory(t: Type): Function {
switch (t.length) {
case 0: case 0:
return function() { return new type(); }; return function() { return new t(); };
case 1: case 1:
return function(a1) { return new type(a1); }; return function(a1) { return new t(a1); };
case 2: case 2:
return function(a1, a2) { return new type(a1, a2); }; return function(a1, a2) { return new t(a1, a2); };
case 3: case 3:
return function(a1, a2, a3) { return new type(a1, a2, a3); }; return function(a1, a2, a3) { return new t(a1, a2, a3); };
case 4: case 4:
return function(a1, a2, a3, a4) { return new type(a1, a2, a3, a4); }; return function(a1, a2, a3, a4) { return new t(a1, a2, a3, a4); };
case 5: case 5:
return function(a1, a2, a3, a4, a5) { return new type(a1, a2, a3, a4, a5); }; return function(a1, a2, a3, a4, a5) { return new t(a1, a2, a3, a4, a5); };
case 6: case 6:
return function(a1, a2, a3, a4, a5, a6) { return new type(a1, a2, a3, a4, a5, a6); }; return function(a1, a2, a3, a4, a5, a6) { return new t(a1, a2, a3, a4, a5, a6); };
case 7: case 7:
return function(a1, a2, a3, a4, a5, a6, a7) { return function(a1, a2, a3, a4, a5, a6, a7) { return new t(a1, a2, a3, a4, a5, a6, a7); };
return new type(a1, a2, a3, a4, a5, a6, a7);
};
case 8: case 8:
return function(a1, a2, a3, a4, a5, a6, a7, a8) { return function(a1, a2, a3, a4, a5, a6, a7, a8) {
return new type(a1, a2, a3, a4, a5, a6, a7, a8); return new t(a1, a2, a3, a4, a5, a6, a7, a8);
}; };
case 9: case 9:
return function(a1, a2, a3, a4, a5, a6, a7, a8, a9) { return function(a1, a2, a3, a4, a5, a6, a7, a8, a9) {
return new type(a1, a2, a3, a4, a5, a6, a7, a8, a9); return new t(a1, a2, a3, a4, a5, a6, a7, a8, a9);
}; };
case 10: case 10:
return function(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { return function(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) {
return new type(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); return new t(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
}; };
}; };
@ -68,9 +70,9 @@ export class ReflectionCapabilities {
if (isPresent(typeOfFunc.parameters)) { if (isPresent(typeOfFunc.parameters)) {
return typeOfFunc.parameters; return typeOfFunc.parameters;
} }
if (isPresent(global.Reflect) && isPresent(global.Reflect.getMetadata)) { if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
var paramAnnotations = global.Reflect.getMetadata('parameters', typeOfFunc); var paramAnnotations = this._reflect.getMetadata('parameters', typeOfFunc);
var paramTypes = global.Reflect.getMetadata('design:paramtypes', typeOfFunc); var paramTypes = this._reflect.getMetadata('design:paramtypes', typeOfFunc);
if (isPresent(paramTypes) || isPresent(paramAnnotations)) { if (isPresent(paramTypes) || isPresent(paramAnnotations)) {
return this._zipTypesAndAnnotaions(paramTypes, paramAnnotations); return this._zipTypesAndAnnotaions(paramTypes, paramAnnotations);
} }
@ -83,8 +85,8 @@ export class ReflectionCapabilities {
if (isPresent(typeOfFunc.annotations)) { if (isPresent(typeOfFunc.annotations)) {
return typeOfFunc.annotations; return typeOfFunc.annotations;
} }
if (isPresent(global.Reflect) && isPresent(global.Reflect.getMetadata)) { if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
var annotations = global.Reflect.getMetadata('annotations', typeOfFunc); var annotations = this._reflect.getMetadata('annotations', typeOfFunc);
if (isPresent(annotations)) return annotations; if (isPresent(annotations)) return annotations;
} }
return []; return [];

View File

@ -8,17 +8,6 @@ export function main() {
rc = new ReflectionCapabilities(); rc = new ReflectionCapabilities();
}); });
function mockReflect(mockData, cls) {
// This only makes sense for JS, but Dart passes trivially too.
if (!IS_DARTIUM) {
global.Reflect = {
'getMetadata': (key, targetCls) => {
return (targetCls == cls) ? mockData[key] : null;
}
}
}
}
function assertTestClassAnnotations(annotations) { function assertTestClassAnnotations(annotations) {
expect(annotations[0]).toBeAnInstanceOf(ClassDec1); expect(annotations[0]).toBeAnInstanceOf(ClassDec1);
expect(annotations[1]).toBeAnInstanceOf(ClassDec2); expect(annotations[1]).toBeAnInstanceOf(ClassDec2);
@ -54,23 +43,34 @@ export function main() {
expect(rc.parameters(TestClassTypesOnly)).toEqual([[P1], [P2]]); expect(rc.parameters(TestClassTypesOnly)).toEqual([[P1], [P2]]);
}); });
if (!IS_DARTIUM) { if (!IS_DARTIUM) {
// Mocking in the tests below is needed because the test runs through Traceur. // Mocking in the tests below is needed because the test runs through Traceur.
// After the switch to TS the setup will have to change, where the direct key // After the switch to TS the setup will have to change, where the direct key
// access will be mocked, and the tests below will be direct. // access will be mocked, and the tests below will be direct.
it('can read out class annotations though Reflect APIs', () => { it('can read out class annotations though Reflect APIs', () => {
mockReflect(mockDataForTestClassDec, TestClassDec); var rc = new ReflectionCapabilities({
'getMetadata': (key, targetCls) => {
return (targetCls == TestClassDec) ? mockDataForTestClassDec[key] : null;
}
});
assertTestClassAnnotations(rc.annotations(TestClassDec)); assertTestClassAnnotations(rc.annotations(TestClassDec));
}); });
it('can read out parameter annotations though Reflect APIs', () => { it('can read out parameter annotations though Reflect APIs', () => {
mockReflect(mockDataForTestClassDec, TestClassDec); var rc = new ReflectionCapabilities({
'getMetadata': (key, targetCls) => {
return (targetCls == TestClassDec) ? mockDataForTestClassDec[key] : null;
}
});
assertTestClassParameters(rc.parameters(TestClassDec)); assertTestClassParameters(rc.parameters(TestClassDec));
}); });
it('can read out parameter annotations though Reflect APIs for types only class', () => { it('can read out parameter annotations though Reflect APIs for types only class', () => {
mockReflect(mockDataForTestClassTypesOnly, TestClassTypesOnlyDec); var rc = new ReflectionCapabilities({
'getMetadata': (key, targetCls) => {
return (targetCls == TestClassTypesOnlyDec) ? mockDataForTestClassTypesOnly[key] : null;
}
});
expect(rc.parameters(TestClassTypesOnlyDec)).toEqual([[P1], [P2]]); expect(rc.parameters(TestClassTypesOnlyDec)).toEqual([[P1], [P2]]);
}); });
} }

View File

@ -100,6 +100,7 @@ module.exports = function makeNodeTree(destinationPath) {
var typescriptTree = compileWithTypescript(modulesTree, { var typescriptTree = compileWithTypescript(modulesTree, {
allowNonTsExtensions: false, allowNonTsExtensions: false,
emitDecoratorMetadata: true,
declaration: true, declaration: true,
mapRoot: '', /* force sourcemaps to use relative path */ mapRoot: '', /* force sourcemaps to use relative path */
module: 'commonjs', module: 'commonjs',

View File

@ -5,6 +5,7 @@ var minijasminenode2 = require('minijasminenode2');
var path = require('path'); var path = require('path');
// Require traceur to exposes $traceurRuntime on global context so that CJS files can run // Require traceur to exposes $traceurRuntime on global context so that CJS files can run
require('traceur/bin/traceur-runtime.js'); require('traceur/bin/traceur-runtime.js');
require('reflect-metadata/Reflect');
glob(process.argv[2], function (error, specFiles) { glob(process.argv[2], function (error, specFiles) {
minijasminenode2.executeSpecs({ minijasminenode2.executeSpecs({