Skip to content

Commit

Permalink
[Tools] Validate values in nested ICU messages (#25378)
Browse files Browse the repository at this point in the history
  • Loading branch information
LeanidShutau authored Nov 13, 2018
1 parent ff8675d commit 56dc78d
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 8 deletions.
5 changes: 5 additions & 0 deletions src/dev/i18n/__snapshots__/utils.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,8 @@ exports[`i18n utils should throw if some key is missing in "values" 1`] = `
"some properties are missing in \\"values\\" object (\\"namespace.message.id\\"):
[password]."
`;
exports[`i18n utils should throw on wrong nested ICU message 1`] = `
"\\"values\\" object contains unused properties (\\"namespace.message.id\\"):
[third]."
`;
42 changes: 34 additions & 8 deletions src/dev/i18n/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import { createFailError } from '../run';
const ESCAPE_LINE_BREAK_REGEX = /(?<!\\)\\\n/g;
const HTML_LINE_BREAK_REGEX = /[\s]*\n[\s]*/g;

const ARGUMENT_ELEMENT_TYPE = 'argumentElement';

export const readFileAsync = promisify(fs.readFile);
export const writeFileAsync = promisify(fs.writeFile);
export const globAsync = promisify(glob);
Expand Down Expand Up @@ -126,6 +128,37 @@ export function createParserErrorMessage(content, error) {
return `${error.message}:\n${context}`;
}

/**
* Recursively extracts all references from ICU message ast.
*
* Example: `'Removed tag {tag} from {assignmentsLength, plural, one {beat {beatName}} other {# beats}}.'`
*
* @param {any} node
* @param {Set<string>} keys
*/
function extractValueReferencesFromIcuAst(node, keys = new Set()) {
if (Array.isArray(node.elements)) {
for (const element of node.elements) {
if (element.type !== ARGUMENT_ELEMENT_TYPE) {
continue;
}

keys.add(element.id);

// format contains all specific parameters for complex argumentElements
if (element.format && Array.isArray(element.format.options)) {
for (const option of element.format.options) {
extractValueReferencesFromIcuAst(option, keys);
}
}
}
} else if (node.value) {
extractValueReferencesFromIcuAst(node.value, keys);
}

return [...keys];
}

/**
* Checks whether values from "values" and "defaultMessage" correspond to each other.
*
Expand Down Expand Up @@ -162,19 +195,12 @@ export function checkValuesProperty(valuesKeys, defaultMessage, messageId) {
throw error;
}

const ARGUMENT_ELEMENT_TYPE = 'argumentElement';

// skip validation if intl-messageformat-parser didn't return an AST with nonempty elements array
if (!defaultMessageAst || !defaultMessageAst.elements || !defaultMessageAst.elements.length) {
return;
}

const defaultMessageValueReferences = defaultMessageAst.elements.reduce((keys, element) => {
if (element.type === ARGUMENT_ELEMENT_TYPE) {
keys.push(element.id);
}
return keys;
}, []);
const defaultMessageValueReferences = extractValueReferencesFromIcuAst(defaultMessageAst);

const missingValuesKeys = difference(defaultMessageValueReferences, valuesKeys);
if (missingValuesKeys.length) {
Expand Down
20 changes: 20 additions & 0 deletions src/dev/i18n/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,26 @@ describe('i18n utils', () => {
).toThrowErrorMatchingSnapshot();
});

test('should parse nested ICU message', () => {
const valuesKeys = ['first', 'second', 'third'];
const defaultMessage = 'Test message {first, plural, one {{second}} other {{third}}}';
const messageId = 'namespace.message.id';

expect(() =>
checkValuesProperty(valuesKeys, defaultMessage, messageId)
).not.toThrow();
});

test(`should throw on wrong nested ICU message`, () => {
const valuesKeys = ['first', 'second', 'third'];
const defaultMessage = 'Test message {first, plural, one {{second}} other {other}}';
const messageId = 'namespace.message.id';

expect(() =>
checkValuesProperty(valuesKeys, defaultMessage, messageId)
).toThrowErrorMatchingSnapshot();
});

test(`should parse string concatenation`, () => {
const source = `
i18n('namespace.id', {
Expand Down

0 comments on commit 56dc78d

Please sign in to comment.