i18next-cli 1.24.13 → 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 (39) hide show
  1. package/dist/cjs/cli.js +1 -1
  2. package/dist/esm/cli.js +1 -1
  3. package/package.json +6 -6
  4. package/types/cli.d.ts +3 -1
  5. package/types/cli.d.ts.map +1 -1
  6. package/CHANGELOG.md +0 -599
  7. package/src/cli.ts +0 -283
  8. package/src/config.ts +0 -215
  9. package/src/extractor/core/ast-visitors.ts +0 -259
  10. package/src/extractor/core/extractor.ts +0 -250
  11. package/src/extractor/core/key-finder.ts +0 -142
  12. package/src/extractor/core/translation-manager.ts +0 -750
  13. package/src/extractor/index.ts +0 -7
  14. package/src/extractor/parsers/ast-utils.ts +0 -87
  15. package/src/extractor/parsers/call-expression-handler.ts +0 -793
  16. package/src/extractor/parsers/comment-parser.ts +0 -424
  17. package/src/extractor/parsers/expression-resolver.ts +0 -391
  18. package/src/extractor/parsers/jsx-handler.ts +0 -488
  19. package/src/extractor/parsers/jsx-parser.ts +0 -1463
  20. package/src/extractor/parsers/scope-manager.ts +0 -445
  21. package/src/extractor/plugin-manager.ts +0 -116
  22. package/src/extractor.ts +0 -15
  23. package/src/heuristic-config.ts +0 -92
  24. package/src/index.ts +0 -22
  25. package/src/init.ts +0 -175
  26. package/src/linter.ts +0 -345
  27. package/src/locize.ts +0 -263
  28. package/src/migrator.ts +0 -208
  29. package/src/rename-key.ts +0 -398
  30. package/src/status.ts +0 -380
  31. package/src/syncer.ts +0 -133
  32. package/src/types-generator.ts +0 -139
  33. package/src/types.ts +0 -577
  34. package/src/utils/default-value.ts +0 -45
  35. package/src/utils/file-utils.ts +0 -167
  36. package/src/utils/funnel-msg-tracker.ts +0 -84
  37. package/src/utils/logger.ts +0 -36
  38. package/src/utils/nested-object.ts +0 -135
  39. package/src/utils/validation.ts +0 -72
