gtx-cli 2.5.35 → 2.5.36
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.
- package/CHANGELOG.md +9 -0
- package/dist/console/formatting.d.ts +1 -0
- package/dist/console/formatting.js +7 -0
- package/dist/console/index.d.ts +3 -1
- package/dist/console/index.js +5 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/react/jsx/utils/constants.d.ts +3 -1
- package/dist/react/jsx/utils/constants.js +13 -3
- package/dist/react/jsx/utils/getCalleeNameFromExpression.d.ts +9 -0
- package/dist/react/jsx/utils/getCalleeNameFromExpression.js +32 -0
- package/dist/react/jsx/utils/getPathsAndAliases.js +3 -3
- package/dist/react/jsx/utils/jsxParsing/handleChildrenWhitespace.js +0 -2
- package/dist/react/jsx/utils/jsxParsing/parseJsx.d.ts +6 -4
- package/dist/react/jsx/utils/jsxParsing/parseJsx.js +168 -154
- package/dist/react/jsx/utils/parseDeclareStatic.d.ts +15 -0
- package/dist/react/jsx/utils/parseDeclareStatic.js +540 -0
- package/dist/react/jsx/utils/parseString.d.ts +25 -0
- package/dist/react/jsx/utils/parseString.js +539 -0
- package/dist/react/jsx/utils/parseStringFunction.d.ts +10 -0
- package/dist/react/jsx/utils/parseStringFunction.js +63 -10
- package/dist/react/jsx/utils/types.d.ts +14 -0
- package/dist/react/jsx/utils/types.js +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
import * as t from '@babel/types';
|
|
2
|
+
import { buildImportMap } from './buildImportMap.js';
|
|
3
|
+
import { resolveImportPath } from './resolveImportPath.js';
|
|
4
|
+
import { parse } from '@babel/parser';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import { warnDeclareStaticNoResultsSync, warnFunctionNotFoundSync, warnInvalidDeclareVarNameSync, } from '../../../console/index.js';
|
|
7
|
+
import traverseModule from '@babel/traverse';
|
|
8
|
+
import { DECLARE_VAR_FUNCTION, GT_LIBRARIES } from './constants.js';
|
|
9
|
+
import { declareVar } from 'generaltranslation/internal';
|
|
10
|
+
import { isStaticExpression } from '../evaluateJsx.js';
|
|
11
|
+
import generateModule from '@babel/generator';
|
|
12
|
+
// Handle CommonJS/ESM interop
|
|
13
|
+
const traverse = traverseModule.default || traverseModule;
|
|
14
|
+
const generate = generateModule.default || generateModule;
|
|
15
|
+
/**
|
|
16
|
+
* Cache for resolved import paths to avoid redundant I/O operations.
|
|
17
|
+
*/
|
|
18
|
+
const resolveImportPathCache = new Map();
|
|
19
|
+
/**
|
|
20
|
+
* Cache for processed functions to avoid re-parsing the same files.
|
|
21
|
+
*/
|
|
22
|
+
const processFunctionCache = new Map();
|
|
23
|
+
/**
|
|
24
|
+
* Processes a string expression node and resolves any function calls within it
|
|
25
|
+
* This handles cases like:
|
|
26
|
+
* - "hello" (string literal)
|
|
27
|
+
* - "hello" + world() (binary expression with function call)
|
|
28
|
+
* - Math.random() > 0.5 ? "day" : "night" (conditional expression)
|
|
29
|
+
* - greeting() (function call that returns string or conditional)
|
|
30
|
+
*
|
|
31
|
+
* @param node - The AST node to process
|
|
32
|
+
* @param tPath - NodePath for scope resolution
|
|
33
|
+
* @param file - Current file path
|
|
34
|
+
* @param parsingOptions - Parsing configuration
|
|
35
|
+
* @param warnings - Set to collect warning messages
|
|
36
|
+
* @returns Node | null
|
|
37
|
+
*/
|
|
38
|
+
export function parseStringExpression(node, tPath, file, parsingOptions, warnings = new Set()) {
|
|
39
|
+
// Handle string literals
|
|
40
|
+
if (t.isStringLiteral(node)) {
|
|
41
|
+
return { type: 'text', text: node.value };
|
|
42
|
+
}
|
|
43
|
+
// Handle numeric literals
|
|
44
|
+
if (t.isNumericLiteral(node)) {
|
|
45
|
+
return { type: 'text', text: String(node.value) };
|
|
46
|
+
}
|
|
47
|
+
// Handle boolean literals
|
|
48
|
+
if (t.isBooleanLiteral(node)) {
|
|
49
|
+
return { type: 'text', text: String(node.value) };
|
|
50
|
+
}
|
|
51
|
+
// Handle null literal
|
|
52
|
+
if (t.isNullLiteral(node)) {
|
|
53
|
+
return { type: 'text', text: 'null' };
|
|
54
|
+
}
|
|
55
|
+
// Handle template literals
|
|
56
|
+
if (t.isTemplateLiteral(node)) {
|
|
57
|
+
const parts = [];
|
|
58
|
+
for (let index = 0; index < node.quasis.length; index++) {
|
|
59
|
+
const quasi = node.quasis[index];
|
|
60
|
+
const text = quasi.value.cooked ?? quasi.value.raw ?? '';
|
|
61
|
+
if (text) {
|
|
62
|
+
parts.push({ type: 'text', text });
|
|
63
|
+
}
|
|
64
|
+
const exprNode = node.expressions[index];
|
|
65
|
+
if (exprNode && t.isExpression(exprNode)) {
|
|
66
|
+
const result = parseStringExpression(exprNode, tPath, file, parsingOptions, warnings);
|
|
67
|
+
if (result === null) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
parts.push(result);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (parts.length === 0) {
|
|
74
|
+
return { type: 'text', text: '' };
|
|
75
|
+
}
|
|
76
|
+
if (parts.length === 1) {
|
|
77
|
+
return parts[0];
|
|
78
|
+
}
|
|
79
|
+
return { type: 'sequence', nodes: parts };
|
|
80
|
+
}
|
|
81
|
+
// Handle binary expressions (e.g., "hello" + world())
|
|
82
|
+
if (t.isBinaryExpression(node) && node.operator === '+') {
|
|
83
|
+
if (!t.isExpression(node.left) || !t.isExpression(node.right)) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const leftResult = parseStringExpression(node.left, tPath, file, parsingOptions, warnings);
|
|
87
|
+
const rightResult = parseStringExpression(node.right, tPath, file, parsingOptions, warnings);
|
|
88
|
+
if (leftResult === null || rightResult === null) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return { type: 'sequence', nodes: [leftResult, rightResult] };
|
|
92
|
+
}
|
|
93
|
+
// Handle conditional expressions (e.g., cond ? "day" : "night")
|
|
94
|
+
if (t.isConditionalExpression(node)) {
|
|
95
|
+
if (!t.isExpression(node.consequent) || !t.isExpression(node.alternate)) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const consequentResult = parseStringExpression(node.consequent, tPath, file, parsingOptions, warnings);
|
|
99
|
+
const alternateResult = parseStringExpression(node.alternate, tPath, file, parsingOptions, warnings);
|
|
100
|
+
if (consequentResult === null || alternateResult === null) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
// Create a choice node with both branches
|
|
104
|
+
return {
|
|
105
|
+
type: 'choice',
|
|
106
|
+
nodes: [consequentResult, alternateResult],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// Handle variable references (e.g., result)
|
|
110
|
+
if (t.isIdentifier(node)) {
|
|
111
|
+
const binding = tPath.scope.getBinding(node.name);
|
|
112
|
+
if (!binding) {
|
|
113
|
+
// Variable not found in scope
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
// Check if it's a const/let/var with an initializer
|
|
117
|
+
if (binding.path.isVariableDeclarator() && binding.path.node.init) {
|
|
118
|
+
const init = binding.path.node.init;
|
|
119
|
+
if (t.isExpression(init)) {
|
|
120
|
+
// Recursively resolve the initializer
|
|
121
|
+
return parseStringExpression(init, binding.path, file, parsingOptions, warnings);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Not a resolvable variable
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
// Handle function calls (e.g., getName())
|
|
128
|
+
if (t.isCallExpression(node) && t.isIdentifier(node.callee)) {
|
|
129
|
+
const functionName = node.callee.name;
|
|
130
|
+
const calleeBinding = tPath.scope.getBinding(functionName);
|
|
131
|
+
if (!calleeBinding) {
|
|
132
|
+
// Function not found in scope
|
|
133
|
+
warnings.add(warnFunctionNotFoundSync(file, functionName, `${node.callee.loc?.start?.line}:${node.callee.loc?.start?.column}`));
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
// Check if this is an imported function
|
|
137
|
+
const programPath = tPath.scope.getProgramParent().path;
|
|
138
|
+
const importedFunctionsMap = buildImportMap(programPath);
|
|
139
|
+
if (importedFunctionsMap.has(functionName)) {
|
|
140
|
+
// Function is imported - resolve cross-file
|
|
141
|
+
let originalName;
|
|
142
|
+
if (calleeBinding.path.isImportSpecifier()) {
|
|
143
|
+
originalName = t.isIdentifier(calleeBinding.path.node.imported)
|
|
144
|
+
? calleeBinding.path.node.imported.name
|
|
145
|
+
: calleeBinding.path.node.imported.value;
|
|
146
|
+
}
|
|
147
|
+
else if (calleeBinding.path.isImportDefaultSpecifier()) {
|
|
148
|
+
originalName = calleeBinding.path.node.local.name;
|
|
149
|
+
}
|
|
150
|
+
else if (calleeBinding.path.isImportNamespaceSpecifier()) {
|
|
151
|
+
originalName = calleeBinding.path.node.local.name;
|
|
152
|
+
}
|
|
153
|
+
const importPath = importedFunctionsMap.get(functionName);
|
|
154
|
+
// Handle declareVar function
|
|
155
|
+
if (originalName === DECLARE_VAR_FUNCTION &&
|
|
156
|
+
GT_LIBRARIES.includes(importPath)) {
|
|
157
|
+
// check for name field eg declareVar('test', { $name: 'test' })
|
|
158
|
+
if (node.arguments.length > 1 &&
|
|
159
|
+
t.isObjectExpression(node.arguments[1])) {
|
|
160
|
+
const name = node.arguments[1].properties
|
|
161
|
+
.filter((prop) => t.isObjectProperty(prop))
|
|
162
|
+
.find((prop) => t.isIdentifier(prop.key) && prop.key.name === '$name')?.value;
|
|
163
|
+
if (name) {
|
|
164
|
+
if (!t.isExpression(name)) {
|
|
165
|
+
warnings.add(warnInvalidDeclareVarNameSync(file, generate(name).code, `${node.arguments[1].loc?.start?.line}:${node.arguments[1].loc?.start?.column}`));
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const staticResult = isStaticExpression(name);
|
|
169
|
+
if (!staticResult.isStatic) {
|
|
170
|
+
warnings.add(warnInvalidDeclareVarNameSync(file, generate(name).code, `${node.arguments[1].loc?.start?.line}:${node.arguments[1].loc?.start?.column}`));
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
type: 'text',
|
|
175
|
+
text: declareVar('', { $name: staticResult.value }),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
type: 'text',
|
|
181
|
+
text: declareVar(''),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const filePath = resolveImportPath(file, importPath, parsingOptions, resolveImportPathCache);
|
|
185
|
+
if (filePath && originalName) {
|
|
186
|
+
return resolveFunctionInFile(filePath, originalName, parsingOptions, warnings);
|
|
187
|
+
}
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
// Resolve the function locally and get its return values
|
|
191
|
+
return resolveFunctionCall(calleeBinding, tPath, file, parsingOptions, warnings);
|
|
192
|
+
}
|
|
193
|
+
// Handle parenthesized expressions
|
|
194
|
+
if (t.isParenthesizedExpression(node)) {
|
|
195
|
+
return parseStringExpression(node.expression, tPath, file, parsingOptions, warnings);
|
|
196
|
+
}
|
|
197
|
+
// Handle unary expressions (e.g., -123)
|
|
198
|
+
if (t.isUnaryExpression(node)) {
|
|
199
|
+
let operator = '';
|
|
200
|
+
if (node.operator === '-') {
|
|
201
|
+
operator = node.operator;
|
|
202
|
+
}
|
|
203
|
+
if (t.isNumericLiteral(node.argument)) {
|
|
204
|
+
if (node.argument.value === 0) {
|
|
205
|
+
return { type: 'text', text: '0' };
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
return {
|
|
209
|
+
type: 'text',
|
|
210
|
+
text: operator + node.argument.value.toString(),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
// Unsupported expression type
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Resolves a function call by traversing its body and collecting return values
|
|
221
|
+
*/
|
|
222
|
+
function resolveFunctionCall(calleeBinding, tPath, file, parsingOptions, warnings) {
|
|
223
|
+
if (!calleeBinding) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
const bindingPath = calleeBinding.path;
|
|
227
|
+
const branches = [];
|
|
228
|
+
// Handle function declarations: function time() { return "day"; }
|
|
229
|
+
if (bindingPath.isFunctionDeclaration()) {
|
|
230
|
+
bindingPath.traverse({
|
|
231
|
+
// Don't skip nested functions - let parseStringExpression handle function calls
|
|
232
|
+
ReturnStatement(returnPath) {
|
|
233
|
+
// Only process return statements that are direct children of this function
|
|
234
|
+
// Skip return statements from nested functions (they'll be handled when those functions are called)
|
|
235
|
+
const parentFunction = returnPath.getFunctionParent();
|
|
236
|
+
if (parentFunction?.node !== bindingPath.node) {
|
|
237
|
+
// This return belongs to a nested function, skip it
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const returnArg = returnPath.node.argument;
|
|
241
|
+
if (!returnArg || !t.isExpression(returnArg)) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const returnResult = parseStringExpression(returnArg, returnPath, file, parsingOptions, warnings);
|
|
245
|
+
if (returnResult !== null) {
|
|
246
|
+
branches.push(returnResult);
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
// Handle arrow functions: const time = () => "day"
|
|
252
|
+
else if (bindingPath.isVariableDeclarator()) {
|
|
253
|
+
const init = bindingPath.get('init');
|
|
254
|
+
if (!init.isArrowFunctionExpression()) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
const body = init.get('body');
|
|
258
|
+
// Handle expression body: () => "day"
|
|
259
|
+
if (body.isExpression()) {
|
|
260
|
+
const bodyResult = parseStringExpression(body.node, body, file, parsingOptions, warnings);
|
|
261
|
+
if (bodyResult !== null) {
|
|
262
|
+
branches.push(bodyResult);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Handle block body: () => { return "day"; }
|
|
266
|
+
else if (body.isBlockStatement()) {
|
|
267
|
+
const arrowFunction = init.node;
|
|
268
|
+
body.traverse({
|
|
269
|
+
// Don't skip nested functions - let parseStringExpression handle function calls
|
|
270
|
+
ReturnStatement(returnPath) {
|
|
271
|
+
// Only process return statements that are direct children of this function
|
|
272
|
+
// Skip return statements from nested functions (they'll be handled when those functions are called)
|
|
273
|
+
const parentFunction = returnPath.getFunctionParent();
|
|
274
|
+
if (parentFunction?.node !== arrowFunction) {
|
|
275
|
+
// This return belongs to a nested function, skip it
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const returnArg = returnPath.node.argument;
|
|
279
|
+
if (!returnArg || !t.isExpression(returnArg)) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const returnResult = parseStringExpression(returnArg, returnPath, file, parsingOptions, warnings);
|
|
283
|
+
if (returnResult !== null) {
|
|
284
|
+
branches.push(returnResult);
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (branches.length === 0) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
if (branches.length === 1) {
|
|
294
|
+
return branches[0];
|
|
295
|
+
}
|
|
296
|
+
return { type: 'choice', nodes: branches };
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Resolves a function definition in an external file
|
|
300
|
+
*/
|
|
301
|
+
function resolveFunctionInFile(filePath, functionName, parsingOptions, warnings) {
|
|
302
|
+
// Check cache first
|
|
303
|
+
const cacheKey = `${filePath}::${functionName}`;
|
|
304
|
+
if (processFunctionCache.has(cacheKey)) {
|
|
305
|
+
return processFunctionCache.get(cacheKey) ?? null;
|
|
306
|
+
}
|
|
307
|
+
let result = null;
|
|
308
|
+
try {
|
|
309
|
+
const code = fs.readFileSync(filePath, 'utf8');
|
|
310
|
+
const ast = parse(code, {
|
|
311
|
+
sourceType: 'module',
|
|
312
|
+
plugins: ['jsx', 'typescript'],
|
|
313
|
+
});
|
|
314
|
+
traverse(ast, {
|
|
315
|
+
// Handle re-exports: export * from './utils1'
|
|
316
|
+
ExportAllDeclaration(path) {
|
|
317
|
+
// Only follow re-exports if we haven't found the function yet
|
|
318
|
+
if (result !== null)
|
|
319
|
+
return;
|
|
320
|
+
if (t.isStringLiteral(path.node.source)) {
|
|
321
|
+
const reexportPath = path.node.source.value;
|
|
322
|
+
const resolvedPath = resolveImportPath(filePath, reexportPath, parsingOptions, resolveImportPathCache);
|
|
323
|
+
if (resolvedPath) {
|
|
324
|
+
// Recursively resolve in the re-exported file
|
|
325
|
+
const reexportResult = resolveFunctionInFile(resolvedPath, functionName, parsingOptions, warnings);
|
|
326
|
+
if (reexportResult) {
|
|
327
|
+
result = reexportResult;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
// Handle named re-exports: export { fn1 } from './utils'
|
|
333
|
+
ExportNamedDeclaration(path) {
|
|
334
|
+
// Only follow re-exports if we haven't found the function yet
|
|
335
|
+
if (result !== null)
|
|
336
|
+
return;
|
|
337
|
+
// Check if this is a re-export with a source
|
|
338
|
+
if (path.node.source && t.isStringLiteral(path.node.source)) {
|
|
339
|
+
// Check if any of the exported specifiers match our function name
|
|
340
|
+
const hasMatchingExport = path.node.specifiers.some((spec) => {
|
|
341
|
+
if (t.isExportSpecifier(spec)) {
|
|
342
|
+
const exportedName = t.isIdentifier(spec.exported)
|
|
343
|
+
? spec.exported.name
|
|
344
|
+
: spec.exported.value;
|
|
345
|
+
return exportedName === functionName;
|
|
346
|
+
}
|
|
347
|
+
return false;
|
|
348
|
+
});
|
|
349
|
+
if (hasMatchingExport) {
|
|
350
|
+
const reexportPath = path.node.source.value;
|
|
351
|
+
const resolvedPath = resolveImportPath(filePath, reexportPath, parsingOptions, resolveImportPathCache);
|
|
352
|
+
if (resolvedPath) {
|
|
353
|
+
// Find the original name in case it was renamed
|
|
354
|
+
const specifier = path.node.specifiers.find((spec) => {
|
|
355
|
+
if (t.isExportSpecifier(spec)) {
|
|
356
|
+
const exportedName = t.isIdentifier(spec.exported)
|
|
357
|
+
? spec.exported.name
|
|
358
|
+
: spec.exported.value;
|
|
359
|
+
return exportedName === functionName;
|
|
360
|
+
}
|
|
361
|
+
return false;
|
|
362
|
+
});
|
|
363
|
+
let originalName = functionName;
|
|
364
|
+
if (specifier &&
|
|
365
|
+
t.isExportSpecifier(specifier) &&
|
|
366
|
+
t.isIdentifier(specifier.local)) {
|
|
367
|
+
originalName = specifier.local.name;
|
|
368
|
+
}
|
|
369
|
+
// Recursively resolve in the re-exported file
|
|
370
|
+
const reexportResult = resolveFunctionInFile(resolvedPath, originalName, parsingOptions, warnings);
|
|
371
|
+
if (reexportResult) {
|
|
372
|
+
result = reexportResult;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
// Handle function declarations: function interjection() { ... }
|
|
379
|
+
FunctionDeclaration(path) {
|
|
380
|
+
if (path.node.id?.name === functionName && result === null) {
|
|
381
|
+
const branches = [];
|
|
382
|
+
path.traverse({
|
|
383
|
+
Function(innerPath) {
|
|
384
|
+
// Skip nested functions
|
|
385
|
+
innerPath.skip();
|
|
386
|
+
},
|
|
387
|
+
ReturnStatement(returnPath) {
|
|
388
|
+
if (!t.isReturnStatement(returnPath.node)) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
const returnArg = returnPath.node.argument;
|
|
392
|
+
if (!returnArg || !t.isExpression(returnArg)) {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const returnResult = parseStringExpression(returnArg, returnPath, filePath, parsingOptions, warnings);
|
|
396
|
+
if (returnResult !== null) {
|
|
397
|
+
branches.push(returnResult);
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
if (branches.length === 1) {
|
|
402
|
+
result = branches[0];
|
|
403
|
+
}
|
|
404
|
+
else if (branches.length > 1) {
|
|
405
|
+
result = { type: 'choice', nodes: branches };
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
// Handle variable declarations: const interjection = () => { ... }
|
|
410
|
+
VariableDeclarator(path) {
|
|
411
|
+
if (t.isIdentifier(path.node.id) &&
|
|
412
|
+
path.node.id.name === functionName &&
|
|
413
|
+
path.node.init &&
|
|
414
|
+
(t.isArrowFunctionExpression(path.node.init) ||
|
|
415
|
+
t.isFunctionExpression(path.node.init)) &&
|
|
416
|
+
result === null) {
|
|
417
|
+
const init = path.get('init');
|
|
418
|
+
if (!init.isArrowFunctionExpression() &&
|
|
419
|
+
!init.isFunctionExpression()) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const bodyPath = init.get('body');
|
|
423
|
+
const branches = [];
|
|
424
|
+
// Handle expression body: () => "day"
|
|
425
|
+
if (!Array.isArray(bodyPath) && t.isExpression(bodyPath.node)) {
|
|
426
|
+
const bodyResult = parseStringExpression(bodyPath.node, bodyPath, filePath, parsingOptions, warnings);
|
|
427
|
+
if (bodyResult !== null) {
|
|
428
|
+
branches.push(bodyResult);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
// Handle block body: () => { return "day"; }
|
|
432
|
+
else if (!Array.isArray(bodyPath) &&
|
|
433
|
+
t.isBlockStatement(bodyPath.node)) {
|
|
434
|
+
const arrowFunction = init.node;
|
|
435
|
+
bodyPath.traverse({
|
|
436
|
+
Function(innerPath) {
|
|
437
|
+
// Skip nested functions
|
|
438
|
+
innerPath.skip();
|
|
439
|
+
},
|
|
440
|
+
ReturnStatement(returnPath) {
|
|
441
|
+
// Only process return statements that are direct children of this function
|
|
442
|
+
const parentFunction = returnPath.getFunctionParent();
|
|
443
|
+
if (parentFunction?.node !== arrowFunction) {
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (!t.isReturnStatement(returnPath.node)) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const returnArg = returnPath.node.argument;
|
|
450
|
+
if (!returnArg || !t.isExpression(returnArg)) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
const returnResult = parseStringExpression(returnArg, returnPath, filePath, parsingOptions, warnings);
|
|
454
|
+
if (returnResult !== null) {
|
|
455
|
+
branches.push(returnResult);
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
if (branches.length === 1) {
|
|
461
|
+
result = branches[0];
|
|
462
|
+
}
|
|
463
|
+
else if (branches.length > 1) {
|
|
464
|
+
result = { type: 'choice', nodes: branches };
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
catch (error) {
|
|
471
|
+
// File read or parse error - return null
|
|
472
|
+
warnings.add(warnDeclareStaticNoResultsSync(filePath, functionName, 'file read/parse error: ' + error));
|
|
473
|
+
result = null;
|
|
474
|
+
}
|
|
475
|
+
// Cache the result
|
|
476
|
+
processFunctionCache.set(cacheKey, result);
|
|
477
|
+
return result;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Converts a Node tree to an array of all possible string combinations
|
|
481
|
+
* This is a helper function for compatibility with existing code
|
|
482
|
+
*/
|
|
483
|
+
export function nodeToStrings(node) {
|
|
484
|
+
if (node === null) {
|
|
485
|
+
return [];
|
|
486
|
+
}
|
|
487
|
+
// Handle TextNode
|
|
488
|
+
if (typeof node === 'object' &&
|
|
489
|
+
node !== null &&
|
|
490
|
+
'type' in node &&
|
|
491
|
+
node.type === 'text') {
|
|
492
|
+
return [node.text];
|
|
493
|
+
}
|
|
494
|
+
// Handle SequenceNode - concatenate all parts
|
|
495
|
+
if (typeof node === 'object' &&
|
|
496
|
+
node !== null &&
|
|
497
|
+
'type' in node &&
|
|
498
|
+
node.type === 'sequence') {
|
|
499
|
+
const partResults = node.nodes.map((n) => nodeToStrings(n));
|
|
500
|
+
return cartesianProduct(partResults);
|
|
501
|
+
}
|
|
502
|
+
// Handle ChoiceNode - flatten all branches
|
|
503
|
+
if (typeof node === 'object' &&
|
|
504
|
+
node !== null &&
|
|
505
|
+
'type' in node &&
|
|
506
|
+
node.type === 'choice') {
|
|
507
|
+
const allStrings = [];
|
|
508
|
+
for (const branch of node.nodes) {
|
|
509
|
+
allStrings.push(...nodeToStrings(branch));
|
|
510
|
+
}
|
|
511
|
+
return [...new Set(allStrings)]; // Deduplicate
|
|
512
|
+
}
|
|
513
|
+
return [];
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Creates cartesian product of string arrays and concatenates them
|
|
517
|
+
* @example cartesianProduct([["Hello "], ["day", "night"]]) → ["Hello day", "Hello night"]
|
|
518
|
+
*/
|
|
519
|
+
function cartesianProduct(arrays) {
|
|
520
|
+
if (arrays.length === 0) {
|
|
521
|
+
return [];
|
|
522
|
+
}
|
|
523
|
+
if (arrays.length === 1) {
|
|
524
|
+
return arrays[0];
|
|
525
|
+
}
|
|
526
|
+
// Start with first array
|
|
527
|
+
let result = arrays[0];
|
|
528
|
+
// Combine with each subsequent array
|
|
529
|
+
for (let i = 1; i < arrays.length; i++) {
|
|
530
|
+
const newResult = [];
|
|
531
|
+
for (const prev of result) {
|
|
532
|
+
for (const curr of arrays[i]) {
|
|
533
|
+
newResult.push(prev + curr);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
result = newResult;
|
|
537
|
+
}
|
|
538
|
+
return result;
|
|
539
|
+
}
|
|
@@ -5,6 +5,16 @@ import type { ParsingConfigOptions } from '../../../types/parsing.js';
|
|
|
5
5
|
* Clears all caches. Useful for testing or when file system changes.
|
|
6
6
|
*/
|
|
7
7
|
export declare function clearParsingCaches(): void;
|
|
8
|
+
/**
|
|
9
|
+
* Recursively resolves variable assignments to find all aliases of a translation callback parameter.
|
|
10
|
+
* Handles cases like: const t = translate; const a = translate; const b = a; const c = b;
|
|
11
|
+
*
|
|
12
|
+
* @param scope The scope to search within
|
|
13
|
+
* @param variableName The variable name to resolve
|
|
14
|
+
* @param visited Set to track already visited variables to prevent infinite loops
|
|
15
|
+
* @returns Array of all variable names that reference the original translation callback
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveVariableAliases(scope: any, variableName: string, visited?: Set<string>): string[];
|
|
8
18
|
/**
|
|
9
19
|
* Main entry point for parsing translation strings from useGT() and getGT() calls.
|
|
10
20
|
*
|