JoostK 6db342a87a test(compiler-cli): reset i18n message index in compliance test macro (#40529)
The compliance test runner has various macros that process the
expectation files before actually checking their contents. Among those
macros are i18n helpers, which uses a global message counter to be able
to uniquely identify ICU variables.

Because of the global nature of this message index, it was susceptible
to ordering issues which could result in flaky tests, although it failed
very infrequently.

This commit resets the global message counter before applying the macros.
As a result of this change an expectation file had to be updated; this
is actually a bug fix as said test used to fail if run in isolation (if
`focusTest: true` was set for that particular testcase).

PR Close #40529
2021-01-25 10:55:42 -08:00

137 lines
4.3 KiB
TypeScript

/**
* @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
*/
/**
* Unique message id index that is needed to avoid different i18n vars with the same name to appear
* in the i18n block while generating an output string (used to verify compiler-generated code).
*/
let msgIndex = 0;
export function resetMessageIndex(): void {
msgIndex = 0;
}
/**
* Generate a string that represents expected i18n block content for a simple message.
*/
export function i18nMsg(message: string, placeholders: Placeholder[], meta: Meta): string {
const varName = `$I18N_${msgIndex++}$`;
const closurePlaceholders = i18nPlaceholdersToString(placeholders);
const locMessageWithPlaceholders = i18nMsgInsertLocalizePlaceholders(message, placeholders);
return `
let ${varName};
if (typeof ngI18nClosureMode !== "undefined" && ngI18nClosureMode) {
${i18nMsgClosureMeta(meta)}
const $MSG_EXTERNAL_${msgIndex}$ = goog.getMsg("${message}"${closurePlaceholders});
${varName} = $MSG_EXTERNAL_${msgIndex}$;
}
else {
${varName} = $localize \`${i18nMsgLocalizeMeta(meta)}${locMessageWithPlaceholders}\`;
}`;
}
/**
* Generate a string that represents expected i18n block content for a message that requires
* post-processing.
*/
export function i18nMsgWithPostprocess(
message: string, placeholders: Placeholder[], meta: Meta,
postprocessPlaceholders: Placeholder[]): string {
const varName = `$I18N_${msgIndex}$`;
const ppPlaceholders = i18nPlaceholdersToString(postprocessPlaceholders);
return String.raw`
${i18nMsg(message, placeholders, meta)}
${varName} = $r3$.ɵɵi18nPostprocess($${varName}$${ppPlaceholders});
`;
}
/**
* Generates a string that represents expected i18n block content for an ICU.
*/
export function i18nIcuMsg(message: string, placeholders: Placeholder[]): string {
return i18nMsgWithPostprocess(message, [], {}, placeholders);
}
/**
* Describes placeholder type used in tests.
*
* Note: the type is an array (not an object), since it's important to preserve the order of
* placeholders (so that we can compare it with generated output).
*/
export type Placeholder = [string, string];
/**
* Describes message metadata object.
*/
interface Meta {
desc?: string;
meaning?: string;
id?: string;
}
/**
* Convert a set of placeholders to a string (as it's expected from compiler).
*/
function i18nPlaceholdersToString(placeholders: Placeholder[]): string {
if (placeholders.length === 0) return '';
const result = placeholders.map(([key, value]) => `"${key}": ${quotedValue(value)}`);
return `, { ${result.join(',')} }`;
}
/**
* Transform a message in a Closure format to a $localize version.
*/
function i18nMsgInsertLocalizePlaceholders(message: string, placeholders: Placeholder[]): string {
if (placeholders.length > 0) {
message = message.replace(/{\$(.*?)}/g, function(_, name) {
const value = placeholders.find(([k, _]) => k === name)![1];
// e.g. startDivTag -> START_DIV_TAG
const key = name.replace(/[A-Z]/g, (ch: string) => '_' + ch).toUpperCase();
return '$' +
`{${quotedValue(value)}}:${key}:`;
});
}
return message;
}
/**
* Generate a string that represents expected Closure metadata output comment.
*/
function i18nMsgClosureMeta(meta?: Meta): string {
if (!meta || !(meta.desc || meta.meaning)) return '';
return `
/**
${meta.desc ? '* @desc ' + meta.desc : ''}
${meta.meaning ? '* @meaning ' + meta.meaning : ''}
*/
`;
}
/**
* Generate a string that represents expected $localize metadata output.
*/
function i18nMsgLocalizeMeta(meta?: Meta): string {
if (!meta) return '';
let localizeMeta = '';
if (meta.meaning) localizeMeta += `${meta.meaning}|`;
if (meta.desc) localizeMeta += meta.desc;
if (meta.id) localizeMeta += `@@${meta.id}`;
return localizeMeta !== '' ? `:${localizeMeta}:` : '';
}
/**
* Wrap a string into quotes if needed.
*
* Note: if `value` starts with `$` it is a special case in tests when ICU reference is used as a
* placeholder value. Such special cases should not be wrapped in quotes.
*/
function quotedValue(value: string): string {
return value.startsWith('$') ? value : `"${value}"`;
}