feat(core): view engine - add debug information (#14197)
Creates debug information for the renderer, and also reports errors relative to the declaration place in the template. Part of #14013 PR Close #14197
This commit is contained in:

committed by
Miško Hevery

parent
c48dd76f5c
commit
52b21275f4
@ -6,13 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {isDevMode} from '../application_ref';
|
||||
import {devModeEqual} from '../change_detection/change_detection';
|
||||
import {SimpleChange} from '../change_detection/change_detection_util';
|
||||
import {looseIdentical} from '../facade/lang';
|
||||
import {ExpressionChangedAfterItHasBeenCheckedError} from '../linker/errors';
|
||||
import {Renderer} from '../render/api';
|
||||
|
||||
import {ElementData, NodeData, NodeDef, NodeFlags, NodeType, ViewData, ViewDefinition, asElementData, asTextData} from './types';
|
||||
import {expressionChangedAfterItHasBeenCheckedError, isViewError, viewWrappedError} from './errors';
|
||||
import {ElementData, EntryAction, NodeData, NodeDef, NodeFlags, NodeType, ViewData, ViewDefinition, ViewDefinitionFactory, asElementData, asTextData} from './types';
|
||||
|
||||
export function setBindingDebugInfo(
|
||||
renderer: Renderer, renderNode: any, propName: string, value: any) {
|
||||
@ -36,7 +37,8 @@ export function checkBindingNoChanges(
|
||||
view: ViewData, def: NodeDef, bindingIdx: number, value: any) {
|
||||
const oldValue = view.oldValues[def.bindingIndex + bindingIdx];
|
||||
if (view.firstChange || !devModeEqual(oldValue, value)) {
|
||||
throw new ExpressionChangedAfterItHasBeenCheckedError(oldValue, value, view.firstChange);
|
||||
throw expressionChangedAfterItHasBeenCheckedError(
|
||||
view.services.createDebugContext(view, def.index), oldValue, value, view.firstChange);
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,4 +78,95 @@ export function renderNode(view: ViewData, def: NodeDef): any {
|
||||
case NodeType.Text:
|
||||
return asTextData(view, def.index).renderText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function isComponentView(view: ViewData): boolean {
|
||||
return view.component === view.context && !!view.parent;
|
||||
}
|
||||
|
||||
const VIEW_DEFINITION_CACHE = new WeakMap<any, ViewDefinition>();
|
||||
|
||||
export function resolveViewDefinition(factory: ViewDefinitionFactory): ViewDefinition {
|
||||
let value: ViewDefinition = VIEW_DEFINITION_CACHE.get(factory);
|
||||
if (!value) {
|
||||
value = factory();
|
||||
VIEW_DEFINITION_CACHE.set(factory, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function sliceErrorStack(start: number, end: number): string {
|
||||
let err: any;
|
||||
try {
|
||||
throw new Error();
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
const stack = err.stack || '';
|
||||
const lines = stack.split('\n');
|
||||
if (lines[0].startsWith('Error')) {
|
||||
// Chrome always adds the message to the stack as well...
|
||||
start++;
|
||||
end++;
|
||||
}
|
||||
return lines.slice(start, end).join('\n');
|
||||
}
|
||||
|
||||
let _currentAction: EntryAction;
|
||||
let _currentView: ViewData;
|
||||
let _currentNodeIndex: number;
|
||||
|
||||
export function currentView() {
|
||||
return _currentView;
|
||||
}
|
||||
|
||||
export function currentNodeIndex() {
|
||||
return _currentNodeIndex;
|
||||
}
|
||||
|
||||
export function currentAction() {
|
||||
return _currentAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the node that is currently worked on.
|
||||
* It needs to be called whenever we call user code,
|
||||
* or code of the framework that might throw as a valid use case.
|
||||
*/
|
||||
export function setCurrentNode(view: ViewData, nodeIndex: number) {
|
||||
_currentView = view;
|
||||
_currentNodeIndex = nodeIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a try/catch handler around the given function to wrap all
|
||||
* errors that occur into new errors that contain the current debug info
|
||||
* set via setCurrentNode.
|
||||
*/
|
||||
export function entryAction<A, R>(action: EntryAction, fn: (arg: A) => R): (arg: A) => R {
|
||||
return <any>function(arg: any) {
|
||||
const oldAction = _currentAction;
|
||||
const oldView = _currentView;
|
||||
const oldNodeIndex = _currentNodeIndex;
|
||||
_currentAction = action;
|
||||
// Note: We can't call `isDevMode()` outside of this closure as
|
||||
// it might not have been initialized.
|
||||
const result = isDevMode() ? callWithTryCatch(fn, arg) : fn(arg);
|
||||
_currentAction = oldAction;
|
||||
_currentView = oldView;
|
||||
_currentNodeIndex = oldNodeIndex;
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
function callWithTryCatch(fn: (a: any) => any, arg: any): any {
|
||||
try {
|
||||
return fn(arg);
|
||||
} catch (e) {
|
||||
if (isViewError(e) || !_currentView) {
|
||||
throw e;
|
||||
}
|
||||
const debugContext = _currentView.services.createDebugContext(_currentView, _currentNodeIndex);
|
||||
throw viewWrappedError(e, debugContext);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user