refactor(core): cleanup i18n/icu data structures (#39233)

- Made `*OpCodes` array branded for safer type checking.
- Simplify `I18NRemoveOpCodes` encoding.
- Broke out  `IcuCreateOpCodes` from `I18nMutableOpCodes`.

PR Close #39233
This commit is contained in:
Misko Hevery 2020-10-13 16:24:58 -07:00 committed by Alex Rickabaugh
parent e1f80d73a8
commit bc5005e35b
10 changed files with 275 additions and 249 deletions

View File

@ -11,7 +11,7 @@ import {assertDefined, assertDomNode, assertEqual, assertGreaterThan, assertInde
import {assertIndexInExpandoRange, assertTIcu} from '../assert'; import {assertIndexInExpandoRange, assertTIcu} from '../assert';
import {attachPatchData} from '../context_discovery'; import {attachPatchData} from '../context_discovery';
import {elementPropertyInternal, setElementAttribute} from '../instructions/shared'; import {elementPropertyInternal, setElementAttribute} from '../instructions/shared';
import {ELEMENT_MARKER, getCurrentICUCaseIndex, getParentFromI18nMutateOpCode, getRefFromI18nMutateOpCode, I18nCreateOpCode, I18nCreateOpCodes, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, ICU_MARKER, IcuType, TI18n, TIcu} from '../interfaces/i18n'; import {ELEMENT_MARKER, getCurrentICUCaseIndex, getParentFromIcuCreateOpCode, getRefFromIcuCreateOpCode, I18nCreateOpCode, I18nCreateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, ICU_MARKER, IcuCreateOpCode, IcuCreateOpCodes, IcuType, TI18n, TIcu} from '../interfaces/i18n';
import {TNode} from '../interfaces/node'; import {TNode} from '../interfaces/node';
import {RElement, RNode, RText} from '../interfaces/renderer'; import {RElement, RNode, RText} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization'; import {SanitizerFn} from '../interfaces/sanitization';
@ -122,7 +122,7 @@ export function applyCreateOpCodes(
* @param anchorRNode place where the i18n node should be inserted. * @param anchorRNode place where the i18n node should be inserted.
*/ */
export function applyMutableOpCodes( export function applyMutableOpCodes(
tView: TView, mutableOpCodes: I18nMutateOpCodes, lView: LView, anchorRNode: RNode): void { tView: TView, mutableOpCodes: IcuCreateOpCodes, lView: LView, anchorRNode: RNode): void {
ngDevMode && assertDomNode(anchorRNode); ngDevMode && assertDomNode(anchorRNode);
const renderer = lView[RENDERER]; const renderer = lView[RENDERER];
// `rootIdx` represents the node into which all inserts happen. // `rootIdx` represents the node into which all inserts happen.
@ -143,9 +143,9 @@ export function applyMutableOpCodes(
lView[textNodeIndex] = createTextNode(renderer, opCode); lView[textNodeIndex] = createTextNode(renderer, opCode);
} }
} else if (typeof opCode == 'number') { } else if (typeof opCode == 'number') {
switch (opCode & I18nMutateOpCode.MASK_INSTRUCTION) { switch (opCode & IcuCreateOpCode.MASK_INSTRUCTION) {
case I18nMutateOpCode.AppendChild: case IcuCreateOpCode.AppendChild:
const parentIdx = getParentFromI18nMutateOpCode(opCode); const parentIdx = getParentFromIcuCreateOpCode(opCode);
if (rootIdx === null) { if (rootIdx === null) {
// The first operation should save the `rootIdx` because the first operation // The first operation should save the `rootIdx` because the first operation
// must insert into the root. (Only subsequent operations can insert into a dynamic // must insert into the root. (Only subsequent operations can insert into a dynamic
@ -169,7 +169,7 @@ export function applyMutableOpCodes(
// create the elements. When the `LView` gets later added to a parent these "root" nodes // create the elements. When the `LView` gets later added to a parent these "root" nodes
// get picked up and added. // get picked up and added.
ngDevMode && assertDomNode(parentRNode); ngDevMode && assertDomNode(parentRNode);
const refIdx = getRefFromI18nMutateOpCode(opCode); const refIdx = getRefFromIcuCreateOpCode(opCode);
ngDevMode && assertGreaterThan(refIdx, HEADER_OFFSET, 'Missing ref'); ngDevMode && assertGreaterThan(refIdx, HEADER_OFFSET, 'Missing ref');
// `unwrapRNode` is not needed here as all of these point to RNodes as part of the i18n // `unwrapRNode` is not needed here as all of these point to RNodes as part of the i18n
// which can't have components. // which can't have components.
@ -188,8 +188,8 @@ export function applyMutableOpCodes(
} }
} }
break; break;
case I18nMutateOpCode.Attr: case IcuCreateOpCode.Attr:
const elementNodeIndex = opCode >>> I18nMutateOpCode.SHIFT_REF; const elementNodeIndex = opCode >>> IcuCreateOpCode.SHIFT_REF;
const attrName = mutableOpCodes[++i] as string; const attrName = mutableOpCodes[++i] as string;
const attrValue = mutableOpCodes[++i] as string; const attrValue = mutableOpCodes[++i] as string;
// This code is used for ICU expressions only, since we don't support // This code is used for ICU expressions only, since we don't support
@ -393,17 +393,15 @@ function applyIcuSwitchCaseRemove(tView: TView, tIcu: TIcu, lView: LView) {
let activeCaseIndex = getCurrentICUCaseIndex(tIcu, lView); let activeCaseIndex = getCurrentICUCaseIndex(tIcu, lView);
if (activeCaseIndex !== null) { if (activeCaseIndex !== null) {
const removeCodes = tIcu.remove[activeCaseIndex]; const removeCodes = tIcu.remove[activeCaseIndex];
for (let k = 0; k < removeCodes.length; k++) { for (let i = 0; i < removeCodes.length; i++) {
const removeOpCode = removeCodes[k] as number; const nodeOrIcuIndex = removeCodes[i] as number;
const nodeOrIcuIndex = removeOpCode >>> I18nMutateOpCode.SHIFT_REF; if (nodeOrIcuIndex > 0) {
switch (removeOpCode & I18nMutateOpCode.MASK_INSTRUCTION) { // Positive numbers are `RNode`s.
case I18nMutateOpCode.Remove: const rNode = getNativeByIndex(nodeOrIcuIndex, lView);
nativeRemoveNode( rNode !== null && nativeRemoveNode(lView[RENDERER], rNode);
lView[RENDERER], getNativeByIndex(nodeOrIcuIndex - HEADER_OFFSET, lView)); } else {
break; // Negative numbers are ICUs
case I18nMutateOpCode.RemoveNestedIcu: applyIcuSwitchCaseRemove(tView, getTIcu(tView, ~nodeOrIcuIndex)!, lView);
applyIcuSwitchCaseRemove(tView, getTIcu(tView, nodeOrIcuIndex)!, lView);
break;
} }
} }
} }

View File

@ -7,7 +7,7 @@
*/ */
import {assertNumber, assertString} from '../../util/assert'; import {assertNumber, assertString} from '../../util/assert';
import {ELEMENT_MARKER, getInstructionFromI18nMutateOpCode, getParentFromI18nMutateOpCode, getRefFromI18nMutateOpCode, I18nCreateOpCode, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, ICU_MARKER} from '../interfaces/i18n'; import {ELEMENT_MARKER, getInstructionFromIcuCreateOpCode, getParentFromIcuCreateOpCode, getRefFromIcuCreateOpCode, I18nCreateOpCode, I18nCreateOpCodes, I18nRemoveOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, ICU_MARKER, IcuCreateOpCode, IcuCreateOpCodes} from '../interfaces/i18n';
/** /**
@ -21,8 +21,8 @@ import {ELEMENT_MARKER, getInstructionFromI18nMutateOpCode, getParentFromI18nMut
* @param opcodes `I18nCreateOpCodes` if invoked as a function. * @param opcodes `I18nCreateOpCodes` if invoked as a function.
*/ */
export function i18nCreateOpCodesToString( export function i18nCreateOpCodesToString(
this: I18nUpdateOpCodes|void, opcodes?: I18nUpdateOpCodes): string[] { this: I18nCreateOpCodes|void, opcodes?: I18nCreateOpCodes): string[] {
const createOpCodes: I18nUpdateOpCodes = opcodes || (Array.isArray(this) ? this : []); const createOpCodes: I18nCreateOpCodes = opcodes || (Array.isArray(this) ? this : [] as any);
let lines: string[] = []; let lines: string[] = [];
for (let i = 0; i < createOpCodes.length; i++) { for (let i = 0; i < createOpCodes.length; i++) {
const opCode = createOpCodes[i++] as any; const opCode = createOpCodes[i++] as any;
@ -103,35 +103,31 @@ export function i18nUpdateOpCodesToString(
} }
/** /**
* Converts `I18nMutableOpCodes` array into a human readable format. * Converts `I18nCreateOpCodes` array into a human readable format.
* *
* This function is attached to the `I18nMutableOpCodes.debug` if `ngDevMode` is enabled. This * This function is attached to the `I18nCreateOpCodes.debug` if `ngDevMode` is enabled. This
* function provides a human readable view of the opcodes. This is useful when debugging the * function provides a human readable view of the opcodes. This is useful when debugging the
* application as well as writing more readable tests. * application as well as writing more readable tests.
* *
* @param this `I18nMutableOpCodes` if attached as a method. * @param this `I18nCreateOpCodes` if attached as a method.
* @param opcodes `I18nMutableOpCodes` if invoked as a function. * @param opcodes `I18nCreateOpCodes` if invoked as a function.
*/ */
export function i18nMutateOpCodesToString( export function icuCreateOpCodesToString(
this: I18nMutateOpCodes|void, opcodes?: I18nMutateOpCodes): string[] { this: IcuCreateOpCodes|void, opcodes?: IcuCreateOpCodes): string[] {
const parser = new OpCodeParser(opcodes || (Array.isArray(this) ? this : [])); const parser = new OpCodeParser(opcodes || (Array.isArray(this) ? this : []));
let lines: string[] = []; let lines: string[] = [];
function consumeOpCode(opCode: number): string { function consumeOpCode(opCode: number): string {
const parent = getParentFromI18nMutateOpCode(opCode); const parent = getParentFromIcuCreateOpCode(opCode);
const ref = getRefFromI18nMutateOpCode(opCode); const ref = getRefFromIcuCreateOpCode(opCode);
switch (getInstructionFromI18nMutateOpCode(opCode)) { switch (getInstructionFromIcuCreateOpCode(opCode)) {
case I18nMutateOpCode.AppendChild: case IcuCreateOpCode.AppendChild:
return `(lView[${parent}] as Element).appendChild(lView[${lastRef}])`; return `(lView[${parent}] as Element).appendChild(lView[${lastRef}])`;
case I18nMutateOpCode.Remove: case IcuCreateOpCode.Attr:
return `(lView[${parent}] as Element).remove(lView[${ref}])`;
case I18nMutateOpCode.Attr:
return `(lView[${ref}] as Element).setAttribute("${parser.consumeString()}", "${ return `(lView[${ref}] as Element).setAttribute("${parser.consumeString()}", "${
parser.consumeString()}")`; parser.consumeString()}")`;
case I18nMutateOpCode.RemoveNestedIcu:
return `removeNestedICU(${ref})`;
} }
throw new Error('Unexpected OpCode'); throw new Error('Unexpected OpCode: ' + getInstructionFromIcuCreateOpCode(opCode));
} }
let lastRef = -1; let lastRef = -1;
@ -159,6 +155,35 @@ export function i18nMutateOpCodesToString(
return lines; return lines;
} }
/**
* Converts `I18nRemoveOpCodes` array into a human readable format.
*
* This function is attached to the `I18nRemoveOpCodes.debug` if `ngDevMode` is enabled. This
* function provides a human readable view of the opcodes. This is useful when debugging the
* application as well as writing more readable tests.
*
* @param this `I18nRemoveOpCodes` if attached as a method.
* @param opcodes `I18nRemoveOpCodes` if invoked as a function.
*/
export function i18nRemoveOpCodesToString(
this: I18nRemoveOpCodes|void, opcodes?: I18nRemoveOpCodes): string[] {
const removeCodes = opcodes || (Array.isArray(this) ? this : []);
let lines: string[] = [];
for (let i = 0; i < removeCodes.length; i++) {
const nodeOrIcuIndex = removeCodes[i] as number;
if (nodeOrIcuIndex > 0) {
// Positive numbers are `RNode`s.
lines.push(`remove(lView[${nodeOrIcuIndex}])`);
} else {
// Negative numbers are ICUs
lines.push(`removeNestedICU(${~nodeOrIcuIndex})`);
}
}
return lines;
}
class OpCodeParser { class OpCodeParser {
i: number = 0; i: number = 0;

View File

@ -16,7 +16,7 @@ import {CharCode} from '../../util/char_code';
import {loadIcuContainerVisitor} from '../instructions/i18n_icu_container_visitor'; import {loadIcuContainerVisitor} from '../instructions/i18n_icu_container_visitor';
import {allocExpando, createTNodeAtIndex, elementAttributeInternal, setInputsForProperty, setNgReflectProperties} from '../instructions/shared'; import {allocExpando, createTNodeAtIndex, elementAttributeInternal, setInputsForProperty, setNgReflectProperties} from '../instructions/shared';
import {getDocument} from '../interfaces/document'; import {getDocument} from '../interfaces/document';
import {ELEMENT_MARKER, ensureIcuContainerVisitorLoaded, I18nCreateOpCode, I18nCreateOpCodes, I18nMutateOpCode, i18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, ICU_MARKER, IcuExpression, IcuType, TI18n, TIcu} from '../interfaces/i18n'; import {ELEMENT_MARKER, ensureIcuContainerVisitorLoaded, I18nCreateOpCode, I18nCreateOpCodes, I18nRemoveOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, ICU_MARKER, icuCreateOpCode, IcuCreateOpCode, IcuCreateOpCodes, IcuExpression, IcuType, TI18n, TIcu} from '../interfaces/i18n';
import {TNode, TNodeType} from '../interfaces/node'; import {TNode, TNodeType} from '../interfaces/node';
import {RComment, RElement} from '../interfaces/renderer'; import {RComment, RElement} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization'; import {SanitizerFn} from '../interfaces/sanitization';
@ -25,7 +25,7 @@ import {getCurrentParentTNode, getCurrentTNode, setCurrentTNode} from '../state'
import {attachDebugGetter} from '../util/debug_utils'; import {attachDebugGetter} from '../util/debug_utils';
import {getNativeByIndex, getTNode} from '../util/view_utils'; import {getNativeByIndex, getTNode} from '../util/view_utils';
import {i18nCreateOpCodesToString, i18nMutateOpCodesToString, i18nUpdateOpCodesToString} from './i18n_debug'; import {i18nCreateOpCodesToString, i18nRemoveOpCodesToString, i18nUpdateOpCodesToString, icuCreateOpCodesToString} from './i18n_debug';
import {addTNodeAndUpdateInsertBeforeIndex} from './i18n_insert_before_index'; import {addTNodeAndUpdateInsertBeforeIndex} from './i18n_insert_before_index';
import {createTNodePlaceholder, setTIcu, setTNodeInsertBeforeIndex} from './i18n_util'; import {createTNodePlaceholder, setTIcu, setTNodeInsertBeforeIndex} from './i18n_util';
@ -70,8 +70,8 @@ export function i18nStartFirstCreatePass(
tView: TView, parentTNodeIndex: number, lView: LView, index: number, message: string, tView: TView, parentTNodeIndex: number, lView: LView, index: number, message: string,
subTemplateIndex: number) { subTemplateIndex: number) {
const rootTNode = getCurrentParentTNode(); const rootTNode = getCurrentParentTNode();
const createOpCodes: I18nCreateOpCodes = []; const createOpCodes: I18nCreateOpCodes = [] as any;
const updateOpCodes: I18nUpdateOpCodes = []; const updateOpCodes: I18nUpdateOpCodes = [] as any;
const existingTNodeStack: TNode[][] = [[]]; const existingTNodeStack: TNode[][] = [[]];
if (ngDevMode) { if (ngDevMode) {
attachDebugGetter(createOpCodes, i18nCreateOpCodesToString); attachDebugGetter(createOpCodes, i18nCreateOpCodesToString);
@ -158,7 +158,7 @@ function createTNodeAndAddOpCode(
let parentTNode = getCurrentParentTNode(); let parentTNode = getCurrentParentTNode();
if (rootTNode === parentTNode) { if (rootTNode === parentTNode) {
// FIXME(misko): A null `parentTNode` should represent when we fall of the `LView` boundry. // FIXME(misko): A null `parentTNode` should represent when we fall of the `LView` boundary.
// (there is no parent), but in some circumstances (because we are inconsistent about how we set // (there is no parent), but in some circumstances (because we are inconsistent about how we set
// `previousOrParentTNode`) it could point to `rootTNode` So this is a work around. // `previousOrParentTNode`) it could point to `rootTNode` So this is a work around.
parentTNode = null; parentTNode = null;
@ -228,7 +228,7 @@ export function i18nAttributesFirstPass(
lView: LView, tView: TView, index: number, values: string[]) { lView: LView, tView: TView, index: number, values: string[]) {
const previousElement = getCurrentTNode()!; const previousElement = getCurrentTNode()!;
const previousElementIndex = previousElement.index; const previousElementIndex = previousElement.index;
const updateOpCodes: I18nUpdateOpCodes = []; const updateOpCodes: I18nUpdateOpCodes = [] as any;
if (ngDevMode) { if (ngDevMode) {
attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString); attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString);
} }
@ -583,12 +583,12 @@ export function i18nParseTextIntoPartsAndICU(pattern: string): (string|IcuExpres
export function parseIcuCase( export function parseIcuCase(
tView: TView, tIcu: TIcu, lView: LView, updateOpCodes: I18nUpdateOpCodes, parentIdx: number, tView: TView, tIcu: TIcu, lView: LView, updateOpCodes: I18nUpdateOpCodes, parentIdx: number,
caseName: string, unsafeCaseHtml: string, nestedIcus: IcuExpression[]): number { caseName: string, unsafeCaseHtml: string, nestedIcus: IcuExpression[]): number {
const create: I18nMutateOpCodes = []; const create: IcuCreateOpCodes = [] as any;
const remove: I18nMutateOpCodes = []; const remove: I18nRemoveOpCodes = [] as any;
const update: I18nUpdateOpCodes = []; const update: I18nUpdateOpCodes = [] as any;
if (ngDevMode) { if (ngDevMode) {
attachDebugGetter(create, i18nMutateOpCodesToString); attachDebugGetter(create, icuCreateOpCodesToString);
attachDebugGetter(remove, i18nMutateOpCodesToString); attachDebugGetter(remove, i18nRemoveOpCodesToString);
attachDebugGetter(update, i18nUpdateOpCodesToString); attachDebugGetter(update, i18nUpdateOpCodesToString);
} }
tIcu.cases.push(caseName); tIcu.cases.push(caseName);
@ -611,7 +611,7 @@ export function parseIcuCase(
function walkIcuTree( function walkIcuTree(
tView: TView, tIcu: TIcu, lView: LView, sharedUpdateOpCodes: I18nUpdateOpCodes, tView: TView, tIcu: TIcu, lView: LView, sharedUpdateOpCodes: I18nUpdateOpCodes,
create: I18nMutateOpCodes, remove: I18nMutateOpCodes, update: I18nUpdateOpCodes, create: IcuCreateOpCodes, remove: I18nRemoveOpCodes, update: I18nUpdateOpCodes,
parentNode: Element, parentIdx: number, nestedIcus: IcuExpression[], depth: number): number { parentNode: Element, parentIdx: number, nestedIcus: IcuExpression[], depth: number): number {
let bindingMask = 0; let bindingMask = 0;
let currentNode = parentNode.firstChild; let currentNode = parentNode.firstChild;
@ -648,9 +648,7 @@ function walkIcuTree(
} (see http://g.co/ng/security#xss)`); } (see http://g.co/ng/security#xss)`);
} }
} else { } else {
create.push( addCreateAttribute(create, newIndex, attr);
newIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Attr, attr.name,
attr.value);
} }
} }
// Parse the children of this node (if any) // Parse the children of this node (if any)
@ -689,16 +687,17 @@ function walkIcuTree(
} }
return bindingMask; return bindingMask;
} }
function addRemoveNode(remove: I18nMutateOpCodes, index: number, depth: number) {
function addRemoveNode(remove: I18nRemoveOpCodes, index: number, depth: number) {
if (depth === 0) { if (depth === 0) {
remove.push(index << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove); remove.push(index);
} }
} }
function addRemoveNestedIcu(remove: I18nMutateOpCodes, index: number, depth: number) { function addRemoveNestedIcu(remove: I18nRemoveOpCodes, index: number, depth: number) {
if (depth === 0) { if (depth === 0) {
remove.push(index << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.RemoveNestedIcu); remove.push(~index); // remove ICU at `index`
remove.push(index << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove); remove.push(index); // remove ICU comment at `index`
} }
} }
@ -714,12 +713,16 @@ function addUpdateIcuUpdate(update: I18nUpdateOpCodes, bindingMask: number, inde
} }
function addCreateNodeAndAppend( function addCreateNodeAndAppend(
create: I18nMutateOpCodes, marker: null|ICU_MARKER|ELEMENT_MARKER, text: string, create: IcuCreateOpCodes, marker: null|ICU_MARKER|ELEMENT_MARKER, text: string,
appendToParentIdx: number, createAtIdx: number) { appendToParentIdx: number, createAtIdx: number) {
if (marker !== null) { if (marker !== null) {
create.push(marker); create.push(marker);
} }
create.push( create.push(
text, createAtIdx, text, createAtIdx,
i18nMutateOpCode(I18nMutateOpCode.AppendChild, appendToParentIdx, createAtIdx)); icuCreateOpCode(IcuCreateOpCode.AppendChild, appendToParentIdx, createAtIdx));
}
function addCreateAttribute(create: IcuCreateOpCodes, newIndex: number, attr: Attr) {
create.push(newIndex << IcuCreateOpCode.SHIFT_REF | IcuCreateOpCode.Attr, attr.name, attr.value);
} }

View File

@ -6,10 +6,10 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {assertDomNode, assertEqual, assertNumber, assertNumberInRange} from '../../util/assert'; import {assertDomNode, assertNumber, assertNumberInRange} from '../../util/assert';
import {assertTIcu, assertTNodeForLView} from '../assert'; import {assertTIcu, assertTNodeForLView} from '../assert';
import {EMPTY_ARRAY} from '../empty'; import {EMPTY_ARRAY} from '../empty';
import {getCurrentICUCaseIndex, I18nMutateOpCode, I18nMutateOpCodes, TIcu} from '../interfaces/i18n'; import {getCurrentICUCaseIndex, I18nRemoveOpCodes, TIcu} from '../interfaces/i18n';
import {TIcuContainerNode} from '../interfaces/node'; import {TIcuContainerNode} from '../interfaces/node';
import {RNode} from '../interfaces/renderer'; import {RNode} from '../interfaces/renderer';
import {LView, TVIEW} from '../interfaces/view'; import {LView, TVIEW} from '../interfaces/view';
@ -18,7 +18,7 @@ export function loadIcuContainerVisitor() {
const _stack: any[] = []; const _stack: any[] = [];
let _index: number = -1; let _index: number = -1;
let _lView: LView; let _lView: LView;
let _removes: I18nMutateOpCodes; let _removes: I18nRemoveOpCodes;
/** /**
* Retrieves a set of root nodes from `TIcu.remove`. Used by `TNodeType.ICUContainer` * Retrieves a set of root nodes from `TIcu.remove`. Used by `TNodeType.ICUContainer`
@ -52,7 +52,7 @@ export function loadIcuContainerVisitor() {
ngDevMode && assertNumberInRange(currentCase, 0, tIcu.cases.length - 1); ngDevMode && assertNumberInRange(currentCase, 0, tIcu.cases.length - 1);
_removes = tIcu.remove[currentCase]; _removes = tIcu.remove[currentCase];
} else { } else {
_removes = EMPTY_ARRAY; _removes = EMPTY_ARRAY as any;
} }
} }
@ -61,16 +61,15 @@ export function loadIcuContainerVisitor() {
if (_index < _removes.length) { if (_index < _removes.length) {
const removeOpCode = _removes[_index++] as number; const removeOpCode = _removes[_index++] as number;
ngDevMode && assertNumber(removeOpCode, 'Expecting OpCode number'); ngDevMode && assertNumber(removeOpCode, 'Expecting OpCode number');
const opCode = removeOpCode & I18nMutateOpCode.MASK_INSTRUCTION; if (removeOpCode > 0) {
if (opCode === I18nMutateOpCode.Remove) { const rNode = _lView[removeOpCode];
const rNode = _lView[removeOpCode >>> I18nMutateOpCode.SHIFT_REF];
ngDevMode && assertDomNode(rNode); ngDevMode && assertDomNode(rNode);
return rNode; return rNode;
} else { } else {
ngDevMode &&
assertEqual(opCode, I18nMutateOpCode.RemoveNestedIcu, 'Expecting RemoveNestedIcu');
_stack.push(_index, _removes); _stack.push(_index, _removes);
const tIcu = _lView[TVIEW].data[removeOpCode >>> I18nMutateOpCode.SHIFT_REF] as TIcu; // ICUs are represented by negative indices
const tIcuIndex = ~removeOpCode;
const tIcu = _lView[TVIEW].data[tIcuIndex] as TIcu;
ngDevMode && assertTIcu(tIcu); ngDevMode && assertTIcu(tIcu);
enterIcu(tIcu, _lView); enterIcu(tIcu, _lView);
return icuContainerIteratorNext(); return icuContainerIteratorNext();

View File

@ -12,6 +12,18 @@ import {RNode} from './renderer';
import {SanitizerFn} from './sanitization'; import {SanitizerFn} from './sanitization';
import {LView} from './view'; import {LView} from './view';
/**
* Stores a list of nodes which need to be removed.
*
* Numbers are indexes into the `LView`
* - index > 0: `removeRNode(lView[0])`
* - index < 0: `removeICU(~lView[0])`
*/
export interface I18nRemoveOpCodes extends Array<number> {
__brand__: 'I18nRemoveOpCodes';
}
/** /**
* `I18nMutateOpCode` defines OpCodes for `I18nMutateOpCodes` array. * `I18nMutateOpCode` defines OpCodes for `I18nMutateOpCodes` array.
* *
@ -35,11 +47,11 @@ import {LView} from './view';
* *
* See: `I18nCreateOpCodes` for example of usage. * See: `I18nCreateOpCodes` for example of usage.
*/ */
export const enum I18nMutateOpCode { export const enum IcuCreateOpCode {
/** /**
* Stores shift amount for bits 17-3 that contain reference index. * Stores shift amount for bits 17-3 that contain reference index.
*/ */
SHIFT_REF = 3, SHIFT_REF = 1,
/** /**
* Stores shift amount for bits 31-17 that contain parent index. * Stores shift amount for bits 31-17 that contain parent index.
*/ */
@ -47,61 +59,124 @@ export const enum I18nMutateOpCode {
/** /**
* Mask for OpCode * Mask for OpCode
*/ */
// FIXME(misko): Shrink mask to 2 bits as 4 choices can fit into two bits. MASK_INSTRUCTION = 0b1,
MASK_INSTRUCTION = 0b111,
/** /**
* Mask for the Reference node (bits 16-3) * Mask for the Reference node (bits 16-3)
*/ */
// FIXME(misko): Why is this not used? MASK_REF = 0b11111111111111110,
MASK_REF = 0b11111111111111000,
// 11111110000000000 // 11111110000000000
// 65432109876543210 // 65432109876543210
/** /**
* Instruction to append the current node to `PARENT`. * Instruction to append the current node to `PARENT`.
*/ */
AppendChild = 0b001, AppendChild = 0b0,
/**
* Instruction to remove the `REF` node from `PARENT`.
*/
Remove = 0b011,
/** /**
* Instruction to set the attribute of a node. * Instruction to set the attribute of a node.
*/ */
Attr = 0b100, Attr = 0b1,
}
/**
* Array storing OpCode for dynamically creating `i18n` blocks.
*
* Example:
* ```ts
* <I18nCreateOpCode>[
* // For adding text nodes
* // ---------------------
* // Equivalent to:
* // lView[1].appendChild(lView[0] = document.createTextNode('xyz'));
* 'xyz', 0, 1 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
*
* // For adding element nodes
* // ---------------------
* // Equivalent to:
* // lView[1].appendChild(lView[0] = document.createElement('div'));
* ELEMENT_MARKER, 'div', 0, 1 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
*
* // For adding comment nodes
* // ---------------------
* // Equivalent to:
* // lView[1].appendChild(lView[0] = document.createComment(''));
* ICU_MARKER, '', 0, 1 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
*
* // For moving existing nodes to a different location
* // --------------------------------------------------
* // Equivalent to:
* // const node = lView[1];
* // lView[2].appendChild(node);
* 1 << SHIFT_REF | Select, 2 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
*
* // For removing existing nodes
* // --------------------------------------------------
* // const node = lView[1];
* // removeChild(tView.data(1), node, lView);
* 1 << SHIFT_REF | Remove,
*
* // For writing attributes
* // --------------------------------------------------
* // const node = lView[1];
* // node.setAttribute('attr', 'value');
* 1 << SHIFT_REF | Attr, 'attr', 'value'
* ];
* ```
*/
export interface IcuCreateOpCodes extends Array<number|string|ELEMENT_MARKER|ICU_MARKER|null>,
I18nDebug {
__brand__: 'I18nCreateOpCodes';
}
export const enum I18nUpdateOpCode {
/**
* Stores shift amount for bits 17-2 that contain reference index.
*/
SHIFT_REF = 2,
/**
* Mask for OpCode
*/
MASK_OPCODE = 0b11,
/** /**
* Instruction to removed the nested ICU. * Instruction to update a text node.
*/ */
RemoveNestedIcu = 0b110, Text = 0b00,
/**
* Instruction to update a attribute of a node.
*/
Attr = 0b01,
/**
* Instruction to switch the current ICU case.
*/
IcuSwitch = 0b10,
/**
* Instruction to update the current ICU case.
*/
IcuUpdate = 0b11,
} }
// FIXME(misko): These function are technically not interfaces, and so we may consider moving them // FIXME(misko): These function are technically not interfaces, and so we may consider moving them
// elsewhere. // elsewhere.
// FIXME(misko): rename to `getParentFromI18nCreateOpCode` export function getParentFromIcuCreateOpCode(mergedCode: number): number {
export function getParentFromI18nMutateOpCode(mergedCode: number): number { return mergedCode >>> IcuCreateOpCode.SHIFT_PARENT;
return mergedCode >>> I18nMutateOpCode.SHIFT_PARENT;
} }
// FIXME(misko): rename to `getRefFromI18nCreateOpCode` export function getRefFromIcuCreateOpCode(mergedCode: number): number {
export function getRefFromI18nMutateOpCode(mergedCode: number): number { return (mergedCode & IcuCreateOpCode.MASK_REF) >>> IcuCreateOpCode.SHIFT_REF;
return (mergedCode & I18nMutateOpCode.MASK_REF) >>> I18nMutateOpCode.SHIFT_REF;
} }
// FIXME(misko): rename to `getInstructionFromI18nCreateOpCode` export function getInstructionFromIcuCreateOpCode(mergedCode: number): number {
export function getInstructionFromI18nMutateOpCode(mergedCode: number): number { return mergedCode & IcuCreateOpCode.MASK_INSTRUCTION;
return mergedCode & I18nMutateOpCode.MASK_INSTRUCTION;
} }
// FIXME(misko): rename to `i18nCreateOpCode` export function icuCreateOpCode(opCode: IcuCreateOpCode, parentIdx: number, refIdx: number) {
export function i18nMutateOpCode(opCode: I18nMutateOpCode, parentIdx: number, refIdx: number) {
ngDevMode && assertGreaterThanOrEqual(parentIdx, 0, 'Missing parent index'); ngDevMode && assertGreaterThanOrEqual(parentIdx, 0, 'Missing parent index');
ngDevMode && assertGreaterThan(refIdx, 0, 'Missing ref index'); ngDevMode && assertGreaterThan(refIdx, 0, 'Missing ref index');
return opCode | parentIdx << I18nMutateOpCode.SHIFT_PARENT | refIdx << I18nMutateOpCode.SHIFT_REF; return opCode | parentIdx << IcuCreateOpCode.SHIFT_PARENT | refIdx << IcuCreateOpCode.SHIFT_REF;
} }
/** /**
@ -173,7 +248,9 @@ export interface I18nDebug {
* } * }
* ``` * ```
*/ */
export interface I18nCreateOpCodes extends Array<number|string>, I18nDebug {} export interface I18nCreateOpCodes extends Array<number|string>, I18nDebug {
__brand__: 'I18nCreateOpCodes';
}
/** /**
* See `I18nCreateOpCodes` * See `I18nCreateOpCodes`
@ -197,84 +274,6 @@ export enum I18nCreateOpCode {
} }
/**
* Array storing OpCode for dynamically creating `i18n` blocks.
*
* Example:
* ```ts
* <I18nCreateOpCode>[
* // For adding text nodes
* // ---------------------
* // Equivalent to:
* // lView[1].appendChild(lView[0] = document.createTextNode('xyz'));
* 'xyz', 0, 1 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
*
* // For adding element nodes
* // ---------------------
* // Equivalent to:
* // lView[1].appendChild(lView[0] = document.createElement('div'));
* ELEMENT_MARKER, 'div', 0, 1 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
*
* // For adding comment nodes
* // ---------------------
* // Equivalent to:
* // lView[1].appendChild(lView[0] = document.createComment(''));
* ICU_MARKER, '', 0, 1 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
*
* // For moving existing nodes to a different location
* // --------------------------------------------------
* // Equivalent to:
* // const node = lView[1];
* // lView[2].appendChild(node);
* 1 << SHIFT_REF | Select, 2 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
*
* // For removing existing nodes
* // --------------------------------------------------
* // const node = lView[1];
* // removeChild(tView.data(1), node, lView);
* 1 << SHIFT_REF | Remove,
*
* // For writing attributes
* // --------------------------------------------------
* // const node = lView[1];
* // node.setAttribute('attr', 'value');
* 1 << SHIFT_REF | Attr, 'attr', 'value'
* ];
* ```
*
* See: `applyI18nCreateOpCodes`;
*/
export interface I18nMutateOpCodes extends Array<number|string|ELEMENT_MARKER|ICU_MARKER|null>,
I18nDebug {}
export const enum I18nUpdateOpCode {
/**
* Stores shift amount for bits 17-2 that contain reference index.
*/
SHIFT_REF = 2,
/**
* Mask for OpCode
*/
MASK_OPCODE = 0b11,
/**
* Instruction to update a text node.
*/
Text = 0b00,
/**
* Instruction to update a attribute of a node.
*/
Attr = 0b01,
/**
* Instruction to switch the current ICU case.
*/
IcuSwitch = 0b10,
/**
* Instruction to update the current ICU case.
*/
IcuUpdate = 0b11,
}
/** /**
* Stores DOM operations which need to be applied to update DOM render tree due to changes in * Stores DOM operations which need to be applied to update DOM render tree due to changes in
* expressions. * expressions.
@ -347,7 +346,9 @@ export const enum I18nUpdateOpCode {
* ``` * ```
* *
*/ */
export interface I18nUpdateOpCodes extends Array<string|number|SanitizerFn|null>, I18nDebug {} export interface I18nUpdateOpCodes extends Array<string|number|SanitizerFn|null>, I18nDebug {
__brand__: 'I18nUpdateOpCodes';
}
/** /**
* Store information for the i18n translation block. * Store information for the i18n translation block.
@ -410,14 +411,12 @@ export interface TIcu {
/** /**
* A set of OpCodes to apply in order to build up the DOM render tree for the ICU * A set of OpCodes to apply in order to build up the DOM render tree for the ICU
*/ */
// FIXME(misko): Rename `I18nMutateOpCodes` to `I18nCreateOpCodes`. create: IcuCreateOpCodes[];
create: I18nMutateOpCodes[];
/** /**
* A set of OpCodes to apply in order to destroy the DOM render tree for the ICU. * A set of OpCodes to apply in order to destroy the DOM render tree for the ICU.
*/ */
// FIXME(misko): Rename `I18nMutateOpCodes` to `I18nRemoveOpCodes`. remove: I18nRemoveOpCodes[];
remove: I18nMutateOpCodes[];
/** /**
* A set of OpCodes to apply in order to update the DOM render tree for the ICU bindings. * A set of OpCodes to apply in order to update the DOM render tree for the ICU bindings.

View File

@ -31,7 +31,7 @@ export function attachDebugObject(obj: any, debug: any): void {
* @param obj Object to patch * @param obj Object to patch
* @param debugGetter Getter returning a value to patch * @param debugGetter Getter returning a value to patch
*/ */
export function attachDebugGetter(obj: any, debugGetter: () => any): void { export function attachDebugGetter<T>(obj: T, debugGetter: (this: T) => any): void {
if (ngDevMode) { if (ngDevMode) {
Object.defineProperty(obj, 'debug', {get: debugGetter, enumerable: false}); Object.defineProperty(obj, 'debug', {get: debugGetter, enumerable: false});
} else { } else {

View File

@ -10,7 +10,7 @@ import {ɵɵi18nApply, ɵɵi18nExp} from '@angular/core';
import {applyCreateOpCodes} from '@angular/core/src/render3/i18n/i18n_apply'; import {applyCreateOpCodes} from '@angular/core/src/render3/i18n/i18n_apply';
import {i18nStartFirstCreatePass} from '@angular/core/src/render3/i18n/i18n_parse'; import {i18nStartFirstCreatePass} from '@angular/core/src/render3/i18n/i18n_parse';
import {getTIcu} from '@angular/core/src/render3/i18n/i18n_util'; import {getTIcu} from '@angular/core/src/render3/i18n/i18n_util';
import {IcuType, TI18n} from '@angular/core/src/render3/interfaces/i18n'; import {I18nUpdateOpCodes, IcuType, TI18n} from '@angular/core/src/render3/interfaces/i18n';
import {HEADER_OFFSET} from '@angular/core/src/render3/interfaces/view'; import {HEADER_OFFSET} from '@angular/core/src/render3/interfaces/view';
import {expect} from '@angular/core/testing/src/testing_internal'; import {expect} from '@angular/core/testing/src/testing_internal';
import {matchTI18n, matchTIcu} from '../matchers'; import {matchTI18n, matchTIcu} from '../matchers';
@ -29,7 +29,7 @@ describe('i18n_parse', () => {
'lView[22] = document.createText("some text");', 'lView[22] = document.createText("some text");',
'parent.appendChild(lView[22]);', 'parent.appendChild(lView[22]);',
]), ]),
update: [], update: [] as unknown as I18nUpdateOpCodes,
})); }));
fixture.apply(() => applyCreateOpCodes(fixture.lView, tI18n.create, fixture.host, null)); fixture.apply(() => applyCreateOpCodes(fixture.lView, tI18n.create, fixture.host, null));
@ -88,8 +88,8 @@ describe('i18n_parse', () => {
matchDebug([]), matchDebug([]),
], ],
remove: [ remove: [
matchDebug(['(lView[0] as Element).remove(lView[25])']), matchDebug(['remove(lView[25])']),
matchDebug(['(lView[0] as Element).remove(lView[26])']) matchDebug(['remove(lView[26])']),
], ],
})); }));
@ -204,13 +204,13 @@ describe('i18n_parse', () => {
], ],
remove: [ remove: [
matchDebug([ matchDebug([
'(lView[0] as Element).remove(lView[26])', 'remove(lView[26])',
'removeNestedICU(27)', 'removeNestedICU(27)',
'(lView[0] as Element).remove(lView[27])', 'remove(lView[27])',
'(lView[0] as Element).remove(lView[31])', 'remove(lView[31])',
]), ]),
matchDebug([ matchDebug([
'(lView[0] as Element).remove(lView[32])', 'remove(lView[32])',
]) ])
], ],
})); }));
@ -237,8 +237,8 @@ describe('i18n_parse', () => {
]), ]),
], ],
remove: [ remove: [
matchDebug(['(lView[0] as Element).remove(lView[29])']), matchDebug(['remove(lView[29])']),
matchDebug(['(lView[0] as Element).remove(lView[30])']) matchDebug(['remove(lView[30])']),
], ],
})); }));

