feat(change_detection): ensure that expression do not change after they have been checked
This commit is contained in:
@ -6,14 +6,37 @@ import {ListWrapper, List} from 'facade/collection';
|
||||
export * from './record';
|
||||
export * from './record_range'
|
||||
|
||||
class ExpressionChangedAfterItHasBeenChecked extends Error {
|
||||
message:string;
|
||||
|
||||
constructor(record:Record) {
|
||||
this.message = `Expression '${record.expressionAsString()}' has changed after it was checked. ` +
|
||||
`Previous value: '${record.previousValue}'. Current value: '${record.currentValue}'`;
|
||||
}
|
||||
|
||||
toString():string {
|
||||
return this.message;
|
||||
}
|
||||
}
|
||||
|
||||
export class ChangeDetector {
|
||||
_rootRecordRange:RecordRange;
|
||||
_enforceNoNewChanges:boolean;
|
||||
|
||||
constructor(recordRange:RecordRange) {
|
||||
constructor(recordRange:RecordRange, enforceNoNewChanges:boolean = false) {
|
||||
this._rootRecordRange = recordRange;
|
||||
this._enforceNoNewChanges = enforceNoNewChanges;
|
||||
}
|
||||
|
||||
detectChanges():int {
|
||||
var count = this._detectChanges(false);
|
||||
if (this._enforceNoNewChanges) {
|
||||
this._detectChanges(true)
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
_detectChanges(throwOnChange:boolean):int {
|
||||
var count = 0;
|
||||
var updatedRecords = null;
|
||||
var record = this._rootRecordRange.findFirstEnabledRecord();
|
||||
@ -23,6 +46,7 @@ export class ChangeDetector {
|
||||
if (record.check()) {
|
||||
count++;
|
||||
if (record.terminatesExpression()) {
|
||||
if (throwOnChange) throw new ExpressionChangedAfterItHasBeenChecked(record);
|
||||
currentRange = record.recordRange;
|
||||
currentGroup = record.groupMemento();
|
||||
updatedRecords = this._addRecord(updatedRecords, record);
|
||||
|
@ -17,6 +17,10 @@ export class AST {
|
||||
|
||||
visit(visitor, args) {
|
||||
}
|
||||
|
||||
toString():string {
|
||||
return "AST";
|
||||
}
|
||||
}
|
||||
|
||||
export class EmptyExpr extends AST {
|
||||
@ -378,13 +382,33 @@ export class FunctionCall extends AST {
|
||||
}
|
||||
}
|
||||
|
||||
export class ASTWithSource {
|
||||
export class ASTWithSource extends AST {
|
||||
ast:AST;
|
||||
source:string;
|
||||
constructor(ast:AST, source:string) {
|
||||
this.source = source;
|
||||
this.ast = ast;
|
||||
}
|
||||
|
||||
eval(context) {
|
||||
return this.ast.eval(context);
|
||||
}
|
||||
|
||||
get isAssignable() {
|
||||
return this.ast.isAssignable;
|
||||
}
|
||||
|
||||
assign(context, value) {
|
||||
return this.ast.assign(context, value);
|
||||
}
|
||||
|
||||
visit(visitor, args) {
|
||||
return this.ast.visit(visitor, args);
|
||||
}
|
||||
|
||||
toString():string {
|
||||
return this.source;
|
||||
}
|
||||
}
|
||||
|
||||
export class TemplateBinding {
|
||||
|
@ -35,6 +35,7 @@ export class ProtoRecord {
|
||||
name:string;
|
||||
dest:any;
|
||||
groupMemento:any;
|
||||
expressionAsString:string;
|
||||
|
||||
next:ProtoRecord;
|
||||
|
||||
@ -46,7 +47,8 @@ export class ProtoRecord {
|
||||
arity:int,
|
||||
name:string,
|
||||
dest,
|
||||
groupMemento) {
|
||||
groupMemento,
|
||||
expressionAsString:string) {
|
||||
|
||||
this.recordRange = recordRange;
|
||||
this._mode = mode;
|
||||
@ -55,6 +57,7 @@ export class ProtoRecord {
|
||||
this.name = name;
|
||||
this.dest = dest;
|
||||
this.groupMemento = groupMemento;
|
||||
this.expressionAsString = expressionAsString;
|
||||
|
||||
this.next = null;
|
||||
// The concrete Record instantiated from this ProtoRecord
|
||||
@ -341,6 +344,10 @@ export class Record {
|
||||
return this.protoRecord.dest;
|
||||
}
|
||||
|
||||
expressionAsString() {
|
||||
return this.protoRecord.expressionAsString;
|
||||
}
|
||||
|
||||
groupMemento() {
|
||||
return isPresent(this.protoRecord) ? this.protoRecord.groupMemento : null;
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ export class ProtoRecordRange {
|
||||
/**
|
||||
* Parses [ast] into [ProtoRecord]s and adds them to [ProtoRecordRange].
|
||||
*
|
||||
* @param ast The expression to watch
|
||||
* @param astWithSource The expression to watch
|
||||
* @param expressionMemento an opaque object which will be passed to ChangeDispatcher on
|
||||
* detecting a change.
|
||||
* @param groupMemento
|
||||
@ -62,7 +62,6 @@ export class ProtoRecordRange {
|
||||
if (content) {
|
||||
ast = new Collection(ast);
|
||||
}
|
||||
|
||||
this.recordCreator.createRecordsFromAST(ast, expressionMemento, groupMemento);
|
||||
}
|
||||
|
||||
@ -318,11 +317,13 @@ class ProtoRecordCreator {
|
||||
headRecord:ProtoRecord;
|
||||
tailRecord:ProtoRecord;
|
||||
groupMemento:any;
|
||||
expressionAsString:string;
|
||||
|
||||
constructor(protoRecordRange) {
|
||||
this.protoRecordRange = protoRecordRange;
|
||||
this.headRecord = null;
|
||||
this.tailRecord = null;
|
||||
this.expressionAsString = null;
|
||||
}
|
||||
|
||||
visitImplicitReceiver(ast:ImplicitReceiver, args) {
|
||||
@ -434,11 +435,13 @@ class ProtoRecordCreator {
|
||||
|
||||
createRecordsFromAST(ast:AST, expressionMemento:any, groupMemento:any){
|
||||
this.groupMemento = groupMemento;
|
||||
this.expressionAsString = ast.toString();
|
||||
ast.visit(this, expressionMemento);
|
||||
}
|
||||
|
||||
construct(recordType, funcOrValue, arity, name, dest) {
|
||||
return new ProtoRecord(this.protoRecordRange, recordType, funcOrValue, arity, name, dest, this.groupMemento);
|
||||
return new ProtoRecord(this.protoRecordRange, recordType, funcOrValue, arity,
|
||||
name, dest, this.groupMemento, this.expressionAsString);
|
||||
}
|
||||
|
||||
add(protoRecord:ProtoRecord) {
|
||||
|
Reference in New Issue
Block a user