@generaltranslation/python-extractor 0.2.20 → 0.2.21
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/dist/constants.js +22 -17
- package/dist/constants.js.map +1 -0
- package/dist/extractCalls.js +188 -271
- package/dist/extractCalls.js.map +1 -0
- package/dist/extractImports.js +61 -76
- package/dist/extractImports.js.map +1 -0
- package/dist/index.js +50 -52
- package/dist/index.js.map +1 -0
- package/dist/parseStringExpression.js +722 -983
- package/dist/parseStringExpression.js.map +1 -0
- package/dist/parser.js +30 -37
- package/dist/parser.js.map +1 -0
- package/dist/resolveFunctionVariants.js +154 -181
- package/dist/resolveFunctionVariants.js.map +1 -0
- package/dist/resolveImport.js +48 -60
- package/dist/resolveImport.js.map +1 -0
- package/dist/stringNode.js +32 -46
- package/dist/stringNode.js.map +1 -0
- package/package.json +7 -4
|
@@ -1,1064 +1,803 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { getParser } from
|
|
3
|
-
import {
|
|
4
|
-
import { resolveFunctionInCurrentFile, resolveFunctionInFile
|
|
5
|
-
import { extractImports } from
|
|
6
|
-
import
|
|
7
|
-
import { declareVar } from
|
|
1
|
+
import "./constants.js";
|
|
2
|
+
import { getParser } from "./parser.js";
|
|
3
|
+
import { resolveImportPath } from "./resolveImport.js";
|
|
4
|
+
import { resolveFunctionInCurrentFile, resolveFunctionInFile } from "./resolveFunctionVariants.js";
|
|
5
|
+
import { extractImports } from "./extractImports.js";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import { declareVar } from "generaltranslation/internal";
|
|
8
|
+
//#region src/parseStringExpression.ts
|
|
8
9
|
/**
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
* Returns true if the original import name is derive() or declare_static() (deprecated).
|
|
11
|
+
*/
|
|
11
12
|
function isDeriveFunction(originalName) {
|
|
12
|
-
|
|
13
|
+
return originalName === "derive" || originalName === "declare_static";
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return hasDeriveCallRecursive(node, staticNames);
|
|
16
|
+
* Checks if an expression contains derive/declare_static or declare_var calls.
|
|
17
|
+
*/
|
|
18
|
+
function containsStaticCalls(node, imports) {
|
|
19
|
+
const staticNames = getDeriveImportNames(imports);
|
|
20
|
+
if (staticNames.size === 0) return false;
|
|
21
|
+
return hasDeriveCallRecursive(node, staticNames);
|
|
22
22
|
}
|
|
23
23
|
function hasDeriveCallRecursive(node, names) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const child = node.child(i);
|
|
34
|
-
if (child && hasDeriveCallRecursive(child, names))
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
return false;
|
|
24
|
+
if (node.type === "call") {
|
|
25
|
+
const funcNode = node.childForFieldName("function");
|
|
26
|
+
if (funcNode && funcNode.type === "identifier" && names.has(funcNode.text)) return true;
|
|
27
|
+
}
|
|
28
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
29
|
+
const child = node.child(i);
|
|
30
|
+
if (child && hasDeriveCallRecursive(child, names)) return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
38
33
|
}
|
|
39
34
|
/**
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const funcNode = node.childForFieldName('function');
|
|
73
|
-
if (funcNode && funcNode.type === 'identifier') {
|
|
74
|
-
const originalName = getOriginalImportName(funcNode.text, ctx.imports);
|
|
75
|
-
if (isDeriveFunction(originalName)) {
|
|
76
|
-
return resolveDeclareStaticArg(node, ctx);
|
|
77
|
-
}
|
|
78
|
-
if (originalName === PYTHON_DECLARE_VAR) {
|
|
79
|
-
return resolveDeclareVarArg(node, ctx);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
ctx.errors.push(`${locationStr(node)}: unsupported expression type "${node.type}" in translation call`);
|
|
84
|
-
return null;
|
|
35
|
+
* Parses the first argument of t() into a StringNode tree.
|
|
36
|
+
* Handles: plain strings, f-strings with derive/declare_var,
|
|
37
|
+
* binary + concatenation, and standalone derive calls.
|
|
38
|
+
*/
|
|
39
|
+
async function parseStringExpression(node, ctx) {
|
|
40
|
+
if (node.type === "parenthesized_expression") {
|
|
41
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
42
|
+
const child = node.child(i);
|
|
43
|
+
if (child && child.type !== "(" && child.type !== ")") return parseStringExpression(child, ctx);
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (node.type === "string" && !isFString(node)) {
|
|
48
|
+
const content = extractStringContent(node);
|
|
49
|
+
if (content === void 0) return null;
|
|
50
|
+
return {
|
|
51
|
+
type: "text",
|
|
52
|
+
text: content
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (node.type === "string" && isFString(node)) return parseFString(node, ctx);
|
|
56
|
+
if (node.type === "binary_operator") return parseBinaryOperator(node, ctx);
|
|
57
|
+
if (node.type === "call") {
|
|
58
|
+
const funcNode = node.childForFieldName("function");
|
|
59
|
+
if (funcNode && funcNode.type === "identifier") {
|
|
60
|
+
const originalName = getOriginalImportName(funcNode.text, ctx.imports);
|
|
61
|
+
if (isDeriveFunction(originalName)) return resolveDeclareStaticArg(node, ctx);
|
|
62
|
+
if (originalName === "declare_var") return resolveDeclareVarArg(node, ctx);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
ctx.errors.push(`${locationStr(node)}: unsupported expression type "${node.type}" in translation call`);
|
|
66
|
+
return null;
|
|
85
67
|
}
|
|
86
68
|
/**
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
69
|
+
* Parses an f-string into a StringNode tree.
|
|
70
|
+
* string_content → text nodes, interpolation → check for derive/declare_var
|
|
71
|
+
*/
|
|
90
72
|
async function parseFString(node, ctx) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
73
|
+
const parts = [];
|
|
74
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
75
|
+
const child = node.child(i);
|
|
76
|
+
if (!child) continue;
|
|
77
|
+
if (child.type === "string_content") {
|
|
78
|
+
if (child.text.length > 0) parts.push({
|
|
79
|
+
type: "text",
|
|
80
|
+
text: child.text
|
|
81
|
+
});
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (child.type === "interpolation") {
|
|
85
|
+
const result = await parseInterpolation(child, ctx);
|
|
86
|
+
if (result) parts.push(result);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (parts.length === 0) return {
|
|
91
|
+
type: "text",
|
|
92
|
+
text: ""
|
|
93
|
+
};
|
|
94
|
+
if (parts.length === 1) return parts[0];
|
|
95
|
+
return {
|
|
96
|
+
type: "sequence",
|
|
97
|
+
nodes: parts
|
|
98
|
+
};
|
|
116
99
|
}
|
|
117
100
|
/**
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
101
|
+
* Parses an interpolation within an f-string.
|
|
102
|
+
* Must be a derive() or declare_var() call.
|
|
103
|
+
*/
|
|
121
104
|
async function parseInterpolation(interpNode, ctx) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return resolveDeclareStaticArg(expr, ctx);
|
|
145
|
-
}
|
|
146
|
-
if (originalName === PYTHON_DECLARE_VAR) {
|
|
147
|
-
return resolveDeclareVarArg(expr, ctx);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
// Not a derive/declare_var call — error
|
|
152
|
-
ctx.errors.push(`${locationStr(interpNode)}: f-string interpolation must use derive() or declare_var(), got "${expr.text}"`);
|
|
153
|
-
return null;
|
|
105
|
+
let expr = null;
|
|
106
|
+
for (let i = 0; i < interpNode.childCount; i++) {
|
|
107
|
+
const child = interpNode.child(i);
|
|
108
|
+
if (child && child.type !== "{" && child.type !== "}" && child.type !== "type_conversion" && child.type !== "format_specifier") {
|
|
109
|
+
expr = child;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!expr) {
|
|
114
|
+
ctx.errors.push(`${locationStr(interpNode)}: empty interpolation in f-string`);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
if (expr.type === "call") {
|
|
118
|
+
const funcNode = expr.childForFieldName("function");
|
|
119
|
+
if (funcNode && funcNode.type === "identifier") {
|
|
120
|
+
const originalName = getOriginalImportName(funcNode.text, ctx.imports);
|
|
121
|
+
if (isDeriveFunction(originalName)) return resolveDeclareStaticArg(expr, ctx);
|
|
122
|
+
if (originalName === "declare_var") return resolveDeclareVarArg(expr, ctx);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
ctx.errors.push(`${locationStr(interpNode)}: f-string interpolation must use derive() or declare_var(), got "${expr.text}"`);
|
|
126
|
+
return null;
|
|
154
127
|
}
|
|
155
128
|
/**
|
|
156
|
-
|
|
157
|
-
|
|
129
|
+
* Parses binary + concatenation into a sequence node.
|
|
130
|
+
*/
|
|
158
131
|
async function parseBinaryOperator(node, ctx) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
if (rightNode.type === 'sequence') {
|
|
184
|
-
parts.push(...rightNode.nodes);
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
parts.push(rightNode);
|
|
188
|
-
}
|
|
189
|
-
return { type: 'sequence', nodes: parts };
|
|
132
|
+
const left = node.childForFieldName("left");
|
|
133
|
+
const operator = node.childForFieldName("operator");
|
|
134
|
+
const right = node.childForFieldName("right");
|
|
135
|
+
if (!left || !right) {
|
|
136
|
+
ctx.errors.push(`${locationStr(node)}: binary operator missing operands`);
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
if (operator && operator.text !== "+") {
|
|
140
|
+
ctx.errors.push(`${locationStr(node)}: unsupported binary operator "${operator.text}" in translation call`);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const leftNode = await parseStringExpression(left, ctx);
|
|
144
|
+
const rightNode = await parseStringExpression(right, ctx);
|
|
145
|
+
if (!leftNode || !rightNode) return null;
|
|
146
|
+
const parts = [];
|
|
147
|
+
if (leftNode.type === "sequence") parts.push(...leftNode.nodes);
|
|
148
|
+
else parts.push(leftNode);
|
|
149
|
+
if (rightNode.type === "sequence") parts.push(...rightNode.nodes);
|
|
150
|
+
else parts.push(rightNode);
|
|
151
|
+
return {
|
|
152
|
+
type: "sequence",
|
|
153
|
+
nodes: parts
|
|
154
|
+
};
|
|
190
155
|
}
|
|
191
156
|
/**
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
157
|
+
* Resolves the argument of a derive() call into a StringNode.
|
|
158
|
+
* Handles: string literals, ternary expressions, function calls.
|
|
159
|
+
*/
|
|
195
160
|
async function resolveDeclareStaticArg(callNode, ctx) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
161
|
+
const arg = getFirstPositionalArg(callNode);
|
|
162
|
+
if (!arg) {
|
|
163
|
+
ctx.errors.push(`${locationStr(callNode)}: derive() / declare_static() requires an argument`);
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
return resolveStaticValue(arg, ctx);
|
|
202
167
|
}
|
|
203
168
|
/**
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
169
|
+
* Resolves a value expression that should produce string variants.
|
|
170
|
+
* Handles: string literals, ternary, function calls, binary concat,
|
|
171
|
+
* and declare_var() calls (nested inside derive).
|
|
172
|
+
*/
|
|
208
173
|
async function resolveStaticValue(node, ctx) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
return resolveFunctionCall(node, ctx);
|
|
244
|
-
}
|
|
245
|
-
// Identifier: resolve to its assigned value
|
|
246
|
-
if (node.type === 'identifier') {
|
|
247
|
-
const result = await resolveIdentifier(node, ctx);
|
|
248
|
-
if (result)
|
|
249
|
-
return result;
|
|
250
|
-
ctx.errors.push(`${locationStr(node)}: could not resolve identifier "${node.text}" to a static value`);
|
|
251
|
-
return null;
|
|
252
|
-
}
|
|
253
|
-
// Subscript: dictionary access like LABELS[score] — returns all values as choices
|
|
254
|
-
if (node.type === 'subscript') {
|
|
255
|
-
return resolveSubscript(node, ctx);
|
|
256
|
-
}
|
|
257
|
-
// Attribute: dictionary access like obj.attr — returns the specific value
|
|
258
|
-
if (node.type === 'attribute') {
|
|
259
|
-
return resolveAttribute(node, ctx);
|
|
260
|
-
}
|
|
261
|
-
ctx.errors.push(`${locationStr(node)}: unsupported derive() argument type "${node.type}"`);
|
|
262
|
-
return null;
|
|
174
|
+
if (node.type === "parenthesized_expression") {
|
|
175
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
176
|
+
const child = node.child(i);
|
|
177
|
+
if (child && child.type !== "(" && child.type !== ")") return resolveStaticValue(child, ctx);
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
if (node.type === "string" && !isFString(node)) {
|
|
182
|
+
const content = extractStringContent(node);
|
|
183
|
+
if (content === void 0) return null;
|
|
184
|
+
return {
|
|
185
|
+
type: "text",
|
|
186
|
+
text: content
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
if (node.type === "conditional_expression") return resolveConditional(node, ctx);
|
|
190
|
+
if (node.type === "binary_operator") return resolveStaticBinaryOperator(node, ctx);
|
|
191
|
+
if (node.type === "call") {
|
|
192
|
+
const funcNode = node.childForFieldName("function");
|
|
193
|
+
if (funcNode && funcNode.type === "identifier") {
|
|
194
|
+
if (getOriginalImportName(funcNode.text, ctx.imports) === "declare_var") return resolveDeclareVarArg(node, ctx);
|
|
195
|
+
}
|
|
196
|
+
return resolveFunctionCall(node, ctx);
|
|
197
|
+
}
|
|
198
|
+
if (node.type === "identifier") {
|
|
199
|
+
const result = await resolveIdentifier(node, ctx);
|
|
200
|
+
if (result) return result;
|
|
201
|
+
ctx.errors.push(`${locationStr(node)}: could not resolve identifier "${node.text}" to a static value`);
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
if (node.type === "subscript") return resolveSubscript(node, ctx);
|
|
205
|
+
if (node.type === "attribute") return resolveAttribute(node, ctx);
|
|
206
|
+
ctx.errors.push(`${locationStr(node)}: unsupported derive() argument type "${node.type}"`);
|
|
207
|
+
return null;
|
|
263
208
|
}
|
|
264
209
|
/**
|
|
265
|
-
|
|
266
|
-
|
|
210
|
+
* Handles binary + concatenation within a static value context.
|
|
211
|
+
*/
|
|
267
212
|
async function resolveStaticBinaryOperator(node, ctx) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
if (rightNode.type === 'sequence') {
|
|
293
|
-
parts.push(...rightNode.nodes);
|
|
294
|
-
}
|
|
295
|
-
else {
|
|
296
|
-
parts.push(rightNode);
|
|
297
|
-
}
|
|
298
|
-
return { type: 'sequence', nodes: parts };
|
|
213
|
+
const left = node.childForFieldName("left");
|
|
214
|
+
const right = node.childForFieldName("right");
|
|
215
|
+
if (!left || !right) {
|
|
216
|
+
ctx.errors.push(`${locationStr(node)}: binary operator missing operands`);
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
const operator = node.childForFieldName("operator");
|
|
220
|
+
if (operator && operator.text !== "+") {
|
|
221
|
+
ctx.errors.push(`${locationStr(node)}: unsupported binary operator "${operator.text}" in static expression`);
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
const leftNode = await resolveStaticValue(left, ctx);
|
|
225
|
+
const rightNode = await resolveStaticValue(right, ctx);
|
|
226
|
+
if (!leftNode || !rightNode) return null;
|
|
227
|
+
const parts = [];
|
|
228
|
+
if (leftNode.type === "sequence") parts.push(...leftNode.nodes);
|
|
229
|
+
else parts.push(leftNode);
|
|
230
|
+
if (rightNode.type === "sequence") parts.push(...rightNode.nodes);
|
|
231
|
+
else parts.push(rightNode);
|
|
232
|
+
return {
|
|
233
|
+
type: "sequence",
|
|
234
|
+
nodes: parts
|
|
235
|
+
};
|
|
299
236
|
}
|
|
300
237
|
/**
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
238
|
+
* Resolves a Python conditional expression (ternary):
|
|
239
|
+
* "day" if cond else "night"
|
|
240
|
+
* tree-sitter: conditional_expression → [consequent, if, condition, else, alternate]
|
|
241
|
+
*/
|
|
305
242
|
async function resolveConditional(node, ctx) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
}
|
|
337
|
-
if (!consequent || !alternate) {
|
|
338
|
-
ctx.errors.push(`${locationStr(node)}: could not parse conditional expression`);
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
// Recursively resolve both branches (handles nested ternaries)
|
|
342
|
-
const consequentNode = await resolveStaticValue(consequent, ctx);
|
|
343
|
-
const alternateNode = await resolveStaticValue(alternate, ctx);
|
|
344
|
-
if (!consequentNode || !alternateNode)
|
|
345
|
-
return null;
|
|
346
|
-
// Flatten choices
|
|
347
|
-
const branches = [];
|
|
348
|
-
if (consequentNode.type === 'choice') {
|
|
349
|
-
branches.push(...consequentNode.nodes);
|
|
350
|
-
}
|
|
351
|
-
else {
|
|
352
|
-
branches.push(consequentNode);
|
|
353
|
-
}
|
|
354
|
-
if (alternateNode.type === 'choice') {
|
|
355
|
-
branches.push(...alternateNode.nodes);
|
|
356
|
-
}
|
|
357
|
-
else {
|
|
358
|
-
branches.push(alternateNode);
|
|
359
|
-
}
|
|
360
|
-
return { type: 'choice', nodes: branches };
|
|
243
|
+
let consequent = null;
|
|
244
|
+
let alternate = null;
|
|
245
|
+
let seenElse = false;
|
|
246
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
247
|
+
const child = node.child(i);
|
|
248
|
+
if (!child) continue;
|
|
249
|
+
if (child.type === "if") continue;
|
|
250
|
+
if (child.type === "else") {
|
|
251
|
+
seenElse = true;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (!seenElse && !consequent) consequent = child;
|
|
255
|
+
else if (seenElse && !alternate) alternate = child;
|
|
256
|
+
}
|
|
257
|
+
if (!consequent || !alternate) {
|
|
258
|
+
ctx.errors.push(`${locationStr(node)}: could not parse conditional expression`);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
const consequentNode = await resolveStaticValue(consequent, ctx);
|
|
262
|
+
const alternateNode = await resolveStaticValue(alternate, ctx);
|
|
263
|
+
if (!consequentNode || !alternateNode) return null;
|
|
264
|
+
const branches = [];
|
|
265
|
+
if (consequentNode.type === "choice") branches.push(...consequentNode.nodes);
|
|
266
|
+
else branches.push(consequentNode);
|
|
267
|
+
if (alternateNode.type === "choice") branches.push(...alternateNode.nodes);
|
|
268
|
+
else branches.push(alternateNode);
|
|
269
|
+
return {
|
|
270
|
+
type: "choice",
|
|
271
|
+
nodes: branches
|
|
272
|
+
};
|
|
361
273
|
}
|
|
362
274
|
/**
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
275
|
+
* Resolves a function call to its string return variants.
|
|
276
|
+
* Looks up the function locally, then in imported files.
|
|
277
|
+
*/
|
|
366
278
|
async function resolveFunctionCall(callNode, ctx) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const importInfo = findImportForName(funcName, ctx);
|
|
391
|
-
if (importInfo) {
|
|
392
|
-
const result = await resolveFunctionInFile(importInfo.originalName, importInfo.filePath, exprParser);
|
|
393
|
-
if (result)
|
|
394
|
-
return result;
|
|
395
|
-
}
|
|
396
|
-
ctx.errors.push(`${locationStr(callNode)}: could not resolve function "${funcName}" to string return values`);
|
|
397
|
-
return null;
|
|
279
|
+
const funcNode = callNode.childForFieldName("function");
|
|
280
|
+
if (!funcNode || funcNode.type !== "identifier") {
|
|
281
|
+
ctx.errors.push(`${locationStr(callNode)}: cannot resolve non-identifier function call`);
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
const funcName = funcNode.text;
|
|
285
|
+
const exprParser = (node, targetRootNode, targetFilePath) => {
|
|
286
|
+
return resolveStaticValue(node, {
|
|
287
|
+
rootNode: targetRootNode,
|
|
288
|
+
imports: extractImportsFromRoot(targetRootNode, ctx.imports),
|
|
289
|
+
filePath: targetFilePath,
|
|
290
|
+
errors: ctx.errors
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
const localResult = await resolveFunctionInCurrentFile(funcName, ctx.rootNode, ctx.filePath, exprParser);
|
|
294
|
+
if (localResult) return localResult;
|
|
295
|
+
const importInfo = findImportForName(funcName, ctx);
|
|
296
|
+
if (importInfo) {
|
|
297
|
+
const result = await resolveFunctionInFile(importInfo.originalName, importInfo.filePath, exprParser);
|
|
298
|
+
if (result) return result;
|
|
299
|
+
}
|
|
300
|
+
ctx.errors.push(`${locationStr(callNode)}: could not resolve function "${funcName}" to string return values`);
|
|
301
|
+
return null;
|
|
398
302
|
}
|
|
399
303
|
/**
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
304
|
+
* Extracts GT import aliases from a target file's root node.
|
|
305
|
+
* Merges with parent imports for GT package functions (declare_var, etc.)
|
|
306
|
+
* that may not be imported in the target file.
|
|
307
|
+
*/
|
|
404
308
|
function extractImportsFromRoot(rootNode, parentImports) {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
imp.originalName === PYTHON_DECLARE_VAR);
|
|
412
|
-
// Deduplicate: prefer the target file's own imports over parent's
|
|
413
|
-
const seen = new Set(fileImports.map((imp) => imp.localName));
|
|
414
|
-
const merged = [...fileImports];
|
|
415
|
-
for (const imp of parentDeclareImports) {
|
|
416
|
-
if (!seen.has(imp.localName)) {
|
|
417
|
-
merged.push(imp);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
return merged;
|
|
309
|
+
const fileImports = extractImports(rootNode);
|
|
310
|
+
const parentDeclareImports = parentImports.filter((imp) => isDeriveFunction(imp.originalName) || imp.originalName === "declare_var");
|
|
311
|
+
const seen = new Set(fileImports.map((imp) => imp.localName));
|
|
312
|
+
const merged = [...fileImports];
|
|
313
|
+
for (const imp of parentDeclareImports) if (!seen.has(imp.localName)) merged.push(imp);
|
|
314
|
+
return merged;
|
|
421
315
|
}
|
|
422
316
|
/**
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
317
|
+
* Resolves the argument of a declare_var() call.
|
|
318
|
+
* Produces ICU placeholder text using declareVar from generaltranslation.
|
|
319
|
+
*/
|
|
426
320
|
async function resolveDeclareVarArg(callNode, ctx) {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
// Use declareVar with empty string for the runtime variable value
|
|
455
|
-
const options = nameOption ? { $name: nameOption } : undefined;
|
|
456
|
-
const icuText = declareVar('', options);
|
|
457
|
-
return { type: 'text', text: icuText };
|
|
321
|
+
const argsNode = callNode.childForFieldName("arguments");
|
|
322
|
+
if (!argsNode) {
|
|
323
|
+
ctx.errors.push(`${locationStr(callNode)}: declare_var() requires arguments`);
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
if (!getFirstPositionalArg(callNode)) {
|
|
327
|
+
ctx.errors.push(`${locationStr(callNode)}: declare_var() requires a variable argument`);
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
let nameOption;
|
|
331
|
+
for (let i = 0; i < argsNode.childCount; i++) {
|
|
332
|
+
const child = argsNode.child(i);
|
|
333
|
+
if (!child || child.type !== "keyword_argument") continue;
|
|
334
|
+
const nameNode = child.childForFieldName("name");
|
|
335
|
+
const valueNode = child.childForFieldName("value");
|
|
336
|
+
if (!nameNode || !valueNode) continue;
|
|
337
|
+
if (nameNode.text === "_name") {
|
|
338
|
+
if (valueNode.type === "string" && !isFString(valueNode)) nameOption = extractStringContent(valueNode);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
type: "text",
|
|
343
|
+
text: declareVar("", nameOption ? { $name: nameOption } : void 0)
|
|
344
|
+
};
|
|
458
345
|
}
|
|
459
|
-
// ===== Constant / Dictionary Resolution ===== //
|
|
460
346
|
/**
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
347
|
+
* Finds a top-level assignment `name = <value>` in the given root node.
|
|
348
|
+
* Returns the right-hand side (value) node, or null if not found.
|
|
349
|
+
*/
|
|
464
350
|
function findConstantAssignment(name, rootNode) {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
return right;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
return null;
|
|
351
|
+
for (let i = 0; i < rootNode.childCount; i++) {
|
|
352
|
+
const child = rootNode.child(i);
|
|
353
|
+
if (!child || child.type !== "expression_statement") continue;
|
|
354
|
+
const expr = child.child(0);
|
|
355
|
+
if (!expr || expr.type !== "assignment") continue;
|
|
356
|
+
const left = expr.childForFieldName("left");
|
|
357
|
+
const right = expr.childForFieldName("right");
|
|
358
|
+
if ((left === null || left === void 0 ? void 0 : left.type) === "identifier" && left.text === name && right) return right;
|
|
359
|
+
}
|
|
360
|
+
return null;
|
|
479
361
|
}
|
|
480
362
|
/**
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
const resolvingIdentifiers = new Set();
|
|
363
|
+
* Guard against infinite recursion when resolving identifier chains.
|
|
364
|
+
* Tracks variable names currently being resolved to detect circular references.
|
|
365
|
+
*/
|
|
366
|
+
const resolvingIdentifiers = /* @__PURE__ */ new Set();
|
|
485
367
|
/**
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
368
|
+
* Resolves an identifier to its static value by finding the assignment
|
|
369
|
+
* in the current file or cross-file via imports.
|
|
370
|
+
*/
|
|
489
371
|
async function resolveIdentifier(node, ctx) {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
imports: externalImports,
|
|
523
|
-
filePath: importInfo.filePath,
|
|
524
|
-
errors: ctx.errors,
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
return null;
|
|
529
|
-
}
|
|
530
|
-
finally {
|
|
531
|
-
resolvingIdentifiers.delete(guardKey);
|
|
532
|
-
}
|
|
372
|
+
const name = node.text;
|
|
373
|
+
const guardKey = `${ctx.filePath}::${name}`;
|
|
374
|
+
if (resolvingIdentifiers.has(guardKey)) return null;
|
|
375
|
+
resolvingIdentifiers.add(guardKey);
|
|
376
|
+
try {
|
|
377
|
+
const localValue = findConstantAssignment(name, ctx.rootNode);
|
|
378
|
+
if (localValue) return await resolveStaticValue(localValue, ctx);
|
|
379
|
+
const importInfo = findImportForName(name, ctx);
|
|
380
|
+
if (importInfo) {
|
|
381
|
+
let source;
|
|
382
|
+
try {
|
|
383
|
+
source = fs.readFileSync(importInfo.filePath, "utf8");
|
|
384
|
+
} catch {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
const tree = (await getParser()).parse(source);
|
|
388
|
+
if (!tree) return null;
|
|
389
|
+
const externalValue = findConstantAssignment(importInfo.originalName, tree.rootNode);
|
|
390
|
+
if (externalValue) {
|
|
391
|
+
const externalImports = extractImportsFromRoot(tree.rootNode, ctx.imports);
|
|
392
|
+
return await resolveStaticValue(externalValue, {
|
|
393
|
+
rootNode: tree.rootNode,
|
|
394
|
+
imports: externalImports,
|
|
395
|
+
filePath: importInfo.filePath,
|
|
396
|
+
errors: ctx.errors
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return null;
|
|
401
|
+
} finally {
|
|
402
|
+
resolvingIdentifiers.delete(guardKey);
|
|
403
|
+
}
|
|
533
404
|
}
|
|
534
405
|
/**
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
406
|
+
* Finds a dictionary assignment and returns the dictionary node.
|
|
407
|
+
* Searches locally first, then cross-file via imports.
|
|
408
|
+
* Returns the dictionary node and the context (rootNode, filePath, imports)
|
|
409
|
+
* for resolving values within it.
|
|
410
|
+
*/
|
|
540
411
|
async function findDictionaryAssignment(name, ctx) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
errors: ctx.errors,
|
|
572
|
-
},
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
return null;
|
|
412
|
+
const localValue = findConstantAssignment(name, ctx.rootNode);
|
|
413
|
+
if (localValue && (localValue.type === "dictionary" || localValue.type === "list")) return {
|
|
414
|
+
dictNode: localValue,
|
|
415
|
+
valueCtx: ctx
|
|
416
|
+
};
|
|
417
|
+
const importInfo = findImportForName(name, ctx);
|
|
418
|
+
if (importInfo) {
|
|
419
|
+
let source;
|
|
420
|
+
try {
|
|
421
|
+
source = fs.readFileSync(importInfo.filePath, "utf8");
|
|
422
|
+
} catch {
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
const tree = (await getParser()).parse(source);
|
|
426
|
+
if (!tree) return null;
|
|
427
|
+
const externalValue = findConstantAssignment(importInfo.originalName, tree.rootNode);
|
|
428
|
+
if (externalValue && (externalValue.type === "dictionary" || externalValue.type === "list")) {
|
|
429
|
+
const externalImports = extractImportsFromRoot(tree.rootNode, ctx.imports);
|
|
430
|
+
return {
|
|
431
|
+
dictNode: externalValue,
|
|
432
|
+
valueCtx: {
|
|
433
|
+
rootNode: tree.rootNode,
|
|
434
|
+
imports: externalImports,
|
|
435
|
+
filePath: importInfo.filePath,
|
|
436
|
+
errors: ctx.errors
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return null;
|
|
577
442
|
}
|
|
578
443
|
/**
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
444
|
+
* Collects all key-value entries from a dictionary node,
|
|
445
|
+
* including entries from spread sources (**base).
|
|
446
|
+
*/
|
|
582
447
|
async function collectDictEntries(dictNode, ctx) {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
if (externalValue && externalValue.type === 'dictionary') {
|
|
642
|
-
const externalImports = extractImportsFromRoot(tree.rootNode, ctx.imports);
|
|
643
|
-
const externalCtx = {
|
|
644
|
-
rootNode: tree.rootNode,
|
|
645
|
-
imports: externalImports,
|
|
646
|
-
filePath: importInfo.filePath,
|
|
647
|
-
errors: ctx.errors,
|
|
648
|
-
};
|
|
649
|
-
entries.push(...(await collectDictEntries(externalValue, externalCtx)));
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
return entries;
|
|
448
|
+
const entries = [];
|
|
449
|
+
for (let i = 0; i < dictNode.childCount; i++) {
|
|
450
|
+
const child = dictNode.child(i);
|
|
451
|
+
if (!child) continue;
|
|
452
|
+
if (child.type === "pair") {
|
|
453
|
+
const keyNode = child.childForFieldName("key");
|
|
454
|
+
const valueNode = child.childForFieldName("value");
|
|
455
|
+
if (!valueNode) continue;
|
|
456
|
+
let key = null;
|
|
457
|
+
if (keyNode) {
|
|
458
|
+
if (keyNode.type === "string" && !isFString(keyNode)) key = extractStringContent(keyNode) ?? null;
|
|
459
|
+
else if (keyNode.type === "identifier") key = keyNode.text;
|
|
460
|
+
else if (keyNode.type === "integer") key = keyNode.text;
|
|
461
|
+
}
|
|
462
|
+
entries.push({
|
|
463
|
+
key,
|
|
464
|
+
valueNode
|
|
465
|
+
});
|
|
466
|
+
} else if (child.type === "dictionary_splat") {
|
|
467
|
+
let splatExpr = null;
|
|
468
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
469
|
+
const splatChild = child.child(j);
|
|
470
|
+
if (splatChild && splatChild.type !== "**") {
|
|
471
|
+
splatExpr = splatChild;
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (!splatExpr || splatExpr.type !== "identifier") continue;
|
|
476
|
+
const name = splatExpr.text;
|
|
477
|
+
const localDict = findConstantAssignment(name, ctx.rootNode);
|
|
478
|
+
if (localDict && localDict.type === "dictionary") entries.push(...await collectDictEntries(localDict, ctx));
|
|
479
|
+
else {
|
|
480
|
+
const importInfo = findImportForName(name, ctx);
|
|
481
|
+
if (importInfo) {
|
|
482
|
+
let source;
|
|
483
|
+
try {
|
|
484
|
+
source = fs.readFileSync(importInfo.filePath, "utf8");
|
|
485
|
+
} catch {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
const tree = (await getParser()).parse(source);
|
|
489
|
+
if (!tree) continue;
|
|
490
|
+
const externalValue = findConstantAssignment(importInfo.originalName, tree.rootNode);
|
|
491
|
+
if (externalValue && externalValue.type === "dictionary") {
|
|
492
|
+
const externalImports = extractImportsFromRoot(tree.rootNode, ctx.imports);
|
|
493
|
+
const externalCtx = {
|
|
494
|
+
rootNode: tree.rootNode,
|
|
495
|
+
imports: externalImports,
|
|
496
|
+
filePath: importInfo.filePath,
|
|
497
|
+
errors: ctx.errors
|
|
498
|
+
};
|
|
499
|
+
entries.push(...await collectDictEntries(externalValue, externalCtx));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return entries;
|
|
656
506
|
}
|
|
657
507
|
/**
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
508
|
+
* Collects all elements from a list node as DictEntry[] with index as key.
|
|
509
|
+
* Handles list_splat (*spread).
|
|
510
|
+
*/
|
|
661
511
|
async function collectListEntries(listNode, ctx) {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
continue;
|
|
722
|
-
}
|
|
723
|
-
// Regular element — any expression
|
|
724
|
-
entries.push({ key: String(index), valueNode: child });
|
|
725
|
-
index++;
|
|
726
|
-
}
|
|
727
|
-
return entries;
|
|
512
|
+
const entries = [];
|
|
513
|
+
let index = 0;
|
|
514
|
+
for (let i = 0; i < listNode.childCount; i++) {
|
|
515
|
+
const child = listNode.child(i);
|
|
516
|
+
if (!child) continue;
|
|
517
|
+
if (child.type === "[" || child.type === "]" || child.type === ",") continue;
|
|
518
|
+
if (child.type === "list_splat") {
|
|
519
|
+
let splatExpr = null;
|
|
520
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
521
|
+
const sc = child.child(j);
|
|
522
|
+
if (sc && sc.type !== "*") {
|
|
523
|
+
splatExpr = sc;
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (!splatExpr || splatExpr.type !== "identifier") continue;
|
|
528
|
+
const localList = findConstantAssignment(splatExpr.text, ctx.rootNode);
|
|
529
|
+
if (localList && localList.type === "list") {
|
|
530
|
+
const spreadEntries = await collectListEntries(localList, ctx);
|
|
531
|
+
for (const e of spreadEntries) entries.push({
|
|
532
|
+
key: String(index++),
|
|
533
|
+
valueNode: e.valueNode
|
|
534
|
+
});
|
|
535
|
+
} else {
|
|
536
|
+
const importInfo = findImportForName(splatExpr.text, ctx);
|
|
537
|
+
if (importInfo) {
|
|
538
|
+
let source;
|
|
539
|
+
try {
|
|
540
|
+
source = fs.readFileSync(importInfo.filePath, "utf8");
|
|
541
|
+
} catch {
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
const tree = (await getParser()).parse(source);
|
|
545
|
+
if (!tree) continue;
|
|
546
|
+
const externalValue = findConstantAssignment(importInfo.originalName, tree.rootNode);
|
|
547
|
+
if (externalValue && externalValue.type === "list") {
|
|
548
|
+
const externalImports = extractImportsFromRoot(tree.rootNode, ctx.imports);
|
|
549
|
+
const spreadEntries = await collectListEntries(externalValue, {
|
|
550
|
+
rootNode: tree.rootNode,
|
|
551
|
+
imports: externalImports,
|
|
552
|
+
filePath: importInfo.filePath,
|
|
553
|
+
errors: ctx.errors
|
|
554
|
+
});
|
|
555
|
+
for (const e of spreadEntries) entries.push({
|
|
556
|
+
key: String(index++),
|
|
557
|
+
valueNode: e.valueNode
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
entries.push({
|
|
565
|
+
key: String(index),
|
|
566
|
+
valueNode: child
|
|
567
|
+
});
|
|
568
|
+
index++;
|
|
569
|
+
}
|
|
570
|
+
return entries;
|
|
728
571
|
}
|
|
729
572
|
/**
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
573
|
+
* Resolves an expression to dictionary AST node(s).
|
|
574
|
+
* Handles identifier, subscript chains, and attribute chains.
|
|
575
|
+
*/
|
|
733
576
|
async function resolveToDictNodes(node, ctx) {
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
entry.valueNode.type === 'list') {
|
|
783
|
-
results.push({
|
|
784
|
-
dictNode: entry.valueNode,
|
|
785
|
-
valueCtx: parent.valueCtx,
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
return results;
|
|
792
|
-
}
|
|
793
|
-
// Case 3: attribute (e.g., D.a in D.a.x)
|
|
794
|
-
if (node.type === 'attribute') {
|
|
795
|
-
const objectNode = node.childForFieldName('object');
|
|
796
|
-
const attrNode = node.childForFieldName('attribute');
|
|
797
|
-
if (!objectNode || !attrNode)
|
|
798
|
-
return [];
|
|
799
|
-
const parentDicts = await resolveToDictNodes(objectNode, ctx);
|
|
800
|
-
if (parentDicts.length === 0)
|
|
801
|
-
return [];
|
|
802
|
-
const attrName = attrNode.text;
|
|
803
|
-
const results = [];
|
|
804
|
-
for (const parent of parentDicts) {
|
|
805
|
-
const entries = await collectDictEntries(parent.dictNode, parent.valueCtx);
|
|
806
|
-
for (const entry of entries) {
|
|
807
|
-
if (entry.key === attrName &&
|
|
808
|
-
(entry.valueNode.type === 'dictionary' ||
|
|
809
|
-
entry.valueNode.type === 'list')) {
|
|
810
|
-
results.push({
|
|
811
|
-
dictNode: entry.valueNode,
|
|
812
|
-
valueCtx: parent.valueCtx,
|
|
813
|
-
});
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
return results;
|
|
818
|
-
}
|
|
819
|
-
return [];
|
|
577
|
+
if (node.type === "identifier") {
|
|
578
|
+
const result = await findDictionaryAssignment(node.text, ctx);
|
|
579
|
+
if (result) return [result];
|
|
580
|
+
return [];
|
|
581
|
+
}
|
|
582
|
+
if (node.type === "subscript") {
|
|
583
|
+
const valueNode = node.childForFieldName("value");
|
|
584
|
+
if (!valueNode) return [];
|
|
585
|
+
const parentDicts = await resolveToDictNodes(valueNode, ctx);
|
|
586
|
+
if (parentDicts.length === 0) return [];
|
|
587
|
+
const subscriptKey = node.childForFieldName("subscript");
|
|
588
|
+
if (!subscriptKey) return [];
|
|
589
|
+
const staticKeyValue = subscriptKey.type === "string" && !isFString(subscriptKey) ? extractStringContent(subscriptKey) : null;
|
|
590
|
+
const staticIntKeyValue = subscriptKey.type === "integer" ? subscriptKey.text : null;
|
|
591
|
+
const results = [];
|
|
592
|
+
for (const parent of parentDicts) {
|
|
593
|
+
const entries = parent.dictNode.type === "list" ? await collectListEntries(parent.dictNode, parent.valueCtx) : await collectDictEntries(parent.dictNode, parent.valueCtx);
|
|
594
|
+
if (staticKeyValue != null || staticIntKeyValue != null) {
|
|
595
|
+
const keyToMatch = staticKeyValue ?? staticIntKeyValue;
|
|
596
|
+
for (const entry of entries) if (entry.key === keyToMatch && (entry.valueNode.type === "dictionary" || entry.valueNode.type === "list")) results.push({
|
|
597
|
+
dictNode: entry.valueNode,
|
|
598
|
+
valueCtx: parent.valueCtx
|
|
599
|
+
});
|
|
600
|
+
} else for (const entry of entries) if (entry.valueNode.type === "dictionary" || entry.valueNode.type === "list") results.push({
|
|
601
|
+
dictNode: entry.valueNode,
|
|
602
|
+
valueCtx: parent.valueCtx
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
return results;
|
|
606
|
+
}
|
|
607
|
+
if (node.type === "attribute") {
|
|
608
|
+
const objectNode = node.childForFieldName("object");
|
|
609
|
+
const attrNode = node.childForFieldName("attribute");
|
|
610
|
+
if (!objectNode || !attrNode) return [];
|
|
611
|
+
const parentDicts = await resolveToDictNodes(objectNode, ctx);
|
|
612
|
+
if (parentDicts.length === 0) return [];
|
|
613
|
+
const attrName = attrNode.text;
|
|
614
|
+
const results = [];
|
|
615
|
+
for (const parent of parentDicts) {
|
|
616
|
+
const entries = await collectDictEntries(parent.dictNode, parent.valueCtx);
|
|
617
|
+
for (const entry of entries) if (entry.key === attrName && (entry.valueNode.type === "dictionary" || entry.valueNode.type === "list")) results.push({
|
|
618
|
+
dictNode: entry.valueNode,
|
|
619
|
+
valueCtx: parent.valueCtx
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
return results;
|
|
623
|
+
}
|
|
624
|
+
return [];
|
|
820
625
|
}
|
|
821
626
|
/**
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
627
|
+
* Resolves a subscript expression (e.g., `LABELS[score]` or `D["a"]["x"]`)
|
|
628
|
+
* by extracting values from the resolved dictionary.
|
|
629
|
+
* Supports nested access chains and spread resolution.
|
|
630
|
+
*/
|
|
826
631
|
async function resolveSubscript(node, ctx) {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
else {
|
|
868
|
-
// Dynamic key: extract ALL values
|
|
869
|
-
for (const entry of entries) {
|
|
870
|
-
const resolved = await resolveStaticValue(entry.valueNode, valueCtx);
|
|
871
|
-
if (resolved) {
|
|
872
|
-
if (resolved.type === 'choice') {
|
|
873
|
-
branches.push(...resolved.nodes);
|
|
874
|
-
}
|
|
875
|
-
else {
|
|
876
|
-
branches.push(resolved);
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
if (branches.length === 0) {
|
|
883
|
-
ctx.errors.push(`${locationStr(node)}: collection has no resolvable values`);
|
|
884
|
-
return null;
|
|
885
|
-
}
|
|
886
|
-
if (branches.length === 1)
|
|
887
|
-
return branches[0];
|
|
888
|
-
return { type: 'choice', nodes: branches };
|
|
632
|
+
const valueNode = node.childForFieldName("value");
|
|
633
|
+
if (!valueNode) {
|
|
634
|
+
ctx.errors.push(`${locationStr(node)}: subscript missing value`);
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
const dicts = await resolveToDictNodes(valueNode, ctx);
|
|
638
|
+
if (dicts.length === 0) {
|
|
639
|
+
ctx.errors.push(`${locationStr(node)}: could not find dictionary or list for "${valueNode.text}"`);
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
const subscriptKey = node.childForFieldName("subscript");
|
|
643
|
+
const staticStringKeyValue = (subscriptKey === null || subscriptKey === void 0 ? void 0 : subscriptKey.type) === "string" && !isFString(subscriptKey) ? extractStringContent(subscriptKey) : null;
|
|
644
|
+
const staticIntKeyValue = (subscriptKey === null || subscriptKey === void 0 ? void 0 : subscriptKey.type) === "integer" ? subscriptKey.text : null;
|
|
645
|
+
const staticKeyValue = staticStringKeyValue ?? staticIntKeyValue;
|
|
646
|
+
const branches = [];
|
|
647
|
+
for (const { dictNode, valueCtx } of dicts) {
|
|
648
|
+
const entries = dictNode.type === "list" ? await collectListEntries(dictNode, valueCtx) : await collectDictEntries(dictNode, valueCtx);
|
|
649
|
+
if (staticKeyValue != null) {
|
|
650
|
+
for (const entry of entries) if (entry.key === staticKeyValue) {
|
|
651
|
+
const resolved = await resolveStaticValue(entry.valueNode, valueCtx);
|
|
652
|
+
if (resolved) if (resolved.type === "choice") branches.push(...resolved.nodes);
|
|
653
|
+
else branches.push(resolved);
|
|
654
|
+
}
|
|
655
|
+
} else for (const entry of entries) {
|
|
656
|
+
const resolved = await resolveStaticValue(entry.valueNode, valueCtx);
|
|
657
|
+
if (resolved) if (resolved.type === "choice") branches.push(...resolved.nodes);
|
|
658
|
+
else branches.push(resolved);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if (branches.length === 0) {
|
|
662
|
+
ctx.errors.push(`${locationStr(node)}: collection has no resolvable values`);
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
if (branches.length === 1) return branches[0];
|
|
666
|
+
return {
|
|
667
|
+
type: "choice",
|
|
668
|
+
nodes: branches
|
|
669
|
+
};
|
|
889
670
|
}
|
|
890
671
|
/**
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
672
|
+
* Resolves an attribute access expression (e.g., `obj.attr` or `obj.a.b`)
|
|
673
|
+
* by finding the specific dictionary pair with a matching key.
|
|
674
|
+
* Supports nested access chains and spread resolution.
|
|
675
|
+
*/
|
|
895
676
|
async function resolveAttribute(node, ctx) {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
if (branches.length === 0) {
|
|
927
|
-
ctx.errors.push(`${locationStr(node)}: could not find key "${attrName}" in dictionary or list`);
|
|
928
|
-
return null;
|
|
929
|
-
}
|
|
930
|
-
if (branches.length === 1)
|
|
931
|
-
return branches[0];
|
|
932
|
-
return { type: 'choice', nodes: branches };
|
|
677
|
+
const objectNode = node.childForFieldName("object");
|
|
678
|
+
const attrNode = node.childForFieldName("attribute");
|
|
679
|
+
if (!objectNode || !attrNode) {
|
|
680
|
+
ctx.errors.push(`${locationStr(node)}: attribute access missing object or attribute`);
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
const attrName = attrNode.text;
|
|
684
|
+
const dicts = await resolveToDictNodes(objectNode, ctx);
|
|
685
|
+
if (dicts.length === 0) {
|
|
686
|
+
ctx.errors.push(`${locationStr(node)}: could not find dictionary or list for "${objectNode.text}"`);
|
|
687
|
+
return null;
|
|
688
|
+
}
|
|
689
|
+
const branches = [];
|
|
690
|
+
for (const { dictNode, valueCtx } of dicts) {
|
|
691
|
+
const entries = await collectDictEntries(dictNode, valueCtx);
|
|
692
|
+
for (const entry of entries) if (entry.key === attrName) {
|
|
693
|
+
const resolved = await resolveStaticValue(entry.valueNode, valueCtx);
|
|
694
|
+
if (resolved) if (resolved.type === "choice") branches.push(...resolved.nodes);
|
|
695
|
+
else branches.push(resolved);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (branches.length === 0) {
|
|
699
|
+
ctx.errors.push(`${locationStr(node)}: could not find key "${attrName}" in dictionary or list`);
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
if (branches.length === 1) return branches[0];
|
|
703
|
+
return {
|
|
704
|
+
type: "choice",
|
|
705
|
+
nodes: branches
|
|
706
|
+
};
|
|
933
707
|
}
|
|
934
|
-
// ===== Helpers ===== //
|
|
935
708
|
function getFirstPositionalArg(callNode) {
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
child.type !== ')' &&
|
|
944
|
-
child.type !== ',' &&
|
|
945
|
-
child.type !== 'keyword_argument') {
|
|
946
|
-
return child;
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
return null;
|
|
709
|
+
const argsNode = callNode.childForFieldName("arguments");
|
|
710
|
+
if (!argsNode) return null;
|
|
711
|
+
for (let i = 0; i < argsNode.childCount; i++) {
|
|
712
|
+
const child = argsNode.child(i);
|
|
713
|
+
if (child && child.type !== "(" && child.type !== ")" && child.type !== "," && child.type !== "keyword_argument") return child;
|
|
714
|
+
}
|
|
715
|
+
return null;
|
|
950
716
|
}
|
|
951
717
|
function getOriginalImportName(localName, imports) {
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
return imp.originalName;
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
return null;
|
|
718
|
+
for (const imp of imports) if (imp.localName === localName) return imp.originalName;
|
|
719
|
+
return null;
|
|
958
720
|
}
|
|
959
721
|
function getDeriveImportNames(imports) {
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
imp.originalName === PYTHON_DECLARE_VAR) {
|
|
964
|
-
names.add(imp.localName);
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
return names;
|
|
722
|
+
const names = /* @__PURE__ */ new Set();
|
|
723
|
+
for (const imp of imports) if (isDeriveFunction(imp.originalName) || imp.originalName === "declare_var") names.add(imp.localName);
|
|
724
|
+
return names;
|
|
968
725
|
}
|
|
969
726
|
/**
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
727
|
+
* Finds import info for a given local name (for cross-file function resolution).
|
|
728
|
+
* Only looks at non-GT imports (user function imports).
|
|
729
|
+
*/
|
|
973
730
|
function findImportForName(localName, ctx) {
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
return null;
|
|
731
|
+
for (let i = 0; i < ctx.rootNode.childCount; i++) {
|
|
732
|
+
const node = ctx.rootNode.child(i);
|
|
733
|
+
if (!node || node.type !== "import_from_statement") continue;
|
|
734
|
+
const moduleName = getModuleName(node);
|
|
735
|
+
if (!moduleName) continue;
|
|
736
|
+
for (let j = 0; j < node.childCount; j++) {
|
|
737
|
+
const child = node.child(j);
|
|
738
|
+
if (!child) continue;
|
|
739
|
+
if (child.type === "aliased_import") {
|
|
740
|
+
const nameNode = child.childForFieldName("name");
|
|
741
|
+
const aliasNode = child.childForFieldName("alias");
|
|
742
|
+
const importedName = nameNode === null || nameNode === void 0 ? void 0 : nameNode.text;
|
|
743
|
+
if (((aliasNode === null || aliasNode === void 0 ? void 0 : aliasNode.text) ?? importedName) === localName && importedName) {
|
|
744
|
+
const filePath = resolveImportPath(moduleName, ctx.filePath);
|
|
745
|
+
if (filePath) return {
|
|
746
|
+
originalName: importedName,
|
|
747
|
+
filePath
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
} else if (child.type === "dotted_name") {
|
|
751
|
+
if (child.text === moduleName) continue;
|
|
752
|
+
if (child.text === localName) {
|
|
753
|
+
const filePath = resolveImportPath(moduleName, ctx.filePath);
|
|
754
|
+
if (filePath) return {
|
|
755
|
+
originalName: localName,
|
|
756
|
+
filePath
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return null;
|
|
1012
763
|
}
|
|
1013
764
|
function getModuleName(importNode) {
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
if (child.type === 'dotted_name')
|
|
1024
|
-
return child.text;
|
|
1025
|
-
if (child.type === 'relative_import')
|
|
1026
|
-
return child.text;
|
|
1027
|
-
}
|
|
1028
|
-
return undefined;
|
|
765
|
+
const moduleNode = importNode.childForFieldName("module_name");
|
|
766
|
+
if (moduleNode) return moduleNode.text;
|
|
767
|
+
for (let i = 0; i < importNode.childCount; i++) {
|
|
768
|
+
const child = importNode.child(i);
|
|
769
|
+
if (!child) continue;
|
|
770
|
+
if (child.type === "import") break;
|
|
771
|
+
if (child.type === "dotted_name") return child.text;
|
|
772
|
+
if (child.type === "relative_import") return child.text;
|
|
773
|
+
}
|
|
1029
774
|
}
|
|
1030
775
|
function isFString(stringNode) {
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
return true;
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
return false;
|
|
776
|
+
for (let i = 0; i < stringNode.childCount; i++) {
|
|
777
|
+
const child = stringNode.child(i);
|
|
778
|
+
if (child && child.type === "string_start") return /^[fF]/.test(child.text);
|
|
779
|
+
if (child && child.type === "interpolation") return true;
|
|
780
|
+
}
|
|
781
|
+
return false;
|
|
1041
782
|
}
|
|
1042
783
|
function extractStringContent(stringNode) {
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
if (child?.type === 'string_end')
|
|
1056
|
-
hasEnd = true;
|
|
1057
|
-
}
|
|
1058
|
-
if (hasStart && hasEnd)
|
|
1059
|
-
return '';
|
|
1060
|
-
return undefined;
|
|
784
|
+
for (let i = 0; i < stringNode.childCount; i++) {
|
|
785
|
+
const child = stringNode.child(i);
|
|
786
|
+
if (child && child.type === "string_content") return child.text;
|
|
787
|
+
}
|
|
788
|
+
let hasStart = false;
|
|
789
|
+
let hasEnd = false;
|
|
790
|
+
for (let i = 0; i < stringNode.childCount; i++) {
|
|
791
|
+
const child = stringNode.child(i);
|
|
792
|
+
if ((child === null || child === void 0 ? void 0 : child.type) === "string_start") hasStart = true;
|
|
793
|
+
if ((child === null || child === void 0 ? void 0 : child.type) === "string_end") hasEnd = true;
|
|
794
|
+
}
|
|
795
|
+
if (hasStart && hasEnd) return "";
|
|
1061
796
|
}
|
|
1062
797
|
function locationStr(node) {
|
|
1063
|
-
|
|
798
|
+
return `line ${node.startPosition.row + 1}, col ${node.startPosition.column}`;
|
|
1064
799
|
}
|
|
800
|
+
//#endregion
|
|
801
|
+
export { containsStaticCalls, parseStringExpression };
|
|
802
|
+
|
|
803
|
+
//# sourceMappingURL=parseStringExpression.js.map
|