@@ -1,391 +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
- // Support selector-style arrow functions used by the selector API:
153
- // e.g. ($) => $.path.to.key -> 'path.to.key'
154
- if (expression.type === 'ArrowFunctionExpression') {
155
- try {
156
- let body: any = expression.body
157
- // Handle block body with return statement
158
- if (body.type === 'BlockStatement') {
159
- const returnStmt = body.stmts.find((s: any) => s.type === 'ReturnStatement')
160
- if (returnStmt?.type === 'ReturnStatement' && returnStmt.argument) {
161
- body = returnStmt.argument
162
- } else {
163
- return []
164
- }
165
- }
166
-
167
- let current: any = body
168
- const parts: string[] = []
169
-
170
- while (current && current.type === 'MemberExpression') {
171
- const prop = current.property
172
- if (prop.type === 'Identifier') {
173
- parts.unshift(prop.value)
174
- } else if (prop.type === 'Computed' && prop.expression && prop.expression.type === 'StringLiteral') {
175
- parts.unshift(prop.expression.value)
176
- } else {
177
- return []
178
- }
179
- current = current.object
180
- }
181
-
182
- if (parts.length > 0) {
183
- return [parts.join('.')]
184
- }
185
- } catch {
186
- return []
187
- }
188
- }
189
-
190
- if (expression.type === 'StringLiteral') {
191
- // Filter out empty strings as they should be treated as "no context" like i18next does
192
- return expression.value || returnEmptyStrings ? [expression.value] : []
193
- }
194
-
195
- if (expression.type === 'ConditionalExpression') { // This is a ternary operator
196
- const consequentValues = this.resolvePossibleStringValuesFromExpression(expression.consequent, returnEmptyStrings)
197
- const alternateValues = this.resolvePossibleStringValuesFromExpression(expression.alternate, returnEmptyStrings)
198
- return [...consequentValues, ...alternateValues]
199
- }
200
-
201
- if (expression.type === 'Identifier' && expression.value === 'undefined') {
202
- return [] // Handle the `undefined` case
203
- }
204
-
205
- if (expression.type === 'TemplateLiteral') {
206
- return this.resolvePossibleStringValuesFromTemplateString(expression)
207
- }
208
-
209
- // MemberExpression: try to resolve object identifier to an object map in the symbol table
210
- if (expression.type === 'MemberExpression') {
211
- try {
212
- const obj = expression.object
213
- const prop = expression.property
214
- // only handle simple identifier base + simple property (Identifier or computed StringLiteral)
215
- if (obj.type === 'Identifier') {
216
- const baseVar = this.variableTable.get(obj.value)
217
- const baseShared = this.sharedEnumTable.get(obj.value)
218
- const base = baseVar ?? baseShared
219
- if (base && typeof base !== 'string' && !Array.isArray(base)) {
220
- let propName: string | undefined
221
- if (prop.type === 'Identifier') propName = prop.value
222
- else if (prop.type === 'Computed' && prop.expression?.type === 'StringLiteral') propName = prop.expression.value
223
- if (propName && base[propName] !== undefined) {
224
- return [base[propName]]
225
- }
226
- }
227
- }
228
- } catch {}
229
- }
230
-
231
- // Binary concatenation support (e.g., a + '_' + b)
232
- // SWC binary expr can be represented as `BinExpr` with left/right; be permissive:
233
- if ((expression as any).left && (expression as any).right) {
234
- try {
235
- const exprAny = expression as any
236
- const leftNode = exprAny.left
237
- const rightNode = exprAny.right
238
-
239
- // Detect explicit binary concatenation (plus) nodes and only then produce concatenated combos.
240
- const isBinaryConcat =
241
- // SWC older shape: BinExpr with op === '+'
242
- (exprAny.type === 'BinExpr' && exprAny.op === '+') ||
243
- // Standard AST: BinaryExpression with operator === '+'
244
- (exprAny.type === 'BinaryExpression' && exprAny.operator === '+') ||
245
- // Fallbacks
246
- exprAny.operator === '+' || exprAny.op === '+'
247
-
248
- if (isBinaryConcat) {
249
- const leftVals = this.resolvePossibleStringValuesFromExpression(leftNode, returnEmptyStrings)
250
- const rightVals = this.resolvePossibleStringValuesFromExpression(rightNode, returnEmptyStrings)
251
- if (leftVals.length > 0 && rightVals.length > 0) {
252
- const combos: string[] = []
253
- for (const L of leftVals) {
254
- for (const R of rightVals) {
255
- combos.push(`${L}${R}`)
256
- }
257
- }
258
- return combos
259
- }
260
- }
261
-
262
- // Handle logical nullish coalescing (a ?? b): result is either left (when not null/undefined) OR right.
263
- // Represent this conservatively as the union of possible left and right values.
264
- const isNullishCoalesce =
265
- // SWC may emit as BinaryExpression with operator '??'
266
- (exprAny.type === 'BinaryExpression' && exprAny.operator === '??') ||
267
- (exprAny.type === 'LogicalExpression' && exprAny.operator === '??') ||
268
- exprAny.operator === '??' || exprAny.op === '??'
269
-
270
- if (isNullishCoalesce) {
271
- const leftVals = this.resolvePossibleStringValuesFromExpression(leftNode, returnEmptyStrings)
272
- const rightVals = this.resolvePossibleStringValuesFromExpression(rightNode, returnEmptyStrings)
273
- if (leftVals.length > 0 || rightVals.length > 0) {
274
- return Array.from(new Set([...leftVals, ...rightVals]))
275
- }
276
- }
277
- } catch {}
278
- }
279
-
280
- if (expression.type === 'NumericLiteral' || expression.type === 'BooleanLiteral') {
281
- return [`${expression.value}`] // Handle literals like 5 or true
282
- }
283
-
284
- // Support building translation keys for
285
- // `variable satisfies 'coaching' | 'therapy'`
286
- if (expression.type === 'TsSatisfiesExpression' || expression.type === 'TsAsExpression') {
287
- const annotation = expression.typeAnnotation
288
- return this.resolvePossibleStringValuesFromType(annotation, returnEmptyStrings)
289
- }
290
-
291
- // Identifier resolution via captured per-file variable table only
292
- if (expression.type === 'Identifier') {
293
- const v = this.variableTable.get(expression.value)
294
- if (!v) return []
295
- if (Array.isArray(v)) return v
296
- // object map - cannot be used directly as key, so return empty
297
- return []
298
- }
299
-
300
- // We can't statically determine the value of other expressions (e.g., variables, function calls)
301
- return []
302
- }
303
-
304
- private resolvePossibleStringValuesFromType (type: TsType, returnEmptyStrings = false): string[] {
305
- if (type.type === 'TsUnionType') {
306
- return type.types.flatMap((t) => this.resolvePossibleStringValuesFromType(t, returnEmptyStrings))
307
- }
308
-
309
- if (type.type === 'TsLiteralType') {
310
- if (type.literal.type === 'StringLiteral') {
311
- // Filter out empty strings as they should be treated as "no context" like i18next does
312
- return type.literal.value || returnEmptyStrings ? [type.literal.value] : []
313
- }
314
-
315
- if (type.literal.type === 'TemplateLiteral') {
316
- return this.resolvePossibleStringValuesFromTemplateLiteralType(type.literal)
317
- }
318
-
319
- if (type.literal.type === 'NumericLiteral' || type.literal.type === 'BooleanLiteral') {
320
- return [`${type.literal.value}`] // Handle literals like 5 or true
321
- }
322
- }
323
-
324
- // We can't statically determine the value of other expressions (e.g., variables, function calls)
325
- return []
326
- }
327
-
328
- /**
329
- * Resolves a template literal string to one or more possible strings that can be
330
- * determined statically from the AST.
331
- *
332
- * @param templateString - The SWC AST template literal string to resolve
333
- * @returns An array of possible string values that the template may produce.
334
- */
335
- private resolvePossibleStringValuesFromTemplateString (templateString: TemplateLiteral): string[] {
336
- // If there are no expressions, we can just return the cooked value
337
- if (templateString.quasis.length === 1 && templateString.expressions.length === 0) {
338
- // Ex. `translation.key.no.substitution`
339
- return [templateString.quasis[0].cooked || '']
340
- }
341
-
342
- // Ex. `translation.key.with.expression.${x ? 'title' : 'description'}`
343
- const [firstQuasis, ...tails] = templateString.quasis
344
-
345
- const stringValues = templateString.expressions.reduce(
346
- (heads, expression, i) => {
347
- return heads.flatMap((head) => {
348
- const tail = tails[i]?.cooked ?? ''
349
- return this.resolvePossibleStringValuesFromExpression(expression, true).map(
350
- (expressionValue) => `${head}${expressionValue}${tail}`
351
- )
352
- })
353
- },
354
- [firstQuasis.cooked ?? '']
355
- )
356
-
357
- return stringValues
358
- }
359
-
360
- /**
361
- * Resolves a template literal type to one or more possible strings that can be
362
- * determined statically from the AST.
363
- *
364
- * @param templateLiteralType - The SWC AST template literal type to resolve
365
- * @returns An array of possible string values that the template may produce.
366
- */
367
- private resolvePossibleStringValuesFromTemplateLiteralType (templateLiteralType: TsTemplateLiteralType): string[] {
368
- // If there are no types, we can just return the cooked value
369
- if (templateLiteralType.quasis.length === 1 && templateLiteralType.types.length === 0) {
370
- // Ex. `translation.key.no.substitution`
371
- return [templateLiteralType.quasis[0].cooked || '']
372
- }
373
-
374
- // Ex. `translation.key.with.expression.${'title' | 'description'}`
375
- const [firstQuasis, ...tails] = templateLiteralType.quasis
376
-
377
- const stringValues = templateLiteralType.types.reduce(
378
- (heads, type, i) => {
379
- return heads.flatMap((head) => {
380
- const tail = tails[i]?.cooked ?? ''
381
- return this.resolvePossibleStringValuesFromType(type, true).map(
382
- (expressionValue) => `${head}${expressionValue}${tail}`
383
- )
384
- })
385
- },
386
- [firstQuasis.cooked ?? '']
387
- )
388
-
389
- return stringValues
390
- }
391
- }