Files
Kristiyan Kostadinov 2a1723c945 test(compiler): fix broken integrity check (#51751)
The `verifyPlaceholdersIntegrity` check in the compliance tests was basically a noop, because it was returning false inside a `forEach` callback. Fixing it revealed that it had fallen out of date, because one of the regexes it uses was incorrect. The problem is that it assumed the placeholder keys would always be string literals, however it's possible that they're identifiers. These changes resolve the issue by not looking at the keys at all since we don't do anything with them.

PR Close #51751
2023-09-13 10:48:32 -07:00

90 lines
2.7 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
*/
const EXTRACT_GENERATED_TRANSLATIONS_REGEXP =
/const\s*(.*?)\s*=\s*goog\.getMsg\("(.*?)",?\s*(.*?)\)/g;
/**
* Verify that placeholders in translation strings match placeholders in the object defined in the
* `goog.getMsg()` function arguments.
*/
export function verifyPlaceholdersIntegrity(output: string): boolean {
const translations = extractTranslations(output);
for (const [msg, args] of translations) {
const bodyPhs = extractPlaceholdersFromMsg(msg);
const argsPhs = extractPlaceholdersFromArgs(args);
if (bodyPhs.size !== argsPhs.size || diff(bodyPhs, argsPhs).size) {
return false;
}
}
return true;
}
/**
* Verify that all the variables initialized with `goog.getMsg()` calls have
* unique names.
*/
export function verifyUniqueConsts(output: string): boolean {
extract(
output, EXTRACT_GENERATED_TRANSLATIONS_REGEXP,
(current: string[], state: Set<string>): string => {
const key = current[1];
if (state.has(key)) {
throw new Error(`Duplicate const ${key} found in generated output!`);
}
return key;
});
return true;
}
/**
* Extract pairs of `[msg, placeholders]`, in calls to `goog.getMsg()`, from the `source`.
*
* @param source The source code to parse.
*/
function extractTranslations(source: string): Set<string[]> {
return extract(
source, EXTRACT_GENERATED_TRANSLATIONS_REGEXP,
([, , msg, placeholders]) => [msg, placeholders]);
}
/**
* Extract placeholder names (of the form `{$PLACEHOLDER}`) from the `msg`.
*
* @param msg The text of the message to parse.
*/
function extractPlaceholdersFromMsg(msg: string): Set<string> {
const regex = /{\$(.*?)}/g;
return extract(msg, regex, ([, placeholders]) => placeholders);
}
/**
* Extract the placeholder names (of the form `"PLACEHOLDER": "XXX"`) from the body of the argument
* provided as `args`.
*
* @param args The body of an object literal containing placeholder info.
*/
function extractPlaceholdersFromArgs(args: string): Set<string> {
const regex = /\s+"(.+?)":\s/g;
return extract(args, regex, ([, placeholders]) => placeholders);
}
function extract<T>(
from: string, regex: RegExp, transformFn: (match: string[], state: Set<T>) => T): Set<T> {
const result = new Set<T>();
let item: RegExpExecArray|null;
while ((item = regex.exec(from)) !== null) {
result.add(transformFn(item, result));
}
return result;
}
function diff(a: Set<string>, b: Set<string>): Set<string> {
return new Set(Array.from(a).filter(x => !b.has(x)));
}