i18next-cli 1.34.0 → 1.34.1

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