This commit provides the machinery for the new file-based compliance test approach for i18n tests, and migrates the i18n tests to this new format. PR Close #39661
		
			
				
	
	
		
			90 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			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);
 | 
						|
  translations.forEach(([msg, args]) => {
 | 
						|
    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;
 | 
						|
  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)));
 | 
						|
}
 |