i18next-cli 1.34.0 → 1.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/cli.js +271 -1
  3. package/dist/cjs/config.js +211 -1
  4. package/dist/cjs/extractor/core/ast-visitors.js +372 -1
  5. package/dist/cjs/extractor/core/extractor.js +245 -1
  6. package/dist/cjs/extractor/core/key-finder.js +132 -1
  7. package/dist/cjs/extractor/core/translation-manager.js +745 -1
  8. package/dist/cjs/extractor/parsers/ast-utils.js +85 -1
  9. package/dist/cjs/extractor/parsers/call-expression-handler.js +942 -1
  10. package/dist/cjs/extractor/parsers/comment-parser.js +375 -1
  11. package/dist/cjs/extractor/parsers/expression-resolver.js +362 -1
  12. package/dist/cjs/extractor/parsers/jsx-handler.js +492 -1
  13. package/dist/cjs/extractor/parsers/jsx-parser.js +355 -1
  14. package/dist/cjs/extractor/parsers/scope-manager.js +408 -1
  15. package/dist/cjs/extractor/plugin-manager.js +106 -1
  16. package/dist/cjs/heuristic-config.js +99 -1
  17. package/dist/cjs/index.js +28 -1
  18. package/dist/cjs/init.js +174 -1
  19. package/dist/cjs/linter.js +431 -1
  20. package/dist/cjs/locize.js +269 -1
  21. package/dist/cjs/migrator.js +196 -1
  22. package/dist/cjs/rename-key.js +354 -1
  23. package/dist/cjs/status.js +336 -1
  24. package/dist/cjs/syncer.js +120 -1
  25. package/dist/cjs/types-generator.js +165 -1
  26. package/dist/cjs/utils/default-value.js +43 -1
  27. package/dist/cjs/utils/file-utils.js +136 -1
  28. package/dist/cjs/utils/funnel-msg-tracker.js +75 -1
  29. package/dist/cjs/utils/logger.js +36 -1
  30. package/dist/cjs/utils/nested-object.js +124 -1
  31. package/dist/cjs/utils/validation.js +71 -1
  32. package/dist/esm/cli.js +269 -1
  33. package/dist/esm/config.js +206 -1
  34. package/dist/esm/extractor/core/ast-visitors.js +370 -1
  35. package/dist/esm/extractor/core/extractor.js +241 -1
  36. package/dist/esm/extractor/core/key-finder.js +130 -1
  37. package/dist/esm/extractor/core/translation-manager.js +743 -1
  38. package/dist/esm/extractor/parsers/ast-utils.js +80 -1
  39. package/dist/esm/extractor/parsers/call-expression-handler.js +940 -1
  40. package/dist/esm/extractor/parsers/comment-parser.js +373 -1
  41. package/dist/esm/extractor/parsers/expression-resolver.js +360 -1
  42. package/dist/esm/extractor/parsers/jsx-handler.js +490 -1
  43. package/dist/esm/extractor/parsers/jsx-parser.js +334 -1
  44. package/dist/esm/extractor/parsers/scope-manager.js +406 -1
  45. package/dist/esm/extractor/plugin-manager.js +103 -1
  46. package/dist/esm/heuristic-config.js +97 -1
  47. package/dist/esm/index.js +11 -1
  48. package/dist/esm/init.js +172 -1
  49. package/dist/esm/linter.js +425 -1
  50. package/dist/esm/locize.js +265 -1
  51. package/dist/esm/migrator.js +194 -1
  52. package/dist/esm/rename-key.js +352 -1
  53. package/dist/esm/status.js +334 -1
  54. package/dist/esm/syncer.js +118 -1
  55. package/dist/esm/types-generator.js +163 -1
  56. package/dist/esm/utils/default-value.js +41 -1
  57. package/dist/esm/utils/file-utils.js +131 -1
  58. package/dist/esm/utils/funnel-msg-tracker.js +72 -1
  59. package/dist/esm/utils/logger.js +34 -1
  60. package/dist/esm/utils/nested-object.js +120 -1
  61. package/dist/esm/utils/validation.js +68 -1
  62. package/package.json +2 -2
  63. package/types/extractor/core/ast-visitors.d.ts.map +1 -1
  64. package/types/extractor/parsers/call-expression-handler.d.ts +3 -2
  65. package/types/extractor/parsers/call-expression-handler.d.ts.map +1 -1
  66. package/types/locize.d.ts.map +1 -1
