fix(change_detection): throw ChangeDetectionError in JIT mode

This commit is contained in:
vsavkin 2015-07-05 14:46:35 -07:00
parent d2774421e8
commit c2efa23e94
8 changed files with 54 additions and 31 deletions

View File

@ -2,6 +2,8 @@ import {isPresent} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection'; import {List, ListWrapper} from 'angular2/src/facade/collection';
import {ChangeDetectorRef} from './change_detector_ref'; import {ChangeDetectorRef} from './change_detector_ref';
import {ChangeDetector} from './interfaces'; import {ChangeDetector} from './interfaces';
import {ChangeDetectionError} from './exceptions';
import {ProtoRecord} from './proto_record';
import {Locals} from './parser/locals'; import {Locals} from './parser/locals';
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants'; import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants';
@ -79,4 +81,8 @@ export class AbstractChangeDetector implements ChangeDetector {
c = c.parent; c = c.parent;
} }
} }
throwError(proto: ProtoRecord, exception: any, stack: any): void {
throw new ChangeDetectionError(proto, exception, stack);
}
} }

View File

@ -28,7 +28,7 @@ var IS_CHANGED_LOCAL = "isChanged";
var CHANGES_LOCAL = "changes"; var CHANGES_LOCAL = "changes";
var LOCALS_ACCESSOR = "this.locals"; var LOCALS_ACCESSOR = "this.locals";
var MODE_ACCESSOR = "this.mode"; var MODE_ACCESSOR = "this.mode";
var CURRENT_PROTO = "currentProto"; var CURRENT_PROTO = "this.currentProto";
var ALREADY_CHECKED_ACCESSOR = "this.alreadyChecked"; var ALREADY_CHECKED_ACCESSOR = "this.alreadyChecked";
@ -74,6 +74,7 @@ export class ChangeDetectorJITGenerator {
${PROTOS_ACCESSOR} = protos; ${PROTOS_ACCESSOR} = protos;
${DIRECTIVES_ACCESSOR} = directiveRecords; ${DIRECTIVES_ACCESSOR} = directiveRecords;
${LOCALS_ACCESSOR} = null; ${LOCALS_ACCESSOR} = null;
${CURRENT_PROTO} = null;
${ALREADY_CHECKED_ACCESSOR} = false; ${ALREADY_CHECKED_ACCESSOR} = false;
${this._genFieldDefinitions()} ${this._genFieldDefinitions()}
} }
@ -84,19 +85,28 @@ export class ChangeDetectorJITGenerator {
if (!this.hydrated()) { if (!this.hydrated()) {
${UTIL}.throwDehydrated(); ${UTIL}.throwDehydrated();
} }
try {
this.__detectChangesInRecords(throwOnChange);
} catch (e) {
this.throwError(${CURRENT_PROTO}, e, e.stack);
}
}
${typeName}.prototype.__detectChangesInRecords = function(throwOnChange) {
${CURRENT_PROTO} = null;
${this._genLocalDefinitions()} ${this._genLocalDefinitions()}
${this._genChangeDefinitions()} ${this._genChangeDefinitions()}
var ${IS_CHANGED_LOCAL} = false; var ${IS_CHANGED_LOCAL} = false;
var ${CURRENT_PROTO};
var ${CHANGES_LOCAL} = null; var ${CHANGES_LOCAL} = null;
context = ${CONTEXT_ACCESSOR}; context = ${CONTEXT_ACCESSOR};
${this.records.map((r) => this._genRecord(r)).join("\n")} ${this.records.map((r) => this._genRecord(r)).join("\n")}
${ALREADY_CHECKED_ACCESSOR} = true; ${ALREADY_CHECKED_ACCESSOR} = true;
} }
${typeName}.prototype.callOnAllChangesDone = function() { ${typeName}.prototype.callOnAllChangesDone = function() {
${this._genCallOnAllChangesDoneBody()} ${this._genCallOnAllChangesDoneBody()}
} }

View File

@ -10,8 +10,6 @@ import {ChangeDetectionUtil, SimpleChange, uninitialized} from './change_detecti
import {ProtoRecord, RecordType} from './proto_record'; import {ProtoRecord, RecordType} from './proto_record';
import {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './exceptions';
export class DynamicChangeDetector extends AbstractChangeDetector { export class DynamicChangeDetector extends AbstractChangeDetector {
locals: Locals = null; locals: Locals = null;
values: List<any>; values: List<any>;
@ -149,7 +147,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
return this._referenceCheck(proto, throwOnChange); return this._referenceCheck(proto, throwOnChange);
} }
} catch (e) { } catch (e) {
throw new ChangeDetectionError(proto, e); this.throwError(proto, e, e.stack);
} }
} }

View File

