refactor(core): add human readable debug
for i18n (#38154)
I18n code breaks up internationalization into opCodes which are then stored in arrays. To make it easier to debug the codebase this PR adds `debug` property to the arrays which presents the data in human readable format. PR Close #38154
This commit is contained in:
parent
18cd1a9937
commit
3821dc5f6c
@ -62,7 +62,7 @@
|
|||||||
"bundle": "TODO(i): we should define ngDevMode to false in Closure, but --define only works in the global scope.",
|
"bundle": "TODO(i): we should define ngDevMode to false in Closure, but --define only works in the global scope.",
|
||||||
"bundle": "TODO(i): (FW-2164) TS 3.9 new class shape seems to have broken Closure in big ways. The size went from 169991 to 252338",
|
"bundle": "TODO(i): (FW-2164) TS 3.9 new class shape seems to have broken Closure in big ways. The size went from 169991 to 252338",
|
||||||
"bundle": "TODO(i): after removal of tsickle from ngc-wrapped / ng_package, we had to switch to SIMPLE optimizations which increased the size from 252338 to 1198917, see PR#37221 and PR#37317 for more info",
|
"bundle": "TODO(i): after removal of tsickle from ngc-wrapped / ng_package, we had to switch to SIMPLE optimizations which increased the size from 252338 to 1198917, see PR#37221 and PR#37317 for more info",
|
||||||
"bundle": 1209659
|
"bundle": 1212027
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import '../util/ng_i18n_closure_mode';
|
import '../util/ng_i18n_closure_mode';
|
||||||
|
import '../util/ng_dev_mode';
|
||||||
|
|
||||||
import {DEFAULT_LOCALE_ID, getPluralCase} from '../i18n/localization';
|
import {DEFAULT_LOCALE_ID, getPluralCase} from '../i18n/localization';
|
||||||
import {getTemplateContent, SRCSET_ATTRS, URI_ATTRS, VALID_ATTRS, VALID_ELEMENTS} from '../sanitization/html_sanitizer';
|
import {getTemplateContent, SRCSET_ATTRS, URI_ATTRS, VALID_ATTRS, VALID_ELEMENTS} from '../sanitization/html_sanitizer';
|
||||||
@ -16,8 +17,8 @@ import {assertDataInRange, assertDefined, assertEqual} from '../util/assert';
|
|||||||
|
|
||||||
import {bindingUpdated} from './bindings';
|
import {bindingUpdated} from './bindings';
|
||||||
import {attachPatchData} from './context_discovery';
|
import {attachPatchData} from './context_discovery';
|
||||||
|
import {i18nMutateOpCodesToString, i18nUpdateOpCodesToString} from './i18n_debug';
|
||||||
import {setDelayProjection} from './instructions/all';
|
import {setDelayProjection} from './instructions/all';
|
||||||
import {attachI18nOpCodesDebug} from './instructions/lview_debug';
|
|
||||||
import {allocExpando, elementAttributeInternal, elementPropertyInternal, getOrCreateTNode, setInputsForProperty, setNgReflectProperties, textBindingInternal} from './instructions/shared';
|
import {allocExpando, elementAttributeInternal, elementPropertyInternal, getOrCreateTNode, setInputsForProperty, setNgReflectProperties, textBindingInternal} from './instructions/shared';
|
||||||
import {LContainer, NATIVE} from './interfaces/container';
|
import {LContainer, NATIVE} from './interfaces/container';
|
||||||
import {getDocument} from './interfaces/document';
|
import {getDocument} from './interfaces/document';
|
||||||
@ -29,6 +30,7 @@ import {isLContainer} from './interfaces/type_checks';
|
|||||||
import {HEADER_OFFSET, LView, RENDERER, T_HOST, TVIEW, TView} from './interfaces/view';
|
import {HEADER_OFFSET, LView, RENDERER, T_HOST, TVIEW, TView} from './interfaces/view';
|
||||||
import {appendChild, applyProjection, createTextNode, nativeRemoveNode} from './node_manipulation';
|
import {appendChild, applyProjection, createTextNode, nativeRemoveNode} from './node_manipulation';
|
||||||
import {getBindingIndex, getIsParent, getLView, getPreviousOrParentTNode, getTView, nextBindingIndex, setIsNotParent, setPreviousOrParentTNode} from './state';
|
import {getBindingIndex, getIsParent, getLView, getPreviousOrParentTNode, getTView, nextBindingIndex, setIsNotParent, setPreviousOrParentTNode} from './state';
|
||||||
|
import {attachDebugGetter} from './util/debug_utils';
|
||||||
import {renderStringify} from './util/misc_utils';
|
import {renderStringify} from './util/misc_utils';
|
||||||
import {getNativeByIndex, getNativeByTNode, getTNode, load} from './util/view_utils';
|
import {getNativeByIndex, getNativeByTNode, getTNode, load} from './util/view_utils';
|
||||||
|
|
||||||
@ -267,6 +269,9 @@ function generateBindingUpdateOpCodes(
|
|||||||
str: string, destinationNode: number, attrName?: string,
|
str: string, destinationNode: number, attrName?: string,
|
||||||
sanitizeFn: SanitizerFn|null = null): I18nUpdateOpCodes {
|
sanitizeFn: SanitizerFn|null = null): I18nUpdateOpCodes {
|
||||||
const updateOpCodes: I18nUpdateOpCodes = [null, null]; // Alloc space for mask and size
|
const updateOpCodes: I18nUpdateOpCodes = [null, null]; // Alloc space for mask and size
|
||||||
|
if (ngDevMode) {
|
||||||
|
attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString);
|
||||||
|
}
|
||||||
const textParts = str.split(BINDING_REGEXP);
|
const textParts = str.split(BINDING_REGEXP);
|
||||||
let mask = 0;
|
let mask = 0;
|
||||||
|
|
||||||
@ -395,6 +400,9 @@ function i18nStartFirstPass(
|
|||||||
let parentIndexPointer = 0;
|
let parentIndexPointer = 0;
|
||||||
parentIndexStack[parentIndexPointer] = parentIndex;
|
parentIndexStack[parentIndexPointer] = parentIndex;
|
||||||
const createOpCodes: I18nMutateOpCodes = [];
|
const createOpCodes: I18nMutateOpCodes = [];
|
||||||
|
if (ngDevMode) {
|
||||||
|
attachDebugGetter(createOpCodes, i18nMutateOpCodesToString);
|
||||||
|
}
|
||||||
// If the previous node wasn't the direct parent then we have a translation without top level
|
// If the previous node wasn't the direct parent then we have a translation without top level
|
||||||
// element and we need to keep a reference of the previous element if there is one. We should also
|
// element and we need to keep a reference of the previous element if there is one. We should also
|
||||||
// keep track whether an element was a parent node or not, so that the logic that consumes
|
// keep track whether an element was a parent node or not, so that the logic that consumes
|
||||||
@ -411,6 +419,9 @@ function i18nStartFirstPass(
|
|||||||
createOpCodes.push(previousTNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select);
|
createOpCodes.push(previousTNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select);
|
||||||
}
|
}
|
||||||
const updateOpCodes: I18nUpdateOpCodes = [];
|
const updateOpCodes: I18nUpdateOpCodes = [];
|
||||||
|
if (ngDevMode) {
|
||||||
|
attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString);
|
||||||
|
}
|
||||||
const icuExpressions: TIcu[] = [];
|
const icuExpressions: TIcu[] = [];
|
||||||
|
|
||||||
if (message === '' && isRootTemplateMessage(subTemplateIndex)) {
|
if (message === '' && isRootTemplateMessage(subTemplateIndex)) {
|
||||||
@ -507,10 +518,6 @@ function i18nStartFirstPass(
|
|||||||
allocExpando(tView, lView, i18nVarsCount);
|
allocExpando(tView, lView, i18nVarsCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngDevMode &&
|
|
||||||
attachI18nOpCodesDebug(
|
|
||||||
createOpCodes, updateOpCodes, icuExpressions.length ? icuExpressions : null, lView);
|
|
||||||
|
|
||||||
// NOTE: local var needed to properly assert the type of `TI18n`.
|
// NOTE: local var needed to properly assert the type of `TI18n`.
|
||||||
const tI18n: TI18n = {
|
const tI18n: TI18n = {
|
||||||
vars: i18nVarsCount,
|
vars: i18nVarsCount,
|
||||||
@ -780,7 +787,7 @@ function readCreateOpCodes(
|
|||||||
visitedNodes.push(textNodeIndex);
|
visitedNodes.push(textNodeIndex);
|
||||||
setIsNotParent();
|
setIsNotParent();
|
||||||
} else if (typeof opCode == 'number') {
|
} else if (typeof opCode == 'number') {
|
||||||
switch (opCode & I18nMutateOpCode.MASK_OPCODE) {
|
switch (opCode & I18nMutateOpCode.MASK_INSTRUCTION) {
|
||||||
case I18nMutateOpCode.AppendChild:
|
case I18nMutateOpCode.AppendChild:
|
||||||
const destinationNodeIndex = opCode >>> I18nMutateOpCode.SHIFT_PARENT;
|
const destinationNodeIndex = opCode >>> I18nMutateOpCode.SHIFT_PARENT;
|
||||||
let destinationTNode: TNode;
|
let destinationTNode: TNode;
|
||||||
@ -799,9 +806,10 @@ function readCreateOpCodes(
|
|||||||
appendI18nNode(tView, currentTNode!, destinationTNode, previousTNode, lView);
|
appendI18nNode(tView, currentTNode!, destinationTNode, previousTNode, lView);
|
||||||
break;
|
break;
|
||||||
case I18nMutateOpCode.Select:
|
case I18nMutateOpCode.Select:
|
||||||
// Negative indicies indicate that a given TNode is a sibling node, not a parent node
|
// Negative indices indicate that a given TNode is a sibling node, not a parent node
|
||||||
// (see `i18nStartFirstPass` for additional information).
|
// (see `i18nStartFirstPass` for additional information).
|
||||||
const isParent = opCode >= 0;
|
const isParent = opCode >= 0;
|
||||||
|
// FIXME(misko): This SHIFT_REF looks suspect as it does not have mask.
|
||||||
const nodeIndex = (isParent ? opCode : ~opCode) >>> I18nMutateOpCode.SHIFT_REF;
|
const nodeIndex = (isParent ? opCode : ~opCode) >>> I18nMutateOpCode.SHIFT_REF;
|
||||||
visitedNodes.push(nodeIndex);
|
visitedNodes.push(nodeIndex);
|
||||||
previousTNode = currentTNode;
|
previousTNode = currentTNode;
|
||||||
@ -890,7 +898,6 @@ function readUpdateOpCodes(
|
|||||||
value += opCode;
|
value += opCode;
|
||||||
} else if (typeof opCode == 'number') {
|
} else if (typeof opCode == 'number') {
|
||||||
if (opCode < 0) {
|
if (opCode < 0) {
|
||||||
// It's a binding index whose value is negative
|
|
||||||
value += renderStringify(lView[bindingsStartIndex - opCode]);
|
value += renderStringify(lView[bindingsStartIndex - opCode]);
|
||||||
} else {
|
} else {
|
||||||
const nodeIndex = opCode >>> I18nUpdateOpCode.SHIFT_REF;
|
const nodeIndex = opCode >>> I18nUpdateOpCode.SHIFT_REF;
|
||||||
@ -909,6 +916,7 @@ function readUpdateOpCodes(
|
|||||||
textBindingInternal(lView, nodeIndex, value);
|
textBindingInternal(lView, nodeIndex, value);
|
||||||
break;
|
break;
|
||||||
case I18nUpdateOpCode.IcuSwitch:
|
case I18nUpdateOpCode.IcuSwitch:
|
||||||
|
// FIXME(misko): Pull to a new function `icuSwitchCase`
|
||||||
tIcuIndex = updateOpCodes[++j] as number;
|
tIcuIndex = updateOpCodes[++j] as number;
|
||||||
tIcu = icus![tIcuIndex];
|
tIcu = icus![tIcuIndex];
|
||||||
icuTNode = getTNode(tView, nodeIndex) as TIcuContainerNode;
|
icuTNode = getTNode(tView, nodeIndex) as TIcuContainerNode;
|
||||||
@ -917,7 +925,7 @@ function readUpdateOpCodes(
|
|||||||
const removeCodes = tIcu.remove[icuTNode.activeCaseIndex];
|
const removeCodes = tIcu.remove[icuTNode.activeCaseIndex];
|
||||||
for (let k = 0; k < removeCodes.length; k++) {
|
for (let k = 0; k < removeCodes.length; k++) {
|
||||||
const removeOpCode = removeCodes[k] as number;
|
const removeOpCode = removeCodes[k] as number;
|
||||||
switch (removeOpCode & I18nMutateOpCode.MASK_OPCODE) {
|
switch (removeOpCode & I18nMutateOpCode.MASK_INSTRUCTION) {
|
||||||
case I18nMutateOpCode.Remove:
|
case I18nMutateOpCode.Remove:
|
||||||
const nodeIndex = removeOpCode >>> I18nMutateOpCode.SHIFT_REF;
|
const nodeIndex = removeOpCode >>> I18nMutateOpCode.SHIFT_REF;
|
||||||
// Remove DOM element, but do *not* mark TNode as detached, since we are
|
// Remove DOM element, but do *not* mark TNode as detached, since we are
|
||||||
@ -951,6 +959,7 @@ function readUpdateOpCodes(
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case I18nUpdateOpCode.IcuUpdate:
|
case I18nUpdateOpCode.IcuUpdate:
|
||||||
|
// FIXME(misko): Pull to a new function `icuUpdateCase`
|
||||||
tIcuIndex = updateOpCodes[++j] as number;
|
tIcuIndex = updateOpCodes[++j] as number;
|
||||||
tIcu = icus![tIcuIndex];
|
tIcu = icus![tIcuIndex];
|
||||||
icuTNode = getTNode(tView, nodeIndex) as TIcuContainerNode;
|
icuTNode = getTNode(tView, nodeIndex) as TIcuContainerNode;
|
||||||
@ -1044,6 +1053,9 @@ function i18nAttributesFirstPass(lView: LView, tView: TView, index: number, valu
|
|||||||
const previousElement = getPreviousOrParentTNode();
|
const previousElement = getPreviousOrParentTNode();
|
||||||
const previousElementIndex = previousElement.index - HEADER_OFFSET;
|
const previousElementIndex = previousElement.index - HEADER_OFFSET;
|
||||||
const updateOpCodes: I18nUpdateOpCodes = [];
|
const updateOpCodes: I18nUpdateOpCodes = [];
|
||||||
|
if (ngDevMode) {
|
||||||
|
attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString);
|
||||||
|
}
|
||||||
for (let i = 0; i < values.length; i += 2) {
|
for (let i = 0; i < values.length; i += 2) {
|
||||||
const attrName = values[i];
|
const attrName = values[i];
|
||||||
const message = values[i + 1];
|
const message = values[i + 1];
|
||||||
@ -1180,9 +1192,9 @@ function getCaseIndex(icuExpression: TIcu, bindingValue: string): number {
|
|||||||
function icuStart(
|
function icuStart(
|
||||||
tIcus: TIcu[], icuExpression: IcuExpression, startIndex: number,
|
tIcus: TIcu[], icuExpression: IcuExpression, startIndex: number,
|
||||||
expandoStartIndex: number): void {
|
expandoStartIndex: number): void {
|
||||||
const createCodes = [];
|
const createCodes: I18nMutateOpCodes[] = [];
|
||||||
const removeCodes = [];
|
const removeCodes: I18nMutateOpCodes[] = [];
|
||||||
const updateCodes = [];
|
const updateCodes: I18nUpdateOpCodes[] = [];
|
||||||
const vars = [];
|
const vars = [];
|
||||||
const childIcus: number[][] = [];
|
const childIcus: number[][] = [];
|
||||||
for (let i = 0; i < icuExpression.values.length; i++) {
|
for (let i = 0; i < icuExpression.values.length; i++) {
|
||||||
@ -1240,6 +1252,11 @@ function parseIcuCase(
|
|||||||
}
|
}
|
||||||
const wrapper = getTemplateContent(inertBodyElement!) as Element || inertBodyElement;
|
const wrapper = getTemplateContent(inertBodyElement!) as Element || inertBodyElement;
|
||||||
const opCodes: IcuCase = {vars: 0, childIcus: [], create: [], remove: [], update: []};
|
const opCodes: IcuCase = {vars: 0, childIcus: [], create: [], remove: [], update: []};
|
||||||
|
if (ngDevMode) {
|
||||||
|
attachDebugGetter(opCodes.create, i18nMutateOpCodesToString);
|
||||||
|
attachDebugGetter(opCodes.remove, i18nMutateOpCodesToString);
|
||||||
|
attachDebugGetter(opCodes.update, i18nUpdateOpCodesToString);
|
||||||
|
}
|
||||||
parseNodes(wrapper.firstChild, opCodes, parentIndex, nestedIcus, tIcus, expandoStartIndex);
|
parseNodes(wrapper.firstChild, opCodes, parentIndex, nestedIcus, tIcus, expandoStartIndex);
|
||||||
return opCodes;
|
return opCodes;
|
||||||
}
|
}
|
||||||
@ -1364,6 +1381,7 @@ function parseNodes(
|
|||||||
3, // skip 3 opCodes if not changed
|
3, // skip 3 opCodes if not changed
|
||||||
-1 - nestedIcu.mainBinding,
|
-1 - nestedIcu.mainBinding,
|
||||||
nestedIcuNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuSwitch,
|
nestedIcuNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuSwitch,
|
||||||
|
// FIXME(misko): Index should be part of the opcode
|
||||||
nestTIcuIndex,
|
nestTIcuIndex,
|
||||||
mask, // mask of all the bindings of this ICU expression
|
mask, // mask of all the bindings of this ICU expression
|
||||||
2, // skip 2 opCodes if not changed
|
2, // skip 2 opCodes if not changed
|
||||||
@ -1371,6 +1389,7 @@ function parseNodes(
|
|||||||
nestTIcuIndex);
|
nestTIcuIndex);
|
||||||
icuCase.remove.push(
|
icuCase.remove.push(
|
||||||
nestTIcuIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.RemoveNestedIcu,
|
nestTIcuIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.RemoveNestedIcu,
|
||||||
|
// FIXME(misko): Index should be part of the opcode
|
||||||
nestedIcuNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove);
|
nestedIcuNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
187
packages/core/src/render3/i18n_debug.ts
Normal file
187
packages/core/src/render3/i18n_debug.ts
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google LLC All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {assertNumber, assertString} from '../util/assert';
|
||||||
|
|
||||||
|
import {COMMENT_MARKER, ELEMENT_MARKER, getInstructionFromI18nMutateOpCode, getParentFromI18nMutateOpCode, getRefFromI18nMutateOpCode, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes} from './interfaces/i18n';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts `I18nUpdateOpCodes` array into a human readable format.
|
||||||
|
*
|
||||||
|
* This function is attached to the `I18nUpdateOpCodes.debug` property 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 `I18nUpdateOpCodes` if attached as a method.
|
||||||
|
* @param opcodes `I18nUpdateOpCodes` if invoked as a function.
|
||||||
|
*/
|
||||||
|
export function i18nUpdateOpCodesToString(
|
||||||
|
this: I18nUpdateOpCodes|void, opcodes?: I18nUpdateOpCodes): string[] {
|
||||||
|
const parser = new OpCodeParser(opcodes || (Array.isArray(this) ? this : []));
|
||||||
|
let lines: string[] = [];
|
||||||
|
|
||||||
|
function consumeOpCode(value: number): string {
|
||||||
|
const ref = value >>> I18nUpdateOpCode.SHIFT_REF;
|
||||||
|
const opCode = value & I18nUpdateOpCode.MASK_OPCODE;
|
||||||
|
switch (opCode) {
|
||||||
|
case I18nUpdateOpCode.Text:
|
||||||
|
return `(lView[${ref}] as Text).textContent = $$$`;
|
||||||
|
case I18nUpdateOpCode.Attr:
|
||||||
|
const attrName = parser.consumeString();
|
||||||
|
const sanitizationFn = parser.consumeFunction();
|
||||||
|
const value = sanitizationFn ? `(${sanitizationFn})($$$)` : '$$$';
|
||||||
|
return `(lView[${ref}] as Element).setAttribute('${attrName}', ${value})`;
|
||||||
|
case I18nUpdateOpCode.IcuSwitch:
|
||||||
|
return `icuSwitchCase(lView[${ref}] as Comment, ${parser.consumeNumber()}, $$$)`;
|
||||||
|
case I18nUpdateOpCode.IcuUpdate:
|
||||||
|
return `icuUpdateCase(lView[${ref}] as Comment, ${parser.consumeNumber()})`;
|
||||||
|
}
|
||||||
|
throw new Error('unexpected OpCode');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
while (parser.hasMore()) {
|
||||||
|
let mask = parser.consumeNumber();
|
||||||
|
let size = parser.consumeNumber();
|
||||||
|
const end = parser.i + size;
|
||||||
|
const statements: string[] = [];
|
||||||
|
let statement = '';
|
||||||
|
while (parser.i < end) {
|
||||||
|
let value = parser.consumeNumberOrString();
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
statement += value;
|
||||||
|
} else if (value < 0) {
|
||||||
|
// Negative numbers are ref indexes
|
||||||
|
statement += '${lView[' + (0 - value) + ']}';
|
||||||
|
} else {
|
||||||
|
// Positive numbers are operations.
|
||||||
|
const opCodeText = consumeOpCode(value);
|
||||||
|
statements.push(opCodeText.replace('$$$', '`' + statement + '`') + ';');
|
||||||
|
statement = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lines.push(`if (mask & 0b${mask.toString(2)}) { ${statements.join(' ')} }`);
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts `I18nMutableOpCodes` array into a human readable format.
|
||||||
|
*
|
||||||
|
* This function is attached to the `I18nMutableOpCodes.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 `I18nMutableOpCodes` if attached as a method.
|
||||||
|
* @param opcodes `I18nMutableOpCodes` if invoked as a function.
|
||||||
|
*/
|
||||||
|
export function i18nMutateOpCodesToString(
|
||||||
|
this: I18nMutateOpCodes|void, opcodes?: I18nMutateOpCodes): string[] {
|
||||||
|
const parser = new OpCodeParser(opcodes || (Array.isArray(this) ? this : []));
|
||||||
|
let lines: string[] = [];
|
||||||
|
|
||||||
|
function consumeOpCode(opCode: number): string {
|
||||||
|
const parent = getParentFromI18nMutateOpCode(opCode);
|
||||||
|
const ref = getRefFromI18nMutateOpCode(opCode);
|
||||||
|
switch (getInstructionFromI18nMutateOpCode(opCode)) {
|
||||||
|
case I18nMutateOpCode.Select:
|
||||||
|
lastRef = ref;
|
||||||
|
return '';
|
||||||
|
case I18nMutateOpCode.AppendChild:
|
||||||
|
return `(lView[${parent}] as Element).appendChild(lView[${lastRef}])`;
|
||||||
|
case I18nMutateOpCode.Remove:
|
||||||
|
return `(lView[${parent}] as Element).remove(lView[${ref}])`;
|
||||||
|
case I18nMutateOpCode.Attr:
|
||||||
|
return `(lView[${ref}] as Element).setAttribute("${parser.consumeString()}", "${
|
||||||
|
parser.consumeString()}")`;
|
||||||
|
case I18nMutateOpCode.ElementEnd:
|
||||||
|
return `setPreviousOrParentTNode(tView.data[${ref}] as TNode)`;
|
||||||
|
case I18nMutateOpCode.RemoveNestedIcu:
|
||||||
|
// FIXME(misko): refactor to have a real method in i18n.ts.
|
||||||
|
return `removeNestedICU(${ref})`;
|
||||||
|
}
|
||||||
|
throw new Error('Unexpected OpCode');
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastRef = -1;
|
||||||
|
while (parser.hasMore()) {
|
||||||
|
let value = parser.consumeNumberStringOrMarker();
|
||||||
|
if (value === COMMENT_MARKER) {
|
||||||
|
const text = parser.consumeString();
|
||||||
|
lastRef = parser.consumeNumber();
|
||||||
|
lines.push(`lView[${lastRef}] = document.createComment("${text}")`);
|
||||||
|
} else if (value === ELEMENT_MARKER) {
|
||||||
|
const text = parser.consumeString();
|
||||||
|
lastRef = parser.consumeNumber();
|
||||||
|
lines.push(`lView[${lastRef}] = document.createElement("${text}")`);
|
||||||
|
} else if (typeof value === 'string') {
|
||||||
|
lastRef = parser.consumeNumber();
|
||||||
|
lines.push(`lView[${lastRef}] = document.createTextNode("${value}")`);
|
||||||
|
} else if (typeof value === 'number') {
|
||||||
|
const line = consumeOpCode(value);
|
||||||
|
line && lines.push(line);
|
||||||
|
} else {
|
||||||
|
throw new Error('Unexpected value');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class OpCodeParser {
|
||||||
|
i: number = 0;
|
||||||
|
codes: any[];
|
||||||
|
|
||||||
|
constructor(codes: any[]) {
|
||||||
|
this.codes = codes;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasMore() {
|
||||||
|
return this.i < this.codes.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
consumeNumber(): number {
|
||||||
|
let value = this.codes[this.i++];
|
||||||
|
assertNumber(value, 'expecting number in OpCode');
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
consumeString(): string {
|
||||||
|
let value = this.codes[this.i++];
|
||||||
|
assertString(value, 'expecting string in OpCode');
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
consumeFunction(): Function|null {
|
||||||
|
let value = this.codes[this.i++];
|
||||||
|
if (value === null || typeof value === 'function') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
throw new Error('expecting function in OpCode');
|
||||||
|
}
|
||||||
|
|
||||||
|
consumeNumberOrString(): number|string {
|
||||||
|
let value = this.codes[this.i++];
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
assertNumber(value, 'expecting number or string in OpCode');
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
consumeNumberStringOrMarker(): number|string|COMMENT_MARKER|ELEMENT_MARKER {
|
||||||
|
let value = this.codes[this.i++];
|
||||||
|
if (typeof value === 'string' || typeof value === 'number' || value == COMMENT_MARKER ||
|
||||||
|
value == ELEMENT_MARKER) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
assertNumber(value, 'expecting number, string, COMMENT_MARKER or ELEMENT_MARKER in OpCode');
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
@ -550,206 +550,3 @@ export function readLViewValue(value: any): LView|null {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class I18NDebugItem {
|
|
||||||
[key: string]: any;
|
|
||||||
|
|
||||||
get tNode() {
|
|
||||||
return getTNode(this._lView[TVIEW], this.nodeIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
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[];
|
|
||||||
}
|
|
||||||
|
@ -236,6 +236,13 @@ export function getOrCreateTNode(
|
|||||||
const tNode = tView.data[adjustedIndex] as TNode ||
|
const tNode = tView.data[adjustedIndex] as TNode ||
|
||||||
createTNodeAtIndex(tView, tHostNode, adjustedIndex, type, name, attrs);
|
createTNodeAtIndex(tView, tHostNode, adjustedIndex, type, name, attrs);
|
||||||
setPreviousOrParentTNode(tNode, true);
|
setPreviousOrParentTNode(tNode, true);
|
||||||
|
if (ngDevMode) {
|
||||||
|
// For performance reasons it is important that the tNode retains the same shape during runtime.
|
||||||
|
// (To make sure that all of the code is monomorphic.) For this reason we seal the object to
|
||||||
|
// prevent class transitions.
|
||||||
|
// FIXME(misko): re-enable this once i18n code is compliant with this.
|
||||||
|
// Object.seal(tNode);
|
||||||
|
}
|
||||||
return tNode as TElementNode & TViewNode & TContainerNode & TElementContainerNode &
|
return tNode as TElementNode & TViewNode & TContainerNode & TElementContainerNode &
|
||||||
TProjectionNode & TIcuContainerNode;
|
TProjectionNode & TIcuContainerNode;
|
||||||
}
|
}
|
||||||
|
@ -6,18 +6,31 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {SanitizerFn} from './sanitization';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `I18nMutateOpCode` defines OpCodes for `I18nMutateOpCodes` array.
|
* `I18nMutateOpCode` defines OpCodes for `I18nMutateOpCodes` array.
|
||||||
*
|
*
|
||||||
|
* OpCodes are efficient operations which can be applied to the DOM to update it. (For example to
|
||||||
|
* update to a new ICU case requires that we clean up previous elements and create new ones.)
|
||||||
|
*
|
||||||
* OpCodes contain three parts:
|
* OpCodes contain three parts:
|
||||||
* 1) Parent node index offset.
|
* 1) Parent node index offset. (p)
|
||||||
* 2) Reference node index offset.
|
* 2) Reference node index offset. (r)
|
||||||
* 3) The OpCode to execute.
|
* 3) The instruction to execute. (i)
|
||||||
|
*
|
||||||
|
* pppp pppp pppp pppp rrrr rrrr rrrr riii
|
||||||
|
* 3322 2222 2222 1111 1111 1110 0000 0000
|
||||||
|
* 1098 7654 3210 9876 5432 1098 7654 3210
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* var parent = lView[opCode >>> SHIFT_PARENT];
|
||||||
|
* var refNode = lView[((opCode & MASK_REF) >>> SHIFT_REF)];
|
||||||
|
* var instruction = opCode & MASK_OPCODE;
|
||||||
|
* ```
|
||||||
*
|
*
|
||||||
* See: `I18nCreateOpCodes` for example of usage.
|
* See: `I18nCreateOpCodes` for example of usage.
|
||||||
*/
|
*/
|
||||||
import {SanitizerFn} from './sanitization';
|
|
||||||
|
|
||||||
export const enum I18nMutateOpCode {
|
export const enum I18nMutateOpCode {
|
||||||
/**
|
/**
|
||||||
* Stores shift amount for bits 17-3 that contain reference index.
|
* Stores shift amount for bits 17-3 that contain reference index.
|
||||||
@ -30,36 +43,61 @@ export const enum I18nMutateOpCode {
|
|||||||
/**
|
/**
|
||||||
* Mask for OpCode
|
* Mask for OpCode
|
||||||
*/
|
*/
|
||||||
MASK_OPCODE = 0b111,
|
MASK_INSTRUCTION = 0b111,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpCode to select a node. (next OpCode will contain the operation.)
|
* Mask for the Reference node (bits 16-3)
|
||||||
|
*/
|
||||||
|
// FIXME(misko): Why is this not used?
|
||||||
|
MASK_REF = 0b11111111111111000,
|
||||||
|
// 11111110000000000
|
||||||
|
// 65432109876543210
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instruction to select a node. (next OpCode will contain the operation.)
|
||||||
*/
|
*/
|
||||||
Select = 0b000,
|
Select = 0b000,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpCode to append the current node to `PARENT`.
|
* Instruction to append the current node to `PARENT`.
|
||||||
*/
|
*/
|
||||||
AppendChild = 0b001,
|
AppendChild = 0b001,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpCode to remove the `REF` node from `PARENT`.
|
* Instruction to remove the `REF` node from `PARENT`.
|
||||||
*/
|
*/
|
||||||
Remove = 0b011,
|
Remove = 0b011,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpCode to set the attribute of a node.
|
* Instruction to set the attribute of a node.
|
||||||
*/
|
*/
|
||||||
Attr = 0b100,
|
Attr = 0b100,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpCode to simulate elementEnd()
|
* Instruction to simulate elementEnd()
|
||||||
*/
|
*/
|
||||||
ElementEnd = 0b101,
|
ElementEnd = 0b101,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpCode to read the remove OpCodes for the nested ICU
|
* Instruction to removed the nested ICU.
|
||||||
*/
|
*/
|
||||||
RemoveNestedIcu = 0b110,
|
RemoveNestedIcu = 0b110,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getParentFromI18nMutateOpCode(mergedCode: number): number {
|
||||||
|
return mergedCode >>> I18nMutateOpCode.SHIFT_PARENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRefFromI18nMutateOpCode(mergedCode: number): number {
|
||||||
|
return (mergedCode & I18nMutateOpCode.MASK_REF) >>> I18nMutateOpCode.SHIFT_REF;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInstructionFromI18nMutateOpCode(mergedCode: number): number {
|
||||||
|
return mergedCode & I18nMutateOpCode.MASK_INSTRUCTION;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks that the next string is for element.
|
* Marks that the next string is an element name.
|
||||||
*
|
*
|
||||||
* See `I18nMutateOpCodes` documentation.
|
* See `I18nMutateOpCodes` documentation.
|
||||||
*/
|
*/
|
||||||
@ -71,7 +109,7 @@ export interface ELEMENT_MARKER {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks that the next string is for comment.
|
* Marks that the next string is comment text.
|
||||||
*
|
*
|
||||||
* See `I18nMutateOpCodes` documentation.
|
* See `I18nMutateOpCodes` documentation.
|
||||||
*/
|
*/
|
||||||
@ -83,6 +121,18 @@ export interface COMMENT_MARKER {
|
|||||||
marker: 'comment';
|
marker: 'comment';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface I18nDebug {
|
||||||
|
/**
|
||||||
|
* Human readable representation of the OpCode arrays.
|
||||||
|
*
|
||||||
|
* NOTE: This property only exists if `ngDevMode` is set to `true` and it is not present in
|
||||||
|
* production. Its presence is purely to help debug issue in development, and should not be relied
|
||||||
|
* on in production application.
|
||||||
|
*/
|
||||||
|
debug?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array storing OpCode for dynamically creating `i18n` blocks.
|
* Array storing OpCode for dynamically creating `i18n` blocks.
|
||||||
*
|
*
|
||||||
@ -92,50 +142,27 @@ export interface COMMENT_MARKER {
|
|||||||
* // For adding text nodes
|
* // For adding text nodes
|
||||||
* // ---------------------
|
* // ---------------------
|
||||||
* // Equivalent to:
|
* // Equivalent to:
|
||||||
* // const node = lView[index++] = document.createTextNode('abc');
|
* // lView[1].appendChild(lView[0] = document.createTextNode('xyz'));
|
||||||
* // lView[1].insertBefore(node, lView[2]);
|
* 'xyz', 0, 1 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
|
||||||
* 'abc', 1 << SHIFT_PARENT | 2 << SHIFT_REF | InsertBefore,
|
|
||||||
*
|
|
||||||
* // Equivalent to:
|
|
||||||
* // const node = lView[index++] = document.createTextNode('xyz');
|
|
||||||
* // lView[1].appendChild(node);
|
|
||||||
* 'xyz', 1 << SHIFT_PARENT | AppendChild,
|
|
||||||
*
|
*
|
||||||
* // For adding element nodes
|
* // For adding element nodes
|
||||||
* // ---------------------
|
* // ---------------------
|
||||||
* // Equivalent to:
|
* // Equivalent to:
|
||||||
* // const node = lView[index++] = document.createElement('div');
|
* // lView[1].appendChild(lView[0] = document.createElement('div'));
|
||||||
* // lView[1].insertBefore(node, lView[2]);
|
* ELEMENT_MARKER, 'div', 0, 1 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
|
||||||
* ELEMENT_MARKER, 'div', 1 << SHIFT_PARENT | 2 << SHIFT_REF | InsertBefore,
|
|
||||||
*
|
|
||||||
* // Equivalent to:
|
|
||||||
* // const node = lView[index++] = document.createElement('div');
|
|
||||||
* // lView[1].appendChild(node);
|
|
||||||
* ELEMENT_MARKER, 'div', 1 << SHIFT_PARENT | AppendChild,
|
|
||||||
*
|
*
|
||||||
* // For adding comment nodes
|
* // For adding comment nodes
|
||||||
* // ---------------------
|
* // ---------------------
|
||||||
* // Equivalent to:
|
* // Equivalent to:
|
||||||
* // const node = lView[index++] = document.createComment('');
|
* // lView[1].appendChild(lView[0] = document.createComment(''));
|
||||||
* // lView[1].insertBefore(node, lView[2]);
|
* COMMENT_MARKER, '', 0, 1 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
|
||||||
* COMMENT_MARKER, '', 1 << SHIFT_PARENT | 2 << SHIFT_REF | InsertBefore,
|
|
||||||
*
|
|
||||||
* // Equivalent to:
|
|
||||||
* // const node = lView[index++] = document.createComment('');
|
|
||||||
* // lView[1].appendChild(node);
|
|
||||||
* COMMENT_MARKER, '', 1 << SHIFT_PARENT | AppendChild,
|
|
||||||
*
|
*
|
||||||
* // For moving existing nodes to a different location
|
* // For moving existing nodes to a different location
|
||||||
* // --------------------------------------------------
|
* // --------------------------------------------------
|
||||||
* // Equivalent to:
|
* // Equivalent to:
|
||||||
* // const node = lView[1];
|
* // const node = lView[1];
|
||||||
* // lView[2].insertBefore(node, lView[3]);
|
|
||||||
* 1 << SHIFT_REF | Select, 2 << SHIFT_PARENT | 3 << SHIFT_REF | InsertBefore,
|
|
||||||
*
|
|
||||||
* // Equivalent to:
|
|
||||||
* // const node = lView[1];
|
|
||||||
* // lView[2].appendChild(node);
|
* // lView[2].appendChild(node);
|
||||||
* 1 << SHIFT_REF | Select, 2 << SHIFT_PARENT | AppendChild,
|
* 1 << SHIFT_REF | Select, 2 << SHIFT_PARENT | 0 << SHIFT_REF | AppendChild,
|
||||||
*
|
*
|
||||||
* // For removing existing nodes
|
* // For removing existing nodes
|
||||||
* // --------------------------------------------------
|
* // --------------------------------------------------
|
||||||
@ -147,18 +174,14 @@ export interface COMMENT_MARKER {
|
|||||||
* // --------------------------------------------------
|
* // --------------------------------------------------
|
||||||
* // const node = lView[1];
|
* // const node = lView[1];
|
||||||
* // node.setAttribute('attr', 'value');
|
* // node.setAttribute('attr', 'value');
|
||||||
* 1 << SHIFT_REF | Select, 'attr', 'value'
|
* 1 << SHIFT_REF | Attr, 'attr', 'value'
|
||||||
* // NOTE: Select followed by two string (vs select followed by OpCode)
|
|
||||||
* ];
|
* ];
|
||||||
* ```
|
* ```
|
||||||
* NOTE:
|
|
||||||
* - `index` is initial location where the extra nodes should be stored in the EXPANDO section of
|
|
||||||
* `LVIewData`.
|
|
||||||
*
|
*
|
||||||
* See: `applyI18nCreateOpCodes`;
|
* See: `applyI18nCreateOpCodes`;
|
||||||
*/
|
*/
|
||||||
export interface I18nMutateOpCodes extends Array<number|string|ELEMENT_MARKER|COMMENT_MARKER|null> {
|
export interface I18nMutateOpCodes extends Array<number|string|ELEMENT_MARKER|COMMENT_MARKER|null>,
|
||||||
}
|
I18nDebug {}
|
||||||
|
|
||||||
export const enum I18nUpdateOpCode {
|
export const enum I18nUpdateOpCode {
|
||||||
/**
|
/**
|
||||||
@ -171,19 +194,19 @@ export const enum I18nUpdateOpCode {
|
|||||||
MASK_OPCODE = 0b11,
|
MASK_OPCODE = 0b11,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OpCode to update a text node.
|
* Instruction to update a text node.
|
||||||
*/
|
*/
|
||||||
Text = 0b00,
|
Text = 0b00,
|
||||||
/**
|
/**
|
||||||
* OpCode to update a attribute of a node.
|
* Instruction to update a attribute of a node.
|
||||||
*/
|
*/
|
||||||
Attr = 0b01,
|
Attr = 0b01,
|
||||||
/**
|
/**
|
||||||
* OpCode to switch the current ICU case.
|
* Instruction to switch the current ICU case.
|
||||||
*/
|
*/
|
||||||
IcuSwitch = 0b10,
|
IcuSwitch = 0b10,
|
||||||
/**
|
/**
|
||||||
* OpCode to update the current ICU case.
|
* Instruction to update the current ICU case.
|
||||||
*/
|
*/
|
||||||
IcuUpdate = 0b11,
|
IcuUpdate = 0b11,
|
||||||
}
|
}
|
||||||
@ -197,6 +220,10 @@ export const enum I18nUpdateOpCode {
|
|||||||
* higher.) The OpCodes then compare its own change mask against the expression change mask to
|
* higher.) The OpCodes then compare its own change mask against the expression change mask to
|
||||||
* determine if the OpCodes should execute.
|
* determine if the OpCodes should execute.
|
||||||
*
|
*
|
||||||
|
* NOTE: 32nd bit is special as it says 32nd or higher. This way if we have more than 32 bindings
|
||||||
|
* the code still works, but with lower efficiency. (it is unlikely that a translation would have
|
||||||
|
* more than 32 bindings.)
|
||||||
|
*
|
||||||
* These OpCodes can be used by both the i18n block as well as ICU sub-block.
|
* These OpCodes can be used by both the i18n block as well as ICU sub-block.
|
||||||
*
|
*
|
||||||
* ## Example
|
* ## Example
|
||||||
@ -220,8 +247,8 @@ export const enum I18nUpdateOpCode {
|
|||||||
* // The following OpCodes represent: `<div i18n-title="pre{{exp1}}in{{exp2}}post">`
|
* // The following OpCodes represent: `<div i18n-title="pre{{exp1}}in{{exp2}}post">`
|
||||||
* // If `changeMask & 0b11`
|
* // If `changeMask & 0b11`
|
||||||
* // has changed then execute update OpCodes.
|
* // has changed then execute update OpCodes.
|
||||||
* // has NOT changed then skip `7` values and start processing next OpCodes.
|
* // has NOT changed then skip `8` values and start processing next OpCodes.
|
||||||
* 0b11, 7,
|
* 0b11, 8,
|
||||||
* // Concatenate `newValue = 'pre'+lView[bindIndex-4]+'in'+lView[bindIndex-3]+'post';`.
|
* // Concatenate `newValue = 'pre'+lView[bindIndex-4]+'in'+lView[bindIndex-3]+'post';`.
|
||||||
* 'pre', -4, 'in', -3, 'post',
|
* 'pre', -4, 'in', -3, 'post',
|
||||||
* // Update attribute: `elementAttribute(1, 'title', sanitizerFn(newValue));`
|
* // Update attribute: `elementAttribute(1, 'title', sanitizerFn(newValue));`
|
||||||
@ -240,8 +267,8 @@ export const enum I18nUpdateOpCode {
|
|||||||
* // The following OpCodes represent: `<div i18n>{exp4, plural, ... }">`
|
* // The following OpCodes represent: `<div i18n>{exp4, plural, ... }">`
|
||||||
* // If `changeMask & 0b1000`
|
* // If `changeMask & 0b1000`
|
||||||
* // has changed then execute update OpCodes.
|
* // has changed then execute update OpCodes.
|
||||||
* // has NOT changed then skip `4` values and start processing next OpCodes.
|
* // has NOT changed then skip `2` values and start processing next OpCodes.
|
||||||
* 0b1000, 4,
|
* 0b1000, 2,
|
||||||
* // Concatenate `newValue = lView[bindIndex -1];`.
|
* // Concatenate `newValue = lView[bindIndex -1];`.
|
||||||
* -1,
|
* -1,
|
||||||
* // Switch ICU: `icuSwitchCase(lView[1], 0, newValue);`
|
* // Switch ICU: `icuSwitchCase(lView[1], 0, newValue);`
|
||||||
@ -256,7 +283,7 @@ export const enum I18nUpdateOpCode {
|
|||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export interface I18nUpdateOpCodes extends Array<string|number|SanitizerFn|null> {}
|
export interface I18nUpdateOpCodes extends Array<string|number|SanitizerFn|null>, I18nDebug {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store information for the i18n translation block.
|
* Store information for the i18n translation block.
|
||||||
|
@ -701,7 +701,9 @@ export interface TIcuContainerNode extends TNode {
|
|||||||
/**
|
/**
|
||||||
* Indicates the current active case for an ICU expression.
|
* Indicates the current active case for an ICU expression.
|
||||||
* It is null when there is no active case.
|
* It is null when there is no active case.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
|
// FIXME(misko): This is at a wrong location as activeCase is `LView` (not `TView`) concern
|
||||||
activeCaseIndex: number|null;
|
activeCaseIndex: number|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,37 @@
|
|||||||
* Use of this source code is governed by an MIT-style license that can be
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
export function attachDebugObject(obj: any, debug: any) {
|
|
||||||
|
/**
|
||||||
|
* Patch a `debug` property on top of the existing object.
|
||||||
|
*
|
||||||
|
* NOTE: always call this method with `ngDevMode && attachDebugObject(...)`
|
||||||
|
*
|
||||||
|
* @param obj Object to patch
|
||||||
|
* @param debug Value to patch
|
||||||
|
*/
|
||||||
|
export function attachDebugObject(obj: any, debug: any): void {
|
||||||
|
if (ngDevMode) {
|
||||||
Object.defineProperty(obj, 'debug', {value: debug, enumerable: false});
|
Object.defineProperty(obj, 'debug', {value: debug, enumerable: false});
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
'This method should be guarded with `ngDevMode` so that it can be tree shaken in production!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch a `debug` property getter on top of the existing object.
|
||||||
|
*
|
||||||
|
* NOTE: always call this method with `ngDevMode && attachDebugObject(...)`
|
||||||
|
*
|
||||||
|
* @param obj Object to patch
|
||||||
|
* @param debugGetter Getter returning a value to patch
|
||||||
|
*/
|
||||||
|
export function attachDebugGetter(obj: any, debugGetter: () => any): void {
|
||||||
|
if (ngDevMode) {
|
||||||
|
Object.defineProperty(obj, 'debug', {get: debugGetter, enumerable: false});
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
'This method should be guarded with `ngDevMode` so that it can be tree shaken in production!');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
140
packages/core/test/render3/i18n_debug_spec.ts
Normal file
140
packages/core/test/render3/i18n_debug_spec.ts
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google LLC All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {i18nMutateOpCodesToString, i18nUpdateOpCodesToString} from '@angular/core/src/render3/i18n_debug';
|
||||||
|
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nUpdateOpCode} from '@angular/core/src/render3/interfaces/i18n';
|
||||||
|
|
||||||
|
describe('i18n debug', () => {
|
||||||
|
describe('i18nUpdateOpCodesToString', () => {
|
||||||
|
it('should print nothing', () => {
|
||||||
|
expect(i18nUpdateOpCodesToString([])).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should print text opCode', () => {
|
||||||
|
expect(i18nUpdateOpCodesToString([
|
||||||
|
0b11,
|
||||||
|
4,
|
||||||
|
'pre ',
|
||||||
|
-4,
|
||||||
|
' post',
|
||||||
|
1 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text,
|
||||||
|
]))
|
||||||
|
.toEqual(
|
||||||
|
['if (mask & 0b11) { (lView[1] as Text).textContent = `pre ${lView[4]} post`; }']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should print Attribute opCode', () => {
|
||||||
|
expect(i18nUpdateOpCodesToString([
|
||||||
|
0b01, 8,
|
||||||
|
'pre ', -4,
|
||||||
|
' in ', -3,
|
||||||
|
' post', 1 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr,
|
||||||
|
'title', null,
|
||||||
|
0b10, 8,
|
||||||
|
'pre ', -4,
|
||||||
|
' in ', -3,
|
||||||
|
' post', 1 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr,
|
||||||
|
'title', (v) => v,
|
||||||
|
]))
|
||||||
|
.toEqual([
|
||||||
|
'if (mask & 0b1) { (lView[1] as Element).setAttribute(\'title\', `pre ${lView[4]} in ${lView[3]} post`); }',
|
||||||
|
'if (mask & 0b10) { (lView[1] as Element).setAttribute(\'title\', (function (v) { return v; })(`pre ${lView[4]} in ${lView[3]} post`)); }'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should print icuSwitch opCode', () => {
|
||||||
|
expect(i18nUpdateOpCodesToString([
|
||||||
|
0b100, 2, -5, 12 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuSwitch,
|
||||||
|
2 // FIXME(misko): Should be part of IcuSwitch
|
||||||
|
])).toEqual(['if (mask & 0b100) { icuSwitchCase(lView[12] as Comment, 2, `${lView[5]}`); }']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should print icuUpdate opCode', () => {
|
||||||
|
expect(i18nUpdateOpCodesToString([
|
||||||
|
0b1000, 2, 13 << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuUpdate,
|
||||||
|
3 // FIXME(misko): should be part of IcuUpdate
|
||||||
|
])).toEqual(['if (mask & 0b1000) { icuUpdateCase(lView[13] as Comment, 3); }']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('i18nMutateOpCodesToString', () => {
|
||||||
|
it('should print nothing', () => {
|
||||||
|
expect(i18nMutateOpCodesToString([])).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should print Move', () => {
|
||||||
|
expect(i18nMutateOpCodesToString([
|
||||||
|
1 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
|
||||||
|
2 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF |
|
||||||
|
I18nMutateOpCode.AppendChild,
|
||||||
|
])).toEqual(['(lView[2] as Element).appendChild(lView[1])']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should print text AppendChild', () => {
|
||||||
|
expect(i18nMutateOpCodesToString([
|
||||||
|
'xyz', 0,
|
||||||
|
1 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF |
|
||||||
|
I18nMutateOpCode.AppendChild
|
||||||
|
]))
|
||||||
|
.toEqual([
|
||||||
|
'lView[0] = document.createTextNode("xyz")',
|
||||||
|
'(lView[1] as Element).appendChild(lView[0])'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should print element AppendChild', () => {
|
||||||
|
expect(i18nMutateOpCodesToString([
|
||||||
|
ELEMENT_MARKER, 'xyz', 0,
|
||||||
|
1 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF |
|
||||||
|
I18nMutateOpCode.AppendChild
|
||||||
|
]))
|
||||||
|
.toEqual([
|
||||||
|
'lView[0] = document.createElement("xyz")',
|
||||||
|
'(lView[1] as Element).appendChild(lView[0])'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should print comment AppendChild', () => {
|
||||||
|
expect(i18nMutateOpCodesToString([
|
||||||
|
COMMENT_MARKER, 'xyz', 0,
|
||||||
|
1 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF |
|
||||||
|
I18nMutateOpCode.AppendChild
|
||||||
|
]))
|
||||||
|
.toEqual([
|
||||||
|
'lView[0] = document.createComment("xyz")',
|
||||||
|
'(lView[1] as Element).appendChild(lView[0])'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should print Remove', () => {
|
||||||
|
expect(i18nMutateOpCodesToString([
|
||||||
|
2 << I18nMutateOpCode.SHIFT_PARENT | 0 << I18nMutateOpCode.SHIFT_REF |
|
||||||
|
I18nMutateOpCode.Remove
|
||||||
|
])).toEqual(['(lView[2] as Element).remove(lView[0])']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should print Attr', () => {
|
||||||
|
expect(i18nMutateOpCodesToString([
|
||||||
|
1 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Attr, 'attr', 'value'
|
||||||
|
])).toEqual(['(lView[1] as Element).setAttribute("attr", "value")']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should print ElementEnd', () => {
|
||||||
|
expect(i18nMutateOpCodesToString([
|
||||||
|
1 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
|
||||||
|
])).toEqual(['setPreviousOrParentTNode(tView.data[1] as TNode)']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should print RemoveNestedIcu', () => {
|
||||||
|
expect(i18nMutateOpCodesToString([
|
||||||
|
1 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.RemoveNestedIcu,
|
||||||
|
])).toEqual(['removeNestedICU(1)']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -9,10 +9,11 @@
|
|||||||
import {noop} from '../../../compiler/src/render3/view/util';
|
import {noop} from '../../../compiler/src/render3/view/util';
|
||||||
import {getTranslationForTemplate, ɵɵi18nAttributes, ɵɵi18nPostprocess, ɵɵi18nStart} from '../../src/render3/i18n';
|
import {getTranslationForTemplate, ɵɵi18nAttributes, ɵɵi18nPostprocess, ɵɵi18nStart} from '../../src/render3/i18n';
|
||||||
import {setDelayProjection, ɵɵelementEnd, ɵɵelementStart} from '../../src/render3/instructions/all';
|
import {setDelayProjection, ɵɵelementEnd, ɵɵelementStart} from '../../src/render3/instructions/all';
|
||||||
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nUpdateOpCode, I18nUpdateOpCodes, TI18n} from '../../src/render3/interfaces/i18n';
|
import {I18nUpdateOpCodes, TI18n, TIcu} from '../../src/render3/interfaces/i18n';
|
||||||
import {HEADER_OFFSET, LView, TVIEW} from '../../src/render3/interfaces/view';
|
import {HEADER_OFFSET, LView, TVIEW} from '../../src/render3/interfaces/view';
|
||||||
import {getNativeByIndex} from '../../src/render3/util/view_utils';
|
import {getNativeByIndex} from '../../src/render3/util/view_utils';
|
||||||
import {TemplateFixture} from './render_util';
|
import {TemplateFixture} from './render_util';
|
||||||
|
import {debugMatch} from './utils';
|
||||||
|
|
||||||
describe('Runtime i18n', () => {
|
describe('Runtime i18n', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -73,24 +74,14 @@ describe('Runtime i18n', () => {
|
|||||||
const index = 0;
|
const index = 0;
|
||||||
const opCodes = getOpCodes(() => {
|
const opCodes = getOpCodes(() => {
|
||||||
ɵɵi18nStart(index, MSG_DIV);
|
ɵɵi18nStart(index, MSG_DIV);
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index) as TI18n;
|
||||||
|
|
||||||
// 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({
|
expect(opCodes).toEqual({
|
||||||
vars: 1,
|
vars: 1,
|
||||||
create: [
|
create: debugMatch([
|
||||||
'simple text', nbConsts,
|
'lView[1] = document.createTextNode("simple text")',
|
||||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
|
'(lView[0] as Element).appendChild(lView[1])'
|
||||||
],
|
]),
|
||||||
update: [],
|
update: [],
|
||||||
icus: null
|
icus: null
|
||||||
});
|
});
|
||||||
@ -102,37 +93,28 @@ describe('Runtime i18n', () => {
|
|||||||
// 3 consts for the 2 divs and 1 span + 1 const for `i18nStart` = 4 consts
|
// 3 consts for the 2 divs and 1 span + 1 const for `i18nStart` = 4 consts
|
||||||
const nbConsts = 4;
|
const nbConsts = 4;
|
||||||
const index = 1;
|
const index = 1;
|
||||||
const elementIndex = 2;
|
|
||||||
const elementIndex2 = 3;
|
|
||||||
const opCodes = getOpCodes(() => {
|
const opCodes = getOpCodes(() => {
|
||||||
ɵɵi18nStart(index, MSG_DIV);
|
ɵɵi18nStart(index, MSG_DIV);
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index);
|
||||||
|
|
||||||
expect(opCodes).toEqual({
|
expect(opCodes).toEqual({
|
||||||
vars: 5,
|
vars: 5,
|
||||||
create: [
|
create: debugMatch([
|
||||||
'Hello ',
|
'lView[4] = document.createTextNode("Hello ")',
|
||||||
nbConsts,
|
'(lView[1] as Element).appendChild(lView[4])',
|
||||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'(lView[1] as Element).appendChild(lView[2])',
|
||||||
elementIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
|
'lView[5] = document.createTextNode("world")',
|
||||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'(lView[2] as Element).appendChild(lView[5])',
|
||||||
'world',
|
'setPreviousOrParentTNode(tView.data[2] as TNode)',
|
||||||
nbConsts + 1,
|
'lView[6] = document.createTextNode(" and ")',
|
||||||
elementIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'(lView[1] as Element).appendChild(lView[6])',
|
||||||
elementIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
|
'(lView[1] as Element).appendChild(lView[3])',
|
||||||
' and ',
|
'lView[7] = document.createTextNode("universe")',
|
||||||
nbConsts + 2,
|
'(lView[3] as Element).appendChild(lView[7])',
|
||||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'setPreviousOrParentTNode(tView.data[3] as TNode)',
|
||||||
elementIndex2 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
|
'lView[8] = document.createTextNode("!")',
|
||||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'(lView[1] as Element).appendChild(lView[8])',
|
||||||
'universe',
|
]),
|
||||||
nbConsts + 3,
|
|
||||||
elementIndex2 << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
|
||||||
elementIndex2 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
|
|
||||||
'!',
|
|
||||||
nbConsts + 4,
|
|
||||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
|
||||||
],
|
|
||||||
update: [],
|
update: [],
|
||||||
icus: null
|
icus: null
|
||||||
});
|
});
|
||||||
@ -146,21 +128,18 @@ describe('Runtime i18n', () => {
|
|||||||
ɵɵi18nStart(index, MSG_DIV);
|
ɵɵi18nStart(index, MSG_DIV);
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index);
|
||||||
|
|
||||||
expect((opCodes as any).update.debug.operations).toEqual([
|
expect((opCodes as any).update.debug).toEqual([
|
||||||
{__raw_opCode: 8, checkBit: 1, type: 'Text', nodeIndex: 2, text: 'Hello <20>0<EFBFBD>!'}
|
'if (mask & 0b1) { (lView[2] as Text).textContent = `Hello ${lView[1]}!`; }'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(opCodes).toEqual({
|
expect(opCodes).toEqual({
|
||||||
vars: 1,
|
vars: 1,
|
||||||
create:
|
create: debugMatch([
|
||||||
['', nbConsts, index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild],
|
'lView[2] = document.createTextNode("")',
|
||||||
update: [
|
'(lView[1] as Element).appendChild(lView[2])',
|
||||||
0b1, // bindings mask
|
]),
|
||||||
4, // if no update, skip 4
|
update: debugMatch(
|
||||||
'Hello ',
|
['if (mask & 0b1) { (lView[2] as Text).textContent = `Hello ${lView[1]}!`; }']),
|
||||||
-1, // binding index
|
|
||||||
'!', (index + 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text
|
|
||||||
],
|
|
||||||
icus: null
|
icus: null
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -175,14 +154,12 @@ describe('Runtime i18n', () => {
|
|||||||
|
|
||||||
expect(opCodes).toEqual({
|
expect(opCodes).toEqual({
|
||||||
vars: 1,
|
vars: 1,
|
||||||
create:
|
create: debugMatch([
|
||||||
['', nbConsts, index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild],
|
'lView[2] = document.createTextNode("")', '(lView[1] as Element).appendChild(lView[2])'
|
||||||
update: [
|
]),
|
||||||
0b11, // bindings mask
|
update: debugMatch([
|
||||||
8, // if no update, skip 8
|
'if (mask & 0b11) { (lView[2] as Text).textContent = `Hello ${lView[1]} and ${lView[2]}, again ${lView[1]}!`; }'
|
||||||
'Hello ', -1, ' and ', -2, ', again ', -1, '!',
|
]),
|
||||||
(index + 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text
|
|
||||||
],
|
|
||||||
icus: null
|
icus: null
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -211,22 +188,14 @@ describe('Runtime i18n', () => {
|
|||||||
|
|
||||||
expect(opCodes).toEqual({
|
expect(opCodes).toEqual({
|
||||||
vars: 2,
|
vars: 2,
|
||||||
create: [
|
create: debugMatch([
|
||||||
'',
|
'lView[3] = document.createTextNode("")', '(lView[1] as Element).appendChild(lView[3])',
|
||||||
nbConsts,
|
'(lView[1] as Element).appendChild(lView[16381])',
|
||||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'lView[4] = document.createTextNode("!")', '(lView[1] as Element).appendChild(lView[4])'
|
||||||
~rootTemplate << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
|
]),
|
||||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
update: debugMatch([
|
||||||
'!',
|
'if (mask & 0b1) { (lView[3] as Text).textContent = `${lView[1]} is rendered as: `; }'
|
||||||
nbConsts + 1,
|
]),
|
||||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
|
||||||
],
|
|
||||||
update: [
|
|
||||||
0b1, // bindings mask
|
|
||||||
3, // if no update, skip 3
|
|
||||||
-1, // binding index
|
|
||||||
' is rendered as: ', firstTextNode << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text
|
|
||||||
],
|
|
||||||
icus: null
|
icus: null
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -243,19 +212,15 @@ describe('Runtime i18n', () => {
|
|||||||
|
|
||||||
expect(opCodes).toEqual({
|
expect(opCodes).toEqual({
|
||||||
vars: 2,
|
vars: 2,
|
||||||
create: [
|
create: debugMatch([
|
||||||
spanElement << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
|
'(lView[0] as Element).appendChild(lView[1])',
|
||||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'lView[3] = document.createTextNode("before")',
|
||||||
'before',
|
'(lView[1] as Element).appendChild(lView[3])',
|
||||||
nbConsts,
|
'(lView[1] as Element).appendChild(lView[16381])',
|
||||||
spanElement << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'lView[4] = document.createTextNode("after")',
|
||||||
~bElementSubTemplate << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
|
'(lView[1] as Element).appendChild(lView[4])',
|
||||||
spanElement << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'setPreviousOrParentTNode(tView.data[1] as TNode)'
|
||||||
'after',
|
]),
|
||||||
nbConsts + 1,
|
|
||||||
spanElement << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
|
||||||
spanElement << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
|
|
||||||
],
|
|
||||||
update: [],
|
update: [],
|
||||||
icus: null
|
icus: null
|
||||||
});
|
});
|
||||||
@ -272,14 +237,12 @@ describe('Runtime i18n', () => {
|
|||||||
|
|
||||||
expect(opCodes).toEqual({
|
expect(opCodes).toEqual({
|
||||||
vars: 1,
|
vars: 1,
|
||||||
create: [
|
create: debugMatch([
|
||||||
bElement << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Select,
|
'(lView[0] as Element).appendChild(lView[1])',
|
||||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'lView[2] = document.createTextNode("middle")',
|
||||||
'middle',
|
'(lView[1] as Element).appendChild(lView[2])',
|
||||||
nbConsts,
|
'setPreviousOrParentTNode(tView.data[1] as TNode)'
|
||||||
bElement << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
]),
|
||||||
bElement << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.ElementEnd,
|
|
||||||
],
|
|
||||||
update: [],
|
update: [],
|
||||||
icus: null
|
icus: null
|
||||||
});
|
});
|
||||||
@ -295,178 +258,75 @@ describe('Runtime i18n', () => {
|
|||||||
const index = 0;
|
const index = 0;
|
||||||
const opCodes = getOpCodes(() => {
|
const opCodes = getOpCodes(() => {
|
||||||
ɵɵi18nStart(index, MSG_DIV);
|
ɵɵi18nStart(index, MSG_DIV);
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index) as TI18n;
|
||||||
const tIcuIndex = 0;
|
|
||||||
const icuCommentNodeIndex = index + 1;
|
|
||||||
const firstTextNodeIndex = index + 2;
|
|
||||||
const bElementNodeIndex = index + 3;
|
|
||||||
const iElementNodeIndex = index + 3;
|
|
||||||
const spanElementNodeIndex = index + 3;
|
|
||||||
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({
|
expect(opCodes).toEqual({
|
||||||
vars: 5,
|
vars: 5,
|
||||||
create: [
|
update: debugMatch([
|
||||||
COMMENT_MARKER, 'ICU 1', icuCommentNodeIndex,
|
'if (mask & 0b1) { icuSwitchCase(lView[1] as Comment, 0, `${lView[1]}`); }',
|
||||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
|
'if (mask & 0b11) { icuUpdateCase(lView[1] as Comment, 0); }',
|
||||||
],
|
]),
|
||||||
update: [
|
create: debugMatch([
|
||||||
0b1, // mask for ICU main binding
|
'lView[1] = document.createComment("ICU 1")',
|
||||||
3, // skip 3 if not changed
|
'(lView[0] as Element).appendChild(lView[1])',
|
||||||
-1, // icu main binding
|
]),
|
||||||
icuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuSwitch, tIcuIndex,
|
icus: [<TIcu>{
|
||||||
0b11, // mask for all ICU bindings
|
|
||||||
2, // skip 2 if not changed
|
|
||||||
icuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuUpdate, tIcuIndex
|
|
||||||
],
|
|
||||||
icus: [{
|
|
||||||
type: 1,
|
type: 1,
|
||||||
vars: [4, 3, 3],
|
vars: [4, 3, 3],
|
||||||
childIcus: [[], [], []],
|
childIcus: [[], [], []],
|
||||||
cases: ['0', '1', 'other'],
|
cases: ['0', '1', 'other'],
|
||||||
create: [
|
create: [
|
||||||
[
|
debugMatch([
|
||||||
'no ',
|
'lView[2] = document.createTextNode("no ")',
|
||||||
firstTextNodeIndex,
|
'(lView[1] as Element).appendChild(lView[2])',
|
||||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'lView[3] = document.createElement("b")',
|
||||||
ELEMENT_MARKER,
|
'(lView[1] as Element).appendChild(lView[3])',
|
||||||
'b',
|
'(lView[3] as Element).setAttribute("title", "none")',
|
||||||
bElementNodeIndex,
|
'lView[4] = document.createTextNode("emails")',
|
||||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'(lView[3] as Element).appendChild(lView[4])',
|
||||||
bElementNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Attr,
|
'lView[5] = document.createTextNode("!")',
|
||||||
'title',
|
'(lView[1] as Element).appendChild(lView[5])'
|
||||||
'none',
|
]),
|
||||||
'emails',
|
debugMatch([
|
||||||
innerTextNode,
|
'lView[2] = document.createTextNode("one ")',
|
||||||
bElementNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'(lView[1] as Element).appendChild(lView[2])',
|
||||||
'!',
|
'lView[3] = document.createElement("i")',
|
||||||
lastTextNode,
|
'(lView[1] as Element).appendChild(lView[3])',
|
||||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'lView[4] = document.createTextNode("email")',
|
||||||
],
|
'(lView[3] as Element).appendChild(lView[4])'
|
||||||
[
|
]),
|
||||||
'one ', firstTextNodeIndex,
|
debugMatch([
|
||||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'lView[2] = document.createTextNode("")',
|
||||||
ELEMENT_MARKER, 'i', iElementNodeIndex,
|
'(lView[1] as Element).appendChild(lView[2])',
|
||||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'lView[3] = document.createElement("span")',
|
||||||
'email', innerTextNode,
|
'(lView[1] as Element).appendChild(lView[3])',
|
||||||
iElementNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
|
'lView[4] = document.createTextNode("emails")',
|
||||||
],
|
'(lView[3] as Element).appendChild(lView[4])'
|
||||||
[
|
])
|
||||||
'', firstTextNodeIndex,
|
|
||||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
|
||||||
ELEMENT_MARKER, 'span', spanElementNodeIndex,
|
|
||||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
|
||||||
'emails', innerTextNode,
|
|
||||||
spanElementNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
remove: [
|
remove: [
|
||||||
[
|
debugMatch([
|
||||||
firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
'(lView[0] as Element).remove(lView[2])',
|
||||||
innerTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
'(lView[0] as Element).remove(lView[4])',
|
||||||
bElementNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
'(lView[0] as Element).remove(lView[3])',
|
||||||
lastTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
'(lView[0] as Element).remove(lView[5])',
|
||||||
],
|
]),
|
||||||
[
|
debugMatch([
|
||||||
firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
'(lView[0] as Element).remove(lView[2])',
|
||||||
innerTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
'(lView[0] as Element).remove(lView[4])',
|
||||||
iElementNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
'(lView[0] as Element).remove(lView[3])',
|
||||||
],
|
]),
|
||||||
[
|
debugMatch([
|
||||||
firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
'(lView[0] as Element).remove(lView[2])',
|
||||||
innerTextNode << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
'(lView[0] as Element).remove(lView[4])',
|
||||||
spanElementNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
'(lView[0] as Element).remove(lView[3])',
|
||||||
]
|
])
|
||||||
],
|
],
|
||||||
update: [
|
update: [
|
||||||
[], [],
|
debugMatch([]), debugMatch([]), debugMatch([
|
||||||
[
|
'if (mask & 0b1) { (lView[2] as Text).textContent = `${lView[1]} `; }',
|
||||||
0b1, // mask for the first binding
|
'if (mask & 0b10) { (lView[3] as Element).setAttribute(\'title\', `${lView[2]}`); }'
|
||||||
3, // skip 3 if not changed
|
])
|
||||||
-1, // binding index
|
|
||||||
' ', // text string to concatenate to the binding value
|
|
||||||
firstTextNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text,
|
|
||||||
0b10, // mask for the title attribute binding
|
|
||||||
4, // skip 4 if not changed
|
|
||||||
-2, // binding index
|
|
||||||
bElementNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr,
|
|
||||||
'title', // attribute name
|
|
||||||
null // sanitize function
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
@ -496,19 +356,14 @@ describe('Runtime i18n', () => {
|
|||||||
|
|
||||||
expect(opCodes).toEqual({
|
expect(opCodes).toEqual({
|
||||||
vars: 6,
|
vars: 6,
|
||||||
create: [
|
create: debugMatch([
|
||||||
COMMENT_MARKER, 'ICU 1', icuCommentNodeIndex,
|
'lView[1] = document.createComment("ICU 1")',
|
||||||
index << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
|
'(lView[0] as Element).appendChild(lView[1])'
|
||||||
],
|
]),
|
||||||
update: [
|
update: debugMatch([
|
||||||
0b1, // mask for ICU main binding
|
'if (mask & 0b1) { icuSwitchCase(lView[1] as Comment, 1, `${lView[1]}`); }',
|
||||||
3, // skip 3 if not changed
|
'if (mask & 0b11) { icuUpdateCase(lView[1] as Comment, 1); }'
|
||||||
-1, // icu main binding
|
]),
|
||||||
icuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuSwitch, tIcuIndex,
|
|
||||||
0b11, // mask for all ICU bindings
|
|
||||||
2, // skip 2 if not changed
|
|
||||||
icuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.IcuUpdate, tIcuIndex
|
|
||||||
],
|
|
||||||
icus: [
|
icus: [
|
||||||
{
|
{
|
||||||
type: 0,
|
type: 0,
|
||||||
@ -516,28 +371,29 @@ describe('Runtime i18n', () => {
|
|||||||
childIcus: [[], [], []],
|
childIcus: [[], [], []],
|
||||||
cases: ['cat', 'dog', 'other'],
|
cases: ['cat', 'dog', 'other'],
|
||||||
create: [
|
create: [
|
||||||
[
|
debugMatch([
|
||||||
'cats', nestedTextNodeIndex,
|
'lView[5] = document.createTextNode("cats")',
|
||||||
nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT |
|
'(lView[3] as Element).appendChild(lView[5])'
|
||||||
I18nMutateOpCode.AppendChild
|
]),
|
||||||
],
|
debugMatch([
|
||||||
[
|
'lView[5] = document.createTextNode("dogs")',
|
||||||
'dogs', nestedTextNodeIndex,
|
'(lView[3] as Element).appendChild(lView[5])'
|
||||||
nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT |
|
]),
|
||||||
I18nMutateOpCode.AppendChild
|
debugMatch([
|
||||||
],
|
'lView[5] = document.createTextNode("animals")',
|
||||||
[
|
'(lView[3] as Element).appendChild(lView[5])'
|
||||||
'animals', nestedTextNodeIndex,
|
]),
|
||||||
nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT |
|
|
||||||
I18nMutateOpCode.AppendChild
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
remove: [
|
remove: [
|
||||||
[nestedTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove],
|
debugMatch(['(lView[0] as Element).remove(lView[5])']),
|
||||||
[nestedTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove],
|
debugMatch(['(lView[0] as Element).remove(lView[5])']),
|
||||||
[nestedTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove]
|
debugMatch(['(lView[0] as Element).remove(lView[5])'])
|
||||||
],
|
],
|
||||||
update: [[], [], []]
|
update: [
|
||||||
|
debugMatch([]),
|
||||||
|
debugMatch([]),
|
||||||
|
debugMatch([]),
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 1,
|
type: 1,
|
||||||
@ -545,48 +401,33 @@ describe('Runtime i18n', () => {
|
|||||||
childIcus: [[], [0]],
|
childIcus: [[], [0]],
|
||||||
cases: ['0', 'other'],
|
cases: ['0', 'other'],
|
||||||
create: [
|
create: [
|
||||||
[
|
debugMatch([
|
||||||
'zero', firstTextNodeIndex,
|
'lView[2] = document.createTextNode("zero")',
|
||||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
|
'(lView[1] as Element).appendChild(lView[2])'
|
||||||
],
|
]),
|
||||||
[
|
debugMatch([
|
||||||
'', firstTextNodeIndex,
|
'lView[2] = document.createTextNode("")',
|
||||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'(lView[1] as Element).appendChild(lView[2])',
|
||||||
COMMENT_MARKER, 'nested ICU 0', nestedIcuCommentNodeIndex,
|
'lView[3] = document.createComment("nested ICU 0")',
|
||||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild,
|
'(lView[1] as Element).appendChild(lView[3])',
|
||||||
'!', lastTextNodeIndex,
|
'lView[4] = document.createTextNode("!")',
|
||||||
icuCommentNodeIndex << I18nMutateOpCode.SHIFT_PARENT | I18nMutateOpCode.AppendChild
|
'(lView[1] as Element).appendChild(lView[4])'
|
||||||
]
|
]),
|
||||||
],
|
],
|
||||||
remove: [
|
remove: [
|
||||||
[firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove],
|
debugMatch(['(lView[0] as Element).remove(lView[2])']),
|
||||||
[
|
debugMatch([
|
||||||
firstTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
'(lView[0] as Element).remove(lView[2])', '(lView[0] as Element).remove(lView[4])',
|
||||||
lastTextNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
'removeNestedICU(0)', '(lView[0] as Element).remove(lView[3])'
|
||||||
0 << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.RemoveNestedIcu,
|
]),
|
||||||
nestedIcuCommentNodeIndex << I18nMutateOpCode.SHIFT_REF | I18nMutateOpCode.Remove,
|
|
||||||
]
|
|
||||||
],
|
],
|
||||||
update: [
|
update: [
|
||||||
[],
|
debugMatch([]),
|
||||||
[
|
debugMatch([
|
||||||
0b1, // mask for ICU main binding
|
'if (mask & 0b1) { (lView[2] as Text).textContent = `${lView[1]} `; }',
|
||||||
3, // skip 3 if not changed
|
'if (mask & 0b10) { icuSwitchCase(lView[3] as Comment, 0, `${lView[2]}`); }',
|
||||||
-1, // binding index
|
'if (mask & 0b10) { icuUpdateCase(lView[3] as Comment, 0); }'
|
||||||
' ', // text string to concatenate to the binding value
|
]),
|
||||||
firstTextNodeIndex << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Text,
|
|
||||||
0b10, // mask for inner ICU main binding
|
|
||||||
3, // skip 3 if not changed
|
|
||||||
-2, // inner ICU main binding
|
|
||||||
nestedIcuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF |
|
|
||||||
I18nUpdateOpCode.IcuSwitch,
|
|
||||||
nestedTIcuIndex,
|
|
||||||
0b10, // mask for all inner ICU bindings
|
|
||||||
2, // skip 2 if not changed
|
|
||||||
nestedIcuCommentNodeIndex << I18nUpdateOpCode.SHIFT_REF |
|
|
||||||
I18nUpdateOpCode.IcuUpdate,
|
|
||||||
nestedTIcuIndex
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -623,13 +464,9 @@ describe('Runtime i18n', () => {
|
|||||||
ɵɵi18nAttributes(index, MSG_div_attr);
|
ɵɵi18nAttributes(index, MSG_div_attr);
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index);
|
||||||
|
|
||||||
expect(opCodes).toEqual([
|
expect(opCodes).toEqual(debugMatch([
|
||||||
0b1, // bindings mask
|
'if (mask & 0b1) { (lView[0] as Element).setAttribute(\'title\', `Hello ${lView[1]}!`); }'
|
||||||
6, // if no update, skip 4
|
]));
|
||||||
'Hello ',
|
|
||||||
-1, // binding index
|
|
||||||
'!', (index - 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr, 'title', null
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('for multiple bindings', () => {
|
it('for multiple bindings', () => {
|
||||||
@ -641,12 +478,9 @@ describe('Runtime i18n', () => {
|
|||||||
ɵɵi18nAttributes(index, MSG_div_attr);
|
ɵɵi18nAttributes(index, MSG_div_attr);
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index);
|
||||||
|
|
||||||
expect(opCodes).toEqual([
|
expect(opCodes).toEqual(debugMatch([
|
||||||
0b11, // bindings mask
|
'if (mask & 0b11) { (lView[0] as Element).setAttribute(\'title\', `Hello ${lView[1]} and ${lView[2]}, again ${lView[1]}!`); }'
|
||||||
10, // size
|
]));
|
||||||
'Hello ', -1, ' and ', -2, ', again ', -1, '!',
|
|
||||||
(index - 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr, 'title', null
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('for multiple attributes', () => {
|
it('for multiple attributes', () => {
|
||||||
@ -658,18 +492,10 @@ describe('Runtime i18n', () => {
|
|||||||
ɵɵi18nAttributes(index, MSG_div_attr);
|
ɵɵi18nAttributes(index, MSG_div_attr);
|
||||||
}, null, nbConsts, index);
|
}, null, nbConsts, index);
|
||||||
|
|
||||||
expect(opCodes).toEqual([
|
expect(opCodes).toEqual(debugMatch([
|
||||||
0b1, // bindings mask
|
'if (mask & 0b1) { (lView[0] as Element).setAttribute(\'title\', `Hello ${lView[1]}!`); }',
|
||||||
6, // if no update, skip 4
|
'if (mask & 0b1) { (lView[0] as Element).setAttribute(\'aria-label\', `Hello ${lView[1]}!`); }'
|
||||||
'Hello ',
|
]));
|
||||||
-1, // binding index
|
|
||||||
'!', (index - 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr, 'title', null,
|
|
||||||
0b1, // bindings mask
|
|
||||||
6, // if no update, skip 4
|
|
||||||
'Hello ',
|
|
||||||
-1, // binding index
|
|
||||||
'!', (index - 1) << I18nUpdateOpCode.SHIFT_REF | I18nUpdateOpCode.Attr, 'aria-label', null
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
73
packages/core/test/render3/utils.ts
Normal file
73
packages/core/test/render3/utils.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google LLC All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Template string function that can be used to strip indentation from a given string literal. */
|
||||||
|
export function dedent(strings: TemplateStringsArray, ...values: any[]) {
|
||||||
|
let joinedString = '';
|
||||||
|
for (let i = 0; i < values.length; i++) {
|
||||||
|
joinedString += `${strings[i]}${values[i]}`;
|
||||||
|
}
|
||||||
|
joinedString += strings[strings.length - 1];
|
||||||
|
const lines = joinedString.split('\n');
|
||||||
|
while (isBlank(lines[0])) {
|
||||||
|
lines.shift();
|
||||||
|
}
|
||||||
|
while (isBlank(lines[lines.length - 1])) {
|
||||||
|
lines.pop();
|
||||||
|
}
|
||||||
|
let minWhitespacePrefix = lines.reduce(
|
||||||
|
(min, line) => Math.min(min, numOfWhiteSpaceLeadingChars(line)), Number.MAX_SAFE_INTEGER);
|
||||||
|
return lines.map((line) => line.substring(minWhitespacePrefix)).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests to see if the line is blank.
|
||||||
|
*
|
||||||
|
* A blank line is such which contains only whitespace.
|
||||||
|
* @param text string to test for blank-ness.
|
||||||
|
*/
|
||||||
|
function isBlank(text: string): boolean {
|
||||||
|
return /^\s*$/.test(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns number of whitespace leading characters.
|
||||||
|
*
|
||||||
|
* @param text
|
||||||
|
*/
|
||||||
|
function numOfWhiteSpaceLeadingChars(text: string): number {
|
||||||
|
return text.match(/^\s*/)![0].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Jasmine AsymmetricMatcher which can be used to assert `.debug` properties.
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* expect(obj).toEqual({
|
||||||
|
* create: debugMatch('someValue')
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* In the above example it will assert that `obj.create.debug === 'someValue'`.
|
||||||
|
*
|
||||||
|
* @param expected Expected value.
|
||||||
|
*/
|
||||||
|
export function debugMatch<T>(expected: T): any {
|
||||||
|
const matcher = function() {};
|
||||||
|
let actual: any = null;
|
||||||
|
|
||||||
|
matcher.asymmetricMatch = function(objectWithDebug: any) {
|
||||||
|
return jasmine.matchersUtil.equals(actual = objectWithDebug.debug, expected);
|
||||||
|
};
|
||||||
|
matcher.jasmineToString = function() {
|
||||||
|
return `<${JSON.stringify(actual)} != ${JSON.stringify(expected)}>`;
|
||||||
|
};
|
||||||
|
return matcher;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user