@@ -1 +1,362 @@
1
- "use strict";exports.ExpressionResolver=class{hooks;variableTable=new Map;sharedEnumTable=new Map;constructor(e){this.hooks=e}resetFileSymbols(){this.variableTable.clear()}captureVariableDeclarator(e){try{if(!e||!e.id||!e.init)return;if("Identifier"!==e.id.type)return;const t=e.id.value,r=e.init;if("ObjectExpression"===r.type&&Array.isArray(r.properties)){const e={};for(const t of r.properties){if(!t||"KeyValueProperty"!==t.type)continue;const r=t.key,s="Identifier"===r?.type||"StringLiteral"===r?.type?r.value:void 0;if(!s)continue;const i=t.value,o=this.resolvePossibleStringValuesFromExpression(i);1===o.length&&(e[s]=o[0])}if(Object.keys(e).length>0)return void this.variableTable.set(t,e)}const s=this.resolvePossibleStringValuesFromExpression(r);s.length>0&&this.variableTable.set(t,s)}catch{}}captureEnumDeclaration(e){try{if(!e||!e.id||!Array.isArray(e.members))return;const t="Identifier"===e.id.type?e.id.value:void 0;if(!t)return;const r={};for(const t of e.members){if(!t||!t.id)continue;const e=t.id,s="Identifier"===e.type||"StringLiteral"===e.type?e.value:void 0;if(!s)continue;const i=t.init??t.initializer;i&&"StringLiteral"===i.type&&(r[s]=i.value)}Object.keys(r).length>0&&this.sharedEnumTable.set(t,r)}catch{}}resolvePossibleContextStringValues(e){return[...this.hooks.resolvePossibleContextStringValues?.(e)??[],...this.resolvePossibleStringValuesFromExpression(e)]}resolvePossibleKeyStringValues(e){return[...this.hooks.resolvePossibleKeyStringValues?.(e)??[],...this.resolvePossibleStringValuesFromExpression(e)]}resolvePossibleStringValuesFromExpression(e,t=!1){if("ArrowFunctionExpression"===e.type)try{let t=e.body;if("BlockStatement"===t.type){const e=t.stmts.find(e=>"ReturnStatement"===e.type);if("ReturnStatement"!==e?.type||!e.argument)return[];t=e.argument}let r=t;const s=[];for(;r&&"MemberExpression"===r.type;){const e=r.property;if("Identifier"===e.type)s.unshift(e.value);else{if("Computed"!==e.type||!e.expression||"StringLiteral"!==e.expression.type)return[];s.unshift(e.expression.value)}r=r.object}if(s.length>0)return[s.join(".")]}catch{return[]}if("StringLiteral"===e.type)return e.value||t?[e.value]:[];if("ConditionalExpression"===e.type){return[...this.resolvePossibleStringValuesFromExpression(e.consequent,t),...this.resolvePossibleStringValuesFromExpression(e.alternate,t)]}if("Identifier"===e.type&&"undefined"===e.value)return[];if("TemplateLiteral"===e.type)return this.resolvePossibleStringValuesFromTemplateString(e);if("MemberExpression"===e.type)try{const t=e.object,r=e.property;if("Identifier"===t.type){const e=this.variableTable.get(t.value),s=this.sharedEnumTable.get(t.value),i=e??s;if(i&&"string"!=typeof i&&!Array.isArray(i)){let e;if("Identifier"===r.type?e=r.value:"Computed"===r.type&&"StringLiteral"===r.expression?.type&&(e=r.expression.value),e&&void 0!==i[e])return[i[e]]}}}catch{}if(e.left&&e.right)try{const r=e,s=r.left,i=r.right;if("BinExpr"===r.type&&"+"===r.op||"BinaryExpression"===r.type&&"+"===r.operator||"+"===r.operator||"+"===r.op){const e=this.resolvePossibleStringValuesFromExpression(s,t),r=this.resolvePossibleStringValuesFromExpression(i,t);if(e.length>0&&r.length>0){const t=[];for(const s of e)for(const e of r)t.push(`${s}${e}`);return t}}if("BinaryExpression"===r.type&&"??"===r.operator||"LogicalExpression"===r.type&&"??"===r.operator||"??"===r.operator||"??"===r.op){const e=this.resolvePossibleStringValuesFromExpression(s,t),r=this.resolvePossibleStringValuesFromExpression(i,t);if(e.length>0||r.length>0)return Array.from(new Set([...e,...r]))}}catch{}if("NumericLiteral"===e.type||"BooleanLiteral"===e.type)return[`${e.value}`];if("TsSatisfiesExpression"===e.type||"TsAsExpression"===e.type){const r=e.typeAnnotation;return this.resolvePossibleStringValuesFromType(r,t)}if("Identifier"===e.type){const t=this.variableTable.get(e.value);return t&&Array.isArray(t)?t:[]}return[]}resolvePossibleStringValuesFromType(e,t=!1){if("TsUnionType"===e.type)return e.types.flatMap(e=>this.resolvePossibleStringValuesFromType(e,t));if("TsLiteralType"===e.type){if("StringLiteral"===e.literal.type)return e.literal.value||t?[e.literal.value]:[];if("TemplateLiteral"===e.literal.type)return this.resolvePossibleStringValuesFromTemplateLiteralType(e.literal);if("NumericLiteral"===e.literal.type||"BooleanLiteral"===e.literal.type)return[`${e.literal.value}`]}return[]}resolvePossibleStringValuesFromTemplateString(e){if(1===e.quasis.length&&0===e.expressions.length)return[e.quasis[0].cooked||""];const[t,...r]=e.quasis;return e.expressions.reduce((e,t,s)=>e.flatMap(e=>{const i=r[s]?.cooked??"";return this.resolvePossibleStringValuesFromExpression(t,!0).map(t=>`${e}${t}${i}`)}),[t.cooked??""])}resolvePossibleStringValuesFromTemplateLiteralType(e){if(1===e.quasis.length&&0===e.types.length)return[e.quasis[0].cooked||""];const[t,...r]=e.quasis;return e.types.reduce((e,t,s)=>e.flatMap(e=>{const i=r[s]?.cooked??"";return this.resolvePossibleStringValuesFromType(t,!0).map(t=>`${e}${t}${i}`)}),[t.cooked??""])}};
1
+ 'use strict';
2
+
3
+ class ExpressionResolver {
4
+ hooks;
5
+ // Per-file symbol table for statically analyzable variables.
6
+ // Maps variableName -> either:
7
+ // - string[] (possible string values)
8
+ // - Record<string, string> (object of static string properties)
9
+ variableTable = new Map();
10
+ // Shared (cross-file) table for enums / exported object maps that should persist
11
+ sharedEnumTable = new Map();
12
+ constructor(hooks) {
13
+ this.hooks = hooks;
14
+ }
15
+ /**
16
+ * Clear per-file captured variables. Enums / shared maps are kept.
17
+ */
18
+ resetFileSymbols() {
19
+ this.variableTable.clear();
20
+ }
21
+ /**
22
+ * Capture a VariableDeclarator node to record simple statically analyzable
23
+ * initializers (string literals, object expressions of string literals,
24
+ * template literals and simple concatenations).
25
+ *
26
+ * This is called during AST traversal before deeper walking so later
27
+ * identifier/member-expression usage can be resolved.
28
+ *
29
+ * @param node - VariableDeclarator-like node (has .id and .init)
30
+ */
31
+ captureVariableDeclarator(node) {
32
+ try {
33
+ if (!node || !node.id || !node.init)
34
+ return;
35
+ // only handle simple identifier bindings like `const x = ...`
36
+ if (node.id.type !== 'Identifier')
37
+ return;
38
+ const name = node.id.value;
39
+ const init = node.init;
40
+ // ObjectExpression -> map of string props
41
+ if (init.type === 'ObjectExpression' && Array.isArray(init.properties)) {
42
+ const map = {};
43
+ for (const p of init.properties) {
44
+ if (!p || p.type !== 'KeyValueProperty')
45
+ continue;
46
+ const keyNode = p.key;
47
+ const keyName = keyNode?.type === 'Identifier' ? keyNode.value : keyNode?.type === 'StringLiteral' ? keyNode.value : undefined;
48
+ if (!keyName)
49
+ continue;
50
+ const valExpr = p.value;
51
+ const vals = this.resolvePossibleStringValuesFromExpression(valExpr);
52
+ // Only capture properties that we can statically resolve to a single string.
53
+ if (vals.length === 1) {
54
+ map[keyName] = vals[0];
55
+ }
56
+ }
57
+ // If at least one property was resolvable, record the partial map.
58
+ if (Object.keys(map).length > 0) {
59
+ this.variableTable.set(name, map);
60
+ return;
61
+ }
62
+ }
63
+ // For other initializers, try to resolve to one-or-more strings
64
+ const vals = this.resolvePossibleStringValuesFromExpression(init);
65
+ if (vals.length > 0) {
66
+ this.variableTable.set(name, vals);
67
+ }
68
+ }
69
+ catch {
70
+ // be silent - conservative only
71
+ }
72
+ }
73
+ /**
74
+ * Capture a TypeScript enum declaration so members can be resolved later.
75
+ * Accepts SWC node shapes like `TsEnumDeclaration` / `TSEnumDeclaration`.
76
+ *
77
+ * Enums are stored in the shared table so they are available across files.
78
+ */
79
+ captureEnumDeclaration(node) {
80
+ try {
81
+ if (!node || !node.id || !Array.isArray(node.members))
82
+ return;
83
+ const name = node.id.type === 'Identifier' ? node.id.value : undefined;
84
+ if (!name)
85
+ return;
86
+ const map = {};
87
+ for (const m of node.members) {
88
+ if (!m || !m.id)
89
+ continue;
90
+ const keyNode = m.id;
91
+ const memberName = keyNode.type === 'Identifier' ? keyNode.value : keyNode.type === 'StringLiteral' ? keyNode.value : undefined;
92
+ if (!memberName)
93
+ continue;
94
+ const init = m.init ?? m.initializer;
95
+ if (init && init.type === 'StringLiteral') {
96
+ map[memberName] = init.value;
97
+ }
98
+ }
99
+ if (Object.keys(map).length > 0) {
100
+ this.sharedEnumTable.set(name, map);
101
+ }
102
+ }
103
+ catch {
104
+ // noop
105
+ }
106
+ }
107
+ /**
108
+ * Resolves an expression to one or more possible context string values that can be
109
+ * determined statically from the AST. This is a wrapper around the plugin hook
110
+ * `extractContextFromExpression` and {@link resolvePossibleStringValuesFromExpression}.
111
+ *
112
+ * @param expression - The SWC AST expression node to resolve
113
+ * @returns An array of possible context string values that the expression may produce.
114
+ */
115
+ resolvePossibleContextStringValues(expression) {
116
+ const strings = this.hooks.resolvePossibleContextStringValues?.(expression) ?? [];
117
+ return [...strings, ...this.resolvePossibleStringValuesFromExpression(expression)];
118
+ }
119
+ /**
120
+ * Resolves an expression to one or more possible key string values that can be
121
+ * determined statically from the AST. This is a wrapper around the plugin hook
122
+ * `extractKeysFromExpression` and {@link resolvePossibleStringValuesFromExpression}.
123
+ *
124
+ * @param expression - The SWC AST expression node to resolve
125
+ * @returns An array of possible key string values that the expression may produce.
126
+ */
127
+ resolvePossibleKeyStringValues(expression) {
128
+ const strings = this.hooks.resolvePossibleKeyStringValues?.(expression) ?? [];
129
+ return [...strings, ...this.resolvePossibleStringValuesFromExpression(expression)];
130
+ }
131
+ /**
132
+ * Resolves an expression to one or more possible string values that can be
133
+ * determined statically from the AST.
134
+ *
135
+ * Supports:
136
+ * - StringLiteral -> single value (filtered to exclude empty strings for context)
137
+ * - NumericLiteral -> single value
138
+ * - BooleanLiteral -> single value
139
+ * - ConditionalExpression (ternary) -> union of consequent and alternate resolved values
140
+ * - TemplateLiteral -> union of all possible string values
141
+ * - The identifier `undefined` -> empty array
142
+ *
143
+ * For any other expression types (identifiers, function calls, member expressions,
144
+ * etc.) the value cannot be determined statically and an empty array is returned.
145
+ *
146
+ * @param expression - The SWC AST expression node to resolve
147
+ * @param returnEmptyStrings - Whether to include empty strings in the result
148
+ * @returns An array of possible string values that the expression may produce.
149
+ */
150
+ resolvePossibleStringValuesFromExpression(expression, returnEmptyStrings = false) {
151
+ // Support selector-style arrow functions used by the selector API:
152
+ // e.g. ($) => $.path.to.key -> 'path.to.key'
153
+ if (expression.type === 'ArrowFunctionExpression') {
154
+ try {
155
+ let body = expression.body;
156
+ // Handle block body with return statement
157
+ if (body.type === 'BlockStatement') {
158
+ const returnStmt = body.stmts.find((s) => s.type === 'ReturnStatement');
159
+ if (returnStmt?.type === 'ReturnStatement' && returnStmt.argument) {
160
+ body = returnStmt.argument;
161
+ }
162
+ else {
163
+ return [];
164
+ }
165
+ }
166
+ let current = body;
167
+ const parts = [];
168
+ while (current && current.type === 'MemberExpression') {
169
+ const prop = current.property;
170
+ if (prop.type === 'Identifier') {
171
+ parts.unshift(prop.value);
172
+ }
173
+ else if (prop.type === 'Computed' && prop.expression && prop.expression.type === 'StringLiteral') {
174
+ parts.unshift(prop.expression.value);
175
+ }
176
+ else {
177
+ return [];
178
+ }
179
+ current = current.object;
180
+ }
181
+ if (parts.length > 0) {
182
+ return [parts.join('.')];
183
+ }
184
+ }
185
+ catch {
186
+ return [];
187
+ }
188
+ }
189
+ if (expression.type === 'StringLiteral') {
190
+ // Filter out empty strings as they should be treated as "no context" like i18next does
191
+ return expression.value || returnEmptyStrings ? [expression.value] : [];
192
+ }
193
+ if (expression.type === 'ConditionalExpression') { // This is a ternary operator
194
+ const consequentValues = this.resolvePossibleStringValuesFromExpression(expression.consequent, returnEmptyStrings);
195
+ const alternateValues = this.resolvePossibleStringValuesFromExpression(expression.alternate, returnEmptyStrings);
196
+ return [...consequentValues, ...alternateValues];
197
+ }
198
+ if (expression.type === 'Identifier' && expression.value === 'undefined') {
199
+ return []; // Handle the `undefined` case
200
+ }
201
+ if (expression.type === 'TemplateLiteral') {
202
+ return this.resolvePossibleStringValuesFromTemplateString(expression);
203
+ }
204
+ // MemberExpression: try to resolve object identifier to an object map in the symbol table
205
+ if (expression.type === 'MemberExpression') {
206
+ try {
207
+ const obj = expression.object;
208
+ const prop = expression.property;
209
+ // only handle simple identifier base + simple property (Identifier or computed StringLiteral)
210
+ if (obj.type === 'Identifier') {
211
+ const baseVar = this.variableTable.get(obj.value);
212
+ const baseShared = this.sharedEnumTable.get(obj.value);
213
+ const base = baseVar ?? baseShared;
214
+ if (base && typeof base !== 'string' && !Array.isArray(base)) {
215
+ let propName;
216
+ if (prop.type === 'Identifier')
217
+ propName = prop.value;
218
+ else if (prop.type === 'Computed' && prop.expression?.type === 'StringLiteral')
219
+ propName = prop.expression.value;
220
+ if (propName && base[propName] !== undefined) {
221
+ return [base[propName]];
222
+ }
223
+ }
224
+ }
225
+ }
226
+ catch { }
227
+ }
228
+ // Binary concatenation support (e.g., a + '_' + b)
229
+ // SWC binary expr can be represented as `BinExpr` with left/right; be permissive:
230
+ if (expression.left && expression.right) {
231
+ try {
232
+ const exprAny = expression;
233
+ const leftNode = exprAny.left;
234
+ const rightNode = exprAny.right;
235
+ // Detect explicit binary concatenation (plus) nodes and only then produce concatenated combos.
236
+ const isBinaryConcat =
237
+ // SWC older shape: BinExpr with op === '+'
238
+ (exprAny.type === 'BinExpr' && exprAny.op === '+') ||
239
+ // Standard AST: BinaryExpression with operator === '+'
240
+ (exprAny.type === 'BinaryExpression' && exprAny.operator === '+') ||
241
+ // Fallbacks
242
+ exprAny.operator === '+' || exprAny.op === '+';
243
+ if (isBinaryConcat) {
244
+ const leftVals = this.resolvePossibleStringValuesFromExpression(leftNode, returnEmptyStrings);
245
+ const rightVals = this.resolvePossibleStringValuesFromExpression(rightNode, returnEmptyStrings);
246
+ if (leftVals.length > 0 && rightVals.length > 0) {
247
+ const combos = [];
248
+ for (const L of leftVals) {
249
+ for (const R of rightVals) {
250
+ combos.push(`${L}${R}`);
251
+ }
252
+ }
253
+ return combos;
254
+ }
255
+ }
256
+ // Handle logical nullish coalescing (a ?? b): result is either left (when not null/undefined) OR right.
257
+ // Represent this conservatively as the union of possible left and right values.
258
+ const isNullishCoalesce =
259
+ // SWC may emit as BinaryExpression with operator '??'
260
+ (exprAny.type === 'BinaryExpression' && exprAny.operator === '??') ||
261
+ (exprAny.type === 'LogicalExpression' && exprAny.operator === '??') ||
262
+ exprAny.operator === '??' || exprAny.op === '??';
263
+ if (isNullishCoalesce) {
264
+ const leftVals = this.resolvePossibleStringValuesFromExpression(leftNode, returnEmptyStrings);
265
+ const rightVals = this.resolvePossibleStringValuesFromExpression(rightNode, returnEmptyStrings);
266
+ if (leftVals.length > 0 || rightVals.length > 0) {
267
+ return Array.from(new Set([...leftVals, ...rightVals]));
268
+ }
269
+ }
270
+ }
271
+ catch { }
272
+ }
273
+ if (expression.type === 'NumericLiteral' || expression.type === 'BooleanLiteral') {
274
+ return [`${expression.value}`]; // Handle literals like 5 or true
275
+ }
276
+ // Support building translation keys for
277
+ // `variable satisfies 'coaching' | 'therapy'`
278
+ if (expression.type === 'TsSatisfiesExpression' || expression.type === 'TsAsExpression') {
279
+ const annotation = expression.typeAnnotation;
280
+ return this.resolvePossibleStringValuesFromType(annotation, returnEmptyStrings);
281
+ }
282
+ // Identifier resolution via captured per-file variable table only
283
+ if (expression.type === 'Identifier') {
284
+ const v = this.variableTable.get(expression.value);
285
+ if (!v)
286
+ return [];
287
+ if (Array.isArray(v))
288
+ return v;
289
+ // object map - cannot be used directly as key, so return empty
290
+ return [];
291
+ }
292
+ // We can't statically determine the value of other expressions (e.g., variables, function calls)
293
+ return [];
294
+ }
295
+ resolvePossibleStringValuesFromType(type, returnEmptyStrings = false) {
296
+ if (type.type === 'TsUnionType') {
297
+ return type.types.flatMap((t) => this.resolvePossibleStringValuesFromType(t, returnEmptyStrings));
298
+ }
299
+ if (type.type === 'TsLiteralType') {
300
+ if (type.literal.type === 'StringLiteral') {
301
+ // Filter out empty strings as they should be treated as "no context" like i18next does
302
+ return type.literal.value || returnEmptyStrings ? [type.literal.value] : [];
303
+ }
304
+ if (type.literal.type === 'TemplateLiteral') {
305
+ return this.resolvePossibleStringValuesFromTemplateLiteralType(type.literal);
306
+ }
307
+ if (type.literal.type === 'NumericLiteral' || type.literal.type === 'BooleanLiteral') {
308
+ return [`${type.literal.value}`]; // Handle literals like 5 or true
309
+ }
310
+ }
311
+ // We can't statically determine the value of other expressions (e.g., variables, function calls)
312
+ return [];
313
+ }
314
+ /**
315
+ * Resolves a template literal string to one or more possible strings that can be
316
+ * determined statically from the AST.
317
+ *
318
+ * @param templateString - The SWC AST template literal string to resolve
319
+ * @returns An array of possible string values that the template may produce.
320
+ */
321
+ resolvePossibleStringValuesFromTemplateString(templateString) {
322
+ // If there are no expressions, we can just return the cooked value
323
+ if (templateString.quasis.length === 1 && templateString.expressions.length === 0) {
324
+ // Ex. `translation.key.no.substitution`
325
+ return [templateString.quasis[0].cooked || ''];
326
+ }
327
+ // Ex. `translation.key.with.expression.${x ? 'title' : 'description'}`
328
+ const [firstQuasis, ...tails] = templateString.quasis;
329
+ const stringValues = templateString.expressions.reduce((heads, expression, i) => {
330
+ return heads.flatMap((head) => {
331
+ const tail = tails[i]?.cooked ?? '';
332
+ return this.resolvePossibleStringValuesFromExpression(expression, true).map((expressionValue) => `${head}${expressionValue}${tail}`);
333
+ });
334
+ }, [firstQuasis.cooked ?? '']);
335
+ return stringValues;
336
+ }
337
+ /**
338
+ * Resolves a template literal type to one or more possible strings that can be
339
+ * determined statically from the AST.
340
+ *
341
+ * @param templateLiteralType - The SWC AST template literal type to resolve
342
+ * @returns An array of possible string values that the template may produce.
343
+ */
344
+ resolvePossibleStringValuesFromTemplateLiteralType(templateLiteralType) {
345
+ // If there are no types, we can just return the cooked value
346
+ if (templateLiteralType.quasis.length === 1 && templateLiteralType.types.length === 0) {
347
+ // Ex. `translation.key.no.substitution`
348
+ return [templateLiteralType.quasis[0].cooked || ''];
349
+ }
350
+ // Ex. `translation.key.with.expression.${'title' | 'description'}`
351
+ const [firstQuasis, ...tails] = templateLiteralType.quasis;
352
+ const stringValues = templateLiteralType.types.reduce((heads, type, i) => {
353
+ return heads.flatMap((head) => {
354
+ const tail = tails[i]?.cooked ?? '';
355
+ return this.resolvePossibleStringValuesFromType(type, true).map((expressionValue) => `${head}${expressionValue}${tail}`);
356
+ });
357
+ }, [firstQuasis.cooked ?? '']);
358
+ return stringValues;
359
+ }
360
+ }
361
+
362
+ exports.ExpressionResolver = ExpressionResolver;