refactor(ivy): Add i18n create op codes debug info (#29348)

Simply adds a `debug` property to the array of create opcodes while inside
`readCreateOpCodes` in i18n. This `debug` property has a property called `operations`
that is a human-readable list of operations that will be performed, as derived
from the op codes themselves, and the view it's acting upon.

PR Close #29348
This commit is contained in:
Ben Lesh 2019-03-15 15:04:34 -07:00 committed by Jason Aden
parent c7ff728723
commit 699ecac2c2
3 changed files with 299 additions and 7 deletions

View File

@ -7,14 +7,18 @@
*/
import {assertDefined} from '../util/assert';
import {ACTIVE_INDEX, LContainer, NATIVE, VIEWS} from './interfaces/container';
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, TIcu} from './interfaces/i18n';
import {TNode} from './interfaces/node';
import {LQueries} from './interfaces/query';
import {RComment, RElement} from './interfaces/renderer';
import {StylingContext} from './interfaces/styling';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TVIEW, TView, T_HOST} from './interfaces/view';
import {unwrapRNode} from './util/view_utils';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TVIEW, T_HOST} from './interfaces/view';
import {getTNode, unwrapRNode} from './util/view_utils';
function attachDebugObject(obj: any, debug: any) {
Object.defineProperty(obj, 'debug', {value: debug, enumerable: false});
}
/*
* This file contains conditionally attached classes which provide human readable (debug) level
@ -47,11 +51,11 @@ import {unwrapRNode} from './util/view_utils';
export function attachLViewDebug(lView: LView) {
(lView as any).debug = new LViewDebug(lView);
attachDebugObject(lView, new LViewDebug(lView));
}
export function attachLContainerDebug(lContainer: LContainer) {
(lContainer as any).debug = new LContainerDebug(lContainer);
attachDebugObject(lContainer, new LContainerDebug(lContainer));
}
export function toDebug(obj: LView): LViewDebug;
@ -230,3 +234,198 @@ export function readLViewValue(value: any): LView|null {
}
return null;
}
export class I18NDebugItem {
[key: string]: any;
get tNode() { return getTNode(this.nodeIndex, this._lView); }
constructor(
public __raw_opCode: any, private _lView: LView, public nodeIndex: number,
public type: string) {}
}
/**
* Turns a list of "Create" & "Update" OpCodes into a human-readable list of operations for
* debugging purposes.
* @param mutateOpCodes mutation opCodes to read
* @param updateOpCodes update opCodes to read
* @param icus list of ICU expressions
* @param lView The view the opCodes are acting on
*/
export function attachI18nOpCodesDebug(
mutateOpCodes: I18nMutateOpCodes, updateOpCodes: I18nUpdateOpCodes, icus: TIcu[] | null,
lView: LView) {
attachDebugObject(mutateOpCodes, new I18nMutateOpCodesDebug(mutateOpCodes, lView));
attachDebugObject(updateOpCodes, new I18nUpdateOpCodesDebug(updateOpCodes, icus, lView));
if (icus) {
icus.forEach(icu => {
icu.create.forEach(
icuCase => { attachDebugObject(icuCase, new I18nMutateOpCodesDebug(icuCase, lView)); });
icu.update.forEach(icuCase => {
attachDebugObject(icuCase, new I18nUpdateOpCodesDebug(icuCase, icus, lView));
});
});
}
}
export class I18nMutateOpCodesDebug implements I18nOpCodesDebug {
constructor(private readonly __raw_opCodes: I18nMutateOpCodes, private readonly __lView: LView) {}
/**
* A list of operation information about how the OpCodes will act on the view.
*/
get operations() {
const {__lView, __raw_opCodes} = this;
const results: any[] = [];
for (let i = 0; i < __raw_opCodes.length; i++) {
const opCode = __raw_opCodes[i];
let result: any;
if (typeof opCode === 'string') {
result = {
__raw_opCode: opCode,
type: 'Create Text Node',
nodeIndex: __raw_opCodes[++i],
text: opCode,
};
}
if (typeof opCode === 'number') {
switch (opCode & I18nMutateOpCode.MASK_OPCODE) {
case I18nMutateOpCode.AppendChild:
const destinationNodeIndex = opCode >>> I18nMutateOpCode.SHIFT_PARENT;
result = new I18NDebugItem(opCode, __lView, destinationNodeIndex, 'AppendChild');
break;
case I18nMutateOpCode.Select:
const nodeIndex = opCode >>> I18nMutateOpCode.SHIFT_REF;
result = new I18NDebugItem(opCode, __lView, nodeIndex, 'Select');
break;
case I18nMutateOpCode.ElementEnd:
let elementIndex = opCode >>> I18nMutateOpCode.SHIFT_REF;
result = new I18NDebugItem(opCode, __lView, elementIndex, 'ElementEnd');
break;
case I18nMutateOpCode.Attr:
elementIndex = opCode >>> I18nMutateOpCode.SHIFT_REF;
result = new I18NDebugItem(opCode, __lView, elementIndex, 'Attr');
result['attrName'] = __raw_opCodes[++i];
result['attrValue'] = __raw_opCodes[++i];
break;
}
}
if (!result) {
switch (opCode) {
case COMMENT_MARKER:
result = {
__raw_opCode: opCode,
type: 'COMMENT_MARKER',
commentValue: __raw_opCodes[++i],
nodeIndex: __raw_opCodes[++i],
};
break;
case ELEMENT_MARKER:
result = {
__raw_opCode: opCode,
type: 'ELEMENT_MARKER',
};
break;
}
}
if (!result) {
result = {
__raw_opCode: opCode,
type: 'Unknown Op Code',
code: opCode,
};
}
results.push(result);
}
return results;
}
}
export class I18nUpdateOpCodesDebug implements I18nOpCodesDebug {
constructor(
private readonly __raw_opCodes: I18nUpdateOpCodes, private readonly icus: TIcu[]|null,
private readonly __lView: LView) {}
/**
* A list of operation information about how the OpCodes will act on the view.
*/
get operations() {
const {__lView, __raw_opCodes, icus} = this;
const results: any[] = [];
for (let i = 0; i < __raw_opCodes.length; i++) {
// bit code to check if we should apply the next update
const checkBit = __raw_opCodes[i] as number;
// Number of opCodes to skip until next set of update codes
const skipCodes = __raw_opCodes[++i] as number;
let value = '';
for (let j = i + 1; j <= (i + skipCodes); j++) {
const opCode = __raw_opCodes[j];
if (typeof opCode === 'string') {
value += opCode;
} else if (typeof opCode == 'number') {
if (opCode < 0) {
// It's a binding index whose value is negative
// We cannot know the value of the binding so we only show the index
value += `<EFBFBD>${-opCode - 1}<EFBFBD>`;
} else {
const nodeIndex = opCode >>> I18nUpdateOpCode.SHIFT_REF;
let tIcuIndex: number;
let tIcu: TIcu;
switch (opCode & I18nUpdateOpCode.MASK_OPCODE) {
case I18nUpdateOpCode.Attr:
const attrName = __raw_opCodes[++j] as string;
const sanitizeFn = __raw_opCodes[++j];
results.push({
__raw_opCode: opCode,
checkBit,
type: 'Attr',
attrValue: value, attrName, sanitizeFn,
});
break;
case I18nUpdateOpCode.Text:
results.push({
__raw_opCode: opCode,
checkBit,
type: 'Text', nodeIndex,
text: value,
});
break;
case I18nUpdateOpCode.IcuSwitch:
tIcuIndex = __raw_opCodes[++j] as number;
tIcu = icus ![tIcuIndex];
let result = new I18NDebugItem(opCode, __lView, nodeIndex, 'IcuSwitch');
result['tIcuIndex'] = tIcuIndex;
result['checkBit'] = checkBit;
result['mainBinding'] = value;
result['tIcu'] = tIcu;
results.push(result);
break;
case I18nUpdateOpCode.IcuUpdate:
tIcuIndex = __raw_opCodes[++j] as number;
tIcu = icus ![tIcuIndex];
result = new I18NDebugItem(opCode, __lView, nodeIndex, 'IcuUpdate');
result['tIcuIndex'] = tIcuIndex;
result['checkBit'] = checkBit;
result['tIcu'] = tIcu;
results.push(result);
break;
}
}
}
}
i += skipCodes;
}
return results;
}
}
export interface I18nOpCodesDebug { operations: any[]; }

