i18next-cli 1.24.12 → 1.24.14

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