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