View File

@ -86,7 +86,7 @@ describe('Runtime i18n', () => {
`lView[${HEADER_OFFSET + 1}] = document.createText("simple text");`, `lView[${HEADER_OFFSET + 1}] = document.createText("simple text");`,
`parent.appendChild(lView[${HEADER_OFFSET + 1}]);`, `parent.appendChild(lView[${HEADER_OFFSET + 1}]);`,
]), ]),
update: [], update: [] as unknown as I18nUpdateOpCodes,
}); });
}); });
@ -113,7 +113,7 @@ describe('Runtime i18n', () => {
`lView[${HEADER_OFFSET + 8}] = document.createText("!");`, `lView[${HEADER_OFFSET + 8}] = document.createText("!");`,
`parent.appendChild(lView[${HEADER_OFFSET + 8}]);`, `parent.appendChild(lView[${HEADER_OFFSET + 8}]);`,
]), ]),
update: [], update: [] as unknown as I18nUpdateOpCodes,
}); });
}); });
@ -212,7 +212,7 @@ describe('Runtime i18n', () => {
`lView[${HEADER_OFFSET + 3}] = document.createText("before");`, `lView[${HEADER_OFFSET + 3}] = document.createText("before");`,
`lView[${HEADER_OFFSET + 4}] = document.createText("after");`, `lView[${HEADER_OFFSET + 4}] = document.createText("after");`,
]), ]),
update: [], update: [] as unknown as I18nUpdateOpCodes,
}); });
@ -229,7 +229,7 @@ describe('Runtime i18n', () => {
create: matchDebug([ create: matchDebug([
`lView[${HEADER_OFFSET + 2}] = document.createText("middle");`, `lView[${HEADER_OFFSET + 2}] = document.createText("middle");`,
]), ]),
update: [], update: [] as unknown as I18nUpdateOpCodes,
}); });
}); });
@ -293,17 +293,17 @@ describe('Runtime i18n', () => {
], ],
remove: [ remove: [
matchDebug([ matchDebug([
'(lView[0] as Element).remove(lView[24])', 'remove(lView[24])',
'(lView[0] as Element).remove(lView[25])', 'remove(lView[25])',
'(lView[0] as Element).remove(lView[27])', 'remove(lView[27])',
]), ]),
matchDebug([ matchDebug([
'(lView[0] as Element).remove(lView[28])', 'remove(lView[28])',
'(lView[0] as Element).remove(lView[29])', 'remove(lView[29])',
]), ]),
matchDebug([ matchDebug([
'(lView[0] as Element).remove(lView[31])', 'remove(lView[31])',
'(lView[0] as Element).remove(lView[32])', 'remove(lView[32])',
]), ]),
], ],
update: [ update: [
@ -372,13 +372,13 @@ describe('Runtime i18n', () => {
], ],
remove: [ remove: [
matchDebug([ matchDebug([
'(lView[0] as Element).remove(lView[24])', 'remove(lView[24])',
]), ]),
matchDebug([ matchDebug([
'(lView[0] as Element).remove(lView[25])', 'remove(lView[25])',
'removeNestedICU(26)', 'removeNestedICU(26)',
'(lView[0] as Element).remove(lView[26])', 'remove(lView[26])',
'(lView[0] as Element).remove(lView[31])', 'remove(lView[31])',
]), ]),
], ],
}); });
@ -407,9 +407,9 @@ describe('Runtime i18n', () => {
matchDebug([]), matchDebug([]),
], ],
remove: [ remove: [
matchDebug(['(lView[0] as Element).remove(lView[28])']), matchDebug(['remove(lView[28])']),
matchDebug(['(lView[0] as Element).remove(lView[29])']), matchDebug(['remove(lView[29])']),
matchDebug(['(lView[0] as Element).remove(lView[30])']) matchDebug(['remove(lView[30])']),
], ],
}); });
}); });

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {i18nCreateOpCodesToString, i18nMutateOpCodesToString, i18nUpdateOpCodesToString} from '@angular/core/src/render3/i18n/i18n_debug'; import {i18nCreateOpCodesToString, i18nRemoveOpCodesToString, i18nUpdateOpCodesToString, icuCreateOpCodesToString} from '@angular/core/src/render3/i18n/i18n_debug';
import {ELEMENT_MARKER, I18nCreateOpCode, I18nMutateOpCode, I18nUpdateOpCode, ICU_MARKER} from '@angular/core/src/render3/interfaces/i18n'; import {ELEMENT_MARKER, I18nCreateOpCode, I18nCreateOpCodes, I18nRemoveOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, ICU_MARKER, IcuCreateOpCode} from '@angular/core/src/render3/interfaces/i18n';
describe('i18n debug', () => { describe('i18n debug', () => {
describe('i18nUpdateOpCodesToString', () => { describe('i18nUpdateOpCodesToString', () => {
it('should print nothing', () => { it('should print nothing', () => {
expect(i18nUpdateOpCodesToString([])).toEqual([]); expect(i18nUpdateOpCodesToString([] as unknown as I18nUpdateOpCodes)).toEqual([]);
}); });
it('should print text opCode', () => { it('should print text opCode', () => {
@ -23,7 +23,7 @@ describe('i18n debug', () => {
-4, -4,
' post', ' post',
1 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text, 1 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text,
])) ] as unknown as I18nUpdateOpCodes))
.toEqual( .toEqual(
['if (mask & 0b11) { (lView[1] as Text).textContent = `pre ${lView[i-4]} post`; }']); ['if (mask & 0b11) { (lView[1] as Text).textContent = `pre ${lView[i-4]} post`; }']);
}); });
@ -39,8 +39,8 @@ describe('i18n debug', () => {
'pre ', -4, 'pre ', -4,
' in ', -3, ' in ', -3,
' post', 1 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr, ' post', 1 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr,
'title', (v) => v, 'title', (v: any) => v,
])) ] as unknown as I18nUpdateOpCodes))
.toEqual([ .toEqual([
'if (mask & 0b1) { (lView[1] as Element).setAttribute(\'title\', `pre ${lView[i-4]} in ${lView[i-3]} post`); }', 'if (mask & 0b1) { (lView[1] as Element).setAttribute(\'title\', `pre ${lView[i-4]} in ${lView[i-3]} post`); }',
'if (mask & 0b10) { (lView[1] as Element).setAttribute(\'title\', (function (v) { return v; })(`pre ${lView[i-4]} in ${lView[i-3]} post`)); }' 'if (mask & 0b10) { (lView[1] as Element).setAttribute(\'title\', (function (v) { return v; })(`pre ${lView[i-4]} in ${lView[i-3]} post`)); }'
@ -48,29 +48,31 @@ describe('i18n debug', () => {
}); });
it('should print icuSwitch opCode', () => { it('should print icuSwitch opCode', () => {
expect(i18nUpdateOpCodesToString([ expect(i18nUpdateOpCodesToString(
0b100, 2, -5, 12 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuSwitch [0b100, 2, -5, 12 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuSwitch] as
])).toEqual(['if (mask & 0b100) { icuSwitchCase(12, `${lView[i-5]}`); }']); unknown as I18nUpdateOpCodes))
.toEqual(['if (mask & 0b100) { icuSwitchCase(12, `${lView[i-5]}`); }']);
}); });
it('should print icuUpdate opCode', () => { it('should print icuUpdate opCode', () => {
expect(i18nUpdateOpCodesToString([ expect(i18nUpdateOpCodesToString(
0b1000, 1, 13 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuUpdate [0b1000, 1, 13 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuUpdate] as
])).toEqual(['if (mask & 0b1000) { icuUpdateCase(13); }']); unknown as I18nUpdateOpCodes))
.toEqual(['if (mask & 0b1000) { icuUpdateCase(13); }']);
}); });
}); });
describe('i18nMutateOpCodesToString', () => { describe('i18nMutateOpCodesToString', () => {
it('should print nothing', () => { it('should print nothing', () => {
expect(i18nMutateOpCodesToString([])).toEqual([]); expect(icuCreateOpCodesToString([] as unknown as I18nCreateOpCodes)).toEqual([]);
}); });
it('should print text AppendChild', () => { it('should print text AppendChild', () => {
expect(i18nMutateOpCodesToString([ expect(icuCreateOpCodesToString([
'xyz', 0, 'xyz', 0,
1 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF | 1 << IcuCreateOpCode.SHIFT_PARENT | 0 << IcuCreateOpCode.SHIFT_REF |
I18nMutateOpCode.AppendChild IcuCreateOpCode.AppendChild
])) ] as unknown as I18nCreateOpCodes))
.toEqual([ .toEqual([
'lView[0] = document.createTextNode("xyz")', 'lView[0] = document.createTextNode("xyz")',
'(lView[1] as Element).appendChild(lView[0])' '(lView[1] as Element).appendChild(lView[0])'
@ -79,11 +81,11 @@ describe('i18n debug', () => {
it('should print element AppendChild', () => { it('should print element AppendChild', () => {
expect(i18nMutateOpCodesToString([ expect(icuCreateOpCodesToString([
ELEMENT_MARKER, 'xyz', 0, ELEMENT_MARKER, 'xyz', 0,
1 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF | 1 << IcuCreateOpCode.SHIFT_PARENT | 0 << IcuCreateOpCode.SHIFT_REF |
I18nMutateOpCode.AppendChild IcuCreateOpCode.AppendChild
])) ] as unknown as I18nCreateOpCodes))
.toEqual([ .toEqual([
'lView[0] = document.createElement("xyz")', 'lView[0] = document.createElement("xyz")',
'(lView[1] as Element).appendChild(lView[0])' '(lView[1] as Element).appendChild(lView[0])'
@ -91,11 +93,11 @@ describe('i18n debug', () => {
}); });
it('should print comment AppendChild', () => { it('should print comment AppendChild', () => {
expect(i18nMutateOpCodesToString([ expect(icuCreateOpCodesToString([
ICU_MARKER, 'xyz', 0, ICU_MARKER, 'xyz', 0,
1 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF | 1 << IcuCreateOpCode.SHIFT_PARENT | 0 << IcuCreateOpCode.SHIFT_REF |
I18nMutateOpCode.AppendChild IcuCreateOpCode.AppendChild
])) ] as unknown as I18nCreateOpCodes))
.toEqual([ .toEqual([
'lView[0] = document.createComment("xyz")', 'lView[0] = document.createComment("xyz")',
'(lView[1] as Element).appendChild(lView[0])' '(lView[1] as Element).appendChild(lView[0])'
@ -103,28 +105,28 @@ describe('i18n debug', () => {
}); });
it('should print Remove', () => { it('should print Remove', () => {
expect(i18nMutateOpCodesToString([ expect(i18nRemoveOpCodesToString([123] as unknown as I18nRemoveOpCodes)).toEqual([
2 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF | 'remove(lView[123])'
I18nMutateOpCode.Remove ]);
])).toEqual(['(lView[2] as Element).remove(lView[0])']);
}); });
it('should print Attr', () => { it('should print Attr', () => {
expect(i18nMutateOpCodesToString([ expect(icuCreateOpCodesToString(
1 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Attr, 'attr', 'value' [1 << IcuCreateOpCode.SHIFT_REF | IcuCreateOpCode.Attr, 'attr', 'value'] as
])).toEqual(['(lView[1] as Element).setAttribute("attr", "value")']); unknown as I18nCreateOpCodes))
.toEqual(['(lView[1] as Element).setAttribute("attr", "value")']);
}); });
it('should print RemoveNestedIcu', () => { it('should print RemoveNestedIcu', () => {
expect(i18nMutateOpCodesToString([ expect(i18nRemoveOpCodesToString([~123] as unknown as I18nRemoveOpCodes)).toEqual([
1 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.RemoveNestedIcu, 'removeNestedICU(123)'
])).toEqual(['removeNestedICU(1)']); ]);
}); });
}); });
describe('i18nCreateOpCodesToString', () => { describe('i18nCreateOpCodesToString', () => {
it('should print nothing', () => { it('should print nothing', () => {
expect(i18nCreateOpCodesToString([])).toEqual([]); expect(i18nCreateOpCodesToString([] as unknown as I18nCreateOpCodes)).toEqual([]);
}); });
it('should print text/comment creation', () => { it('should print text/comment creation', () => {
@ -134,7 +136,7 @@ describe('i18n debug', () => {
12 << I18nCreateOpCode.SHIFT | I18nCreateOpCode.COMMENT, 'comment at 12', // 12 << I18nCreateOpCode.SHIFT | I18nCreateOpCode.COMMENT, 'comment at 12', //
13 << I18nCreateOpCode.SHIFT | I18nCreateOpCode.COMMENT | I18nCreateOpCode.APPEND_EAGERLY, 13 << I18nCreateOpCode.SHIFT | I18nCreateOpCode.COMMENT | I18nCreateOpCode.APPEND_EAGERLY,
'comment at 13, append', // 'comment at 13, append', //
])) ] as unknown as I18nCreateOpCodes))
.toEqual([ .toEqual([
'lView[10] = document.createText("text at 10");', 'lView[10] = document.createText("text at 10");',
'lView[11] = document.createText("text at 11, append");', 'lView[11] = document.createText("text at 11, append");',

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {I18nDebug, I18nMutateOpCodes, TI18n, TIcu} from '@angular/core/src/render3/interfaces/i18n'; import {I18nDebug, IcuCreateOpCodes, TI18n, TIcu} from '@angular/core/src/render3/interfaces/i18n';
import {TNode} from '@angular/core/src/render3/interfaces/node'; import {TNode} from '@angular/core/src/render3/interfaces/node';
import {TView} from '@angular/core/src/render3/interfaces/view'; import {TView} from '@angular/core/src/render3/interfaces/view';
@ -233,7 +233,7 @@ export function matchDomText(expectedText: string|undefined = undefined):
} }
export function matchI18nMutableOpCodes(expectedMutableOpCodes: string[]): export function matchI18nMutableOpCodes(expectedMutableOpCodes: string[]):
jasmine.AsymmetricMatcher<I18nMutateOpCodes> { jasmine.AsymmetricMatcher<IcuCreateOpCodes> {
const matcher = function() {}; const matcher = function() {};
let _actual: any = null; let _actual: any = null;