@@ -39,6 +39,8 @@ import { createFailError } from '../run';
3939const ESCAPE_LINE_BREAK_REGEX = / (?< ! \\ ) \\ \n / g;
4040const HTML_LINE_BREAK_REGEX = / [ \s ] * \n [ \s ] * / g;
4141
42+ const ARGUMENT_ELEMENT_TYPE = 'argumentElement' ;
43+
4244export const readFileAsync = promisify ( fs . readFile ) ;
4345export const writeFileAsync = promisify ( fs . writeFile ) ;
4446export const globAsync = promisify ( glob ) ;
@@ -126,6 +128,37 @@ export function createParserErrorMessage(content, error) {
126128 return `${ error . message } :\n${ context } ` ;
127129}
128130
131+ /**
132+ * Recursively extracts all references from ICU message ast.
133+ *
134+ * Example: `'Removed tag {tag} from {assignmentsLength, plural, one {beat {beatName}} other {# beats} }.'`
135+ *
136+ * @param {any } node
137+ * @param {Set<string> } keys
138+ */
139+ function extractValueReferencesFromIcuAst ( node , keys = new Set ( ) ) {
140+ if ( Array . isArray ( node . elements ) ) {
141+ for ( const element of node . elements ) {
142+ if ( element . type !== ARGUMENT_ELEMENT_TYPE ) {
143+ continue ;
144+ }
145+
146+ keys . add ( element . id ) ;
147+
148+ // format contains all specific parameters for complex argumentElements
149+ if ( element . format && Array . isArray ( element . format . options ) ) {
150+ for ( const option of element . format . options ) {
151+ extractValueReferencesFromIcuAst ( option , keys ) ;
152+ }
153+ }
154+ }
155+ } else if ( node . value ) {
156+ extractValueReferencesFromIcuAst ( node . value , keys ) ;
157+ }
158+
159+ return [ ...keys ] ;
160+ }
161+
129162/**
130163 * Checks whether values from "values" and "defaultMessage" correspond to each other.
131164 *
@@ -162,19 +195,12 @@ export function checkValuesProperty(valuesKeys, defaultMessage, messageId) {
162195 throw error ;
163196 }
164197
165- const ARGUMENT_ELEMENT_TYPE = 'argumentElement' ;
166-
167198 // skip validation if intl-messageformat-parser didn't return an AST with nonempty elements array
168199 if ( ! defaultMessageAst || ! defaultMessageAst . elements || ! defaultMessageAst . elements . length ) {
169200 return ;
170201 }
171202
172- const defaultMessageValueReferences = defaultMessageAst . elements . reduce ( ( keys , element ) => {
173- if ( element . type === ARGUMENT_ELEMENT_TYPE ) {
174- keys . push ( element . id ) ;
175- }
176- return keys ;
177- } , [ ] ) ;
203+ const defaultMessageValueReferences = extractValueReferencesFromIcuAst ( defaultMessageAst ) ;
178204
179205 const missingValuesKeys = difference ( defaultMessageValueReferences , valuesKeys ) ;
180206 if ( missingValuesKeys . length ) {
0 commit comments