@ -2,29 +2,20 @@ import {ProtoRecord} from './proto_record';
import {BaseException} from "angular2/src/facade/lang"; import {BaseException} from "angular2/src/facade/lang";
export class ExpressionChangedAfterItHasBeenChecked extends BaseException { export class ExpressionChangedAfterItHasBeenChecked extends BaseException {
message: string;
constructor(proto: ProtoRecord, change: any) { constructor(proto: ProtoRecord, change: any) {
super(); super(`Expression '${proto.expressionAsString}' has changed after it was checked. ` +
this.message = `Previous value: '${change.previousValue}'. Current value: '${change.currentValue}'`);
`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 { export class ChangeDetectionError extends BaseException {
message: string;
location: string; location: string;
constructor(proto: ProtoRecord, public originalException: any) { constructor(proto: ProtoRecord, originalException: any, originalStack: any) {
super(); super(`${originalException} in [${proto.expressionAsString}]`, originalException,
originalStack);
this.location = proto.expressionAsString; this.location = proto.expressionAsString;
this.message = `${this.originalException} in [${this.location}]`;
} }
toString(): string { return this.message; }
} }
export class DehydratedException extends BaseException { export class DehydratedException extends BaseException {

View File

@ -131,6 +131,7 @@ class _CodegenState {
$_DIRECTIVES_ACCESSOR; $_DIRECTIVES_ACCESSOR;
dynamic $_LOCALS_ACCESSOR = null; dynamic $_LOCALS_ACCESSOR = null;
dynamic $_ALREADY_CHECKED_ACCESSOR = false; dynamic $_ALREADY_CHECKED_ACCESSOR = false;
dynamic $_CURRENT_PROTO = null;
${_allFields().map((f) { ${_allFields().map((f) {
if (f == _CONTEXT_ACCESSOR) { if (f == _CONTEXT_ACCESSOR) {
return '$_contextTypeName $f = null;'; return '$_contextTypeName $f = null;';
@ -148,10 +149,18 @@ class _CodegenState {
if (!hydrated()) { if (!hydrated()) {
$_UTIL.throwDehydrated(); $_UTIL.throwDehydrated();
} }
try {
this.__detectChangesInRecords(throwOnChange);
} catch (e, s) {
this.throwError($_CURRENT_PROTO, e, s);
}
}
void __detectChangesInRecords(throwOnChange) {
${_genLocalDefinitions()} ${_genLocalDefinitions()}
${_genChangeDefinitons()} ${_genChangeDefinitons()}
var $_IS_CHANGED_LOCAL = false; var $_IS_CHANGED_LOCAL = false;
var $_CURRENT_PROTO; $_CURRENT_PROTO = null;
var $_CHANGES_LOCAL = null; var $_CHANGES_LOCAL = null;
context = $_CONTEXT_ACCESSOR; context = $_CONTEXT_ACCESSOR;
@ -159,7 +168,7 @@ class _CodegenState {
$_ALREADY_CHECKED_ACCESSOR = true; $_ALREADY_CHECKED_ACCESSOR = true;
} }
void callOnAllChangesDone() { void callOnAllChangesDone() {
${_getCallOnAllChangesDoneBody()} ${_getCallOnAllChangesDoneBody()}
} }

View File

@ -309,5 +309,6 @@ var _availableDefinitions = [
'sayHi("Jim")', 'sayHi("Jim")',
'a()(99)', 'a()(99)',
'a.sayHi("Jim")', 'a.sayHi("Jim")',
'passThrough([12])' 'passThrough([12])',
'invalidFn(1)'
]; ];

View File

@ -542,16 +542,15 @@ export function main() {
}); });
}); });
// TODO vsavkin: implement it
describe('error handling', () => { describe('error handling', () => {
xit('should wrap exceptions into ChangeDetectionError', () => { it('should wrap exceptions into ChangeDetectionError', () => {
var val = _createChangeDetector('invalidProp'); var val = _createChangeDetector('invalidFn(1)');
try { try {
val.changeDetector.detectChanges(); val.changeDetector.detectChanges();
throw new BaseException('fail'); throw new BaseException('fail');
} catch (e) { } catch (e) {
expect(e).toBeAnInstanceOf(ChangeDetectionError); expect(e).toBeAnInstanceOf(ChangeDetectionError);
expect(e.location).toEqual('invalidProp in someComponent'); expect(e.location).toEqual('invalidFn(1) in location');
} }
}); });
}); });

View File

@ -32,6 +32,7 @@ class _MyComponent_ChangeDetector0 extends _gen.AbstractChangeDetector {
final _gen.List<_gen.DirectiveRecord> _directiveRecords; final _gen.List<_gen.DirectiveRecord> _directiveRecords;
dynamic _locals = null; dynamic _locals = null;
dynamic _alreadyChecked = false; dynamic _alreadyChecked = false;
dynamic currentProto = null;
MyComponent _context = null; MyComponent _context = null;
dynamic _myNum0 = _gen.ChangeDetectionUtil.uninitialized(); dynamic _myNum0 = _gen.ChangeDetectionUtil.uninitialized();
dynamic _interpolate1 = _gen.ChangeDetectionUtil.uninitialized(); dynamic _interpolate1 = _gen.ChangeDetectionUtil.uninitialized();
@ -44,6 +45,14 @@ class _MyComponent_ChangeDetector0 extends _gen.AbstractChangeDetector {
if (!hydrated()) { if (!hydrated()) {
_gen.ChangeDetectionUtil.throwDehydrated(); _gen.ChangeDetectionUtil.throwDehydrated();
} }
try {
this.__detectChangesInRecords(throwOnChange);
} catch (e, s) {
this.throwError(currentProto, e, s);
}
}
void __detectChangesInRecords(throwOnChange) {
var context = null; var context = null;
var myNum0 = null; var myNum0 = null;
var interpolate1 = null; var interpolate1 = null;
@ -51,7 +60,7 @@ class _MyComponent_ChangeDetector0 extends _gen.AbstractChangeDetector {
var change_myNum0 = false; var change_myNum0 = false;
var change_interpolate1 = false; var change_interpolate1 = false;
var isChanged = false; var isChanged = false;
var currentProto; currentProto = null;
var changes = null; var changes = null;
context = _context; context = _context;