View File

@ -13,6 +13,7 @@ import {addAllToArray} from '../util/array_utils';
import {assertDefined, assertEqual, assertGreaterThan} from '../util/assert';
import {attachPatchData} from './context_discovery';
import {attachI18nOpCodesDebug} from './debug';
import {elementAttribute, load, textBinding} from './instructions/all';
import {allocExpando, createNodeAtIndex} from './instructions/shared';
import {LContainer, NATIVE} from './interfaces/container';
@ -457,6 +458,10 @@ function i18nStartFirstPass(
allocExpando(viewData, i18nVarsCount);
ngDevMode &&
attachI18nOpCodesDebug(
createOpCodes, updateOpCodes, icuExpressions.length ? icuExpressions : null, viewData);
// NOTE: local var needed to properly assert the type of `TI18n`.
const tI18n: TI18n = {
vars: i18nVarsCount,
@ -464,6 +469,7 @@ function i18nStartFirstPass(
update: updateOpCodes,
icus: icuExpressions.length ? icuExpressions : null,
};
tView.data[index + HEADER_OFFSET] = tI18n;
}

View File

@ -16,7 +16,7 @@ import {AttributeMarker} from '../../src/render3/interfaces/node';
import {getNativeByIndex, getTNode} from '../../src/render3/util/view_utils';
import {NgForOf, NgIf} from './common_with_def';
import {allocHostVars, element, elementEnd, elementStart, template, text, nextContext, bind, elementProperty, projectionDef, projection, elementContainerStart, elementContainerEnd, textBinding} from '../../src/render3/instructions/all';
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nUpdateOpCode, I18nUpdateOpCodes, TI18n} from '../../src/render3/interfaces/i18n';
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nUpdateOpCode, I18nUpdateOpCodes, TI18n, IcuType} from '../../src/render3/interfaces/i18n';
import {HEADER_OFFSET, LView, TVIEW} from '../../src/render3/interfaces/view';
import {ComponentFixture, TemplateFixture} from './render_util';
@ -81,6 +81,17 @@ describe('Runtime i18n', () => {
const index = 0;
const opCodes = getOpCodes(() => { i18nStart(index, MSG_DIV); }, null, nbConsts, index);
// Check debug
const debugOps = (opCodes as any).create.debug !.operations;
expect(debugOps[0].__raw_opCode).toBe('simple text');
expect(debugOps[0].type).toBe('Create Text Node');
expect(debugOps[0].nodeIndex).toBe(1);
expect(debugOps[0].text).toBe('simple text');
expect(debugOps[1].__raw_opCode).toBe(1);
expect(debugOps[1].type).toBe('AppendChild');
expect(debugOps[1].nodeIndex).toBe(0);
expect(opCodes).toEqual({
vars: 1,
create: [
@ -138,6 +149,10 @@ describe('Runtime i18n', () => {
const index = 1;
const opCodes = getOpCodes(() => { i18nStart(index, MSG_DIV); }, null, nbConsts, index);
expect((opCodes as any).update.debug.operations).toEqual([
{__raw_opCode: 8, checkBit: 1, type: 'Text', nodeIndex: 2, text: 'Hello <20>0<EFBFBD>!'}
]);
expect(opCodes).toEqual({
vars: 1,
create:
@ -282,6 +297,79 @@ describe('Runtime i18n', () => {
const innerTextNode = index + 4;
const lastTextNode = index + 5;
const debugOps = (opCodes as any).update.debug.operations;
expect(debugOps[0].__raw_opCode).toBe(6);
expect(debugOps[0].checkBit).toBe(1);
expect(debugOps[0].type).toBe('IcuSwitch');
expect(debugOps[0].nodeIndex).toBe(1);
expect(debugOps[0].tIcuIndex).toBe(0);
expect(debugOps[0].mainBinding).toBe('<27>0<EFBFBD>');
expect(debugOps[1].__raw_opCode).toBe(7);
expect(debugOps[1].checkBit).toBe(3);
expect(debugOps[1].type).toBe('IcuUpdate');
expect(debugOps[1].nodeIndex).toBe(1);
expect(debugOps[1].tIcuIndex).toBe(0);
const icuDebugOps = (opCodes as any).icus[0].create[0].debug.operations;
let op: any;
let i = 0;
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe('no ');
expect(op.type).toBe('Create Text Node');
expect(op.nodeIndex).toBe(2);
expect(op.text).toBe('no ');
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe(131073);
expect(op.type).toBe('AppendChild');
expect(op.nodeIndex).toBe(1);
op = icuDebugOps[i++];
expect(op.__raw_opCode).toEqual({marker: 'element'});
expect(op.type).toBe('ELEMENT_MARKER');
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe('b');
expect(op.type).toBe('Create Text Node');
expect(op.nodeIndex).toBe(3);
expect(op.text).toBe('b');
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe(131073);
expect(op.type).toBe('AppendChild');
expect(op.nodeIndex).toBe(1);
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe(28);
expect(op.type).toBe('Attr');
expect(op.nodeIndex).toBe(3);
expect(op.attrName).toBe('title');
expect(op.attrValue).toBe('none');
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe('emails');
expect(op.type).toBe('Create Text Node');
expect(op.nodeIndex).toBe(4);
expect(op.text).toBe('emails');
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe(393217);
expect(op.type).toBe('AppendChild');
expect(op.nodeIndex).toBe(3);
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe('!');
expect(op.type).toBe('Create Text Node');
expect(op.nodeIndex).toBe(5);
expect(op.text).toBe('!');
op = icuDebugOps[i++];
expect(op.__raw_opCode).toBe(131073);
expect(op.type).toBe('AppendChild');
expect(op.nodeIndex).toBe(1);
expect(opCodes).toEqual({
vars: 5,
create: [
@ -2168,5 +2256,4 @@ describe('Runtime i18n', () => {
});
});
});
});