@nithin554/code-crawler 0.1.39 → 0.1.41
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/README.md +203 -47
- package/dist/crawler.d.ts +27 -1
- package/dist/crawler.js +360 -11
- package/dist/crawler.js.map +1 -1
- package/dist/graph/queries.d.ts +70 -4
- package/dist/graph/queries.js +125 -1
- package/dist/graph/queries.js.map +1 -1
- package/dist/graph/schema.d.ts +24 -2
- package/dist/graph/schema.js +9 -0
- package/dist/graph/schema.js.map +1 -1
- package/dist/parser/controlFlow.d.ts +27 -0
- package/dist/parser/controlFlow.js +82 -0
- package/dist/parser/controlFlow.js.map +1 -0
- package/dist/parser/language.d.ts +101 -2
- package/dist/parser/languages/index.js +4 -0
- package/dist/parser/languages/index.js.map +1 -1
- package/dist/parser/languages/java.js +160 -6
- package/dist/parser/languages/java.js.map +1 -1
- package/dist/parser/languages/python.d.ts +2 -0
- package/dist/parser/languages/python.js +956 -0
- package/dist/parser/languages/python.js.map +1 -0
- package/dist/parser/languages/typescript.d.ts +2 -0
- package/dist/parser/languages/typescript.js +1111 -0
- package/dist/parser/languages/typescript.js.map +1 -0
- package/dist/parser/moduleId.d.ts +35 -0
- package/dist/parser/moduleId.js +93 -0
- package/dist/parser/moduleId.js.map +1 -0
- package/dist/parser/treeSitter.d.ts +6 -0
- package/dist/parser/treeSitter.js +13 -2
- package/dist/parser/treeSitter.js.map +1 -1
- package/dist/resolver/common.d.ts +52 -0
- package/dist/resolver/common.js +624 -0
- package/dist/resolver/common.js.map +1 -0
- package/dist/resolver/dynamic.js +66 -1
- package/dist/resolver/dynamic.js.map +1 -1
- package/dist/resolver/genericResolver.d.ts +45 -0
- package/dist/resolver/genericResolver.js +235 -0
- package/dist/resolver/genericResolver.js.map +1 -0
- package/dist/resolver/types.d.ts +24 -1
- package/dist/resolver/types.js +50 -0
- package/dist/resolver/types.js.map +1 -1
- package/dist/server.js +108 -5
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,956 @@
|
|
|
1
|
+
import { childOfType, childrenOfType, createParser, fieldChild, loadGrammar, sameNode, walk, } from "../treeSitter.js";
|
|
2
|
+
import { controlFlowOf, insideAny } from "../controlFlow.js";
|
|
3
|
+
import { moduleNodeId, pythonModuleId, resolvePythonRelative, } from "../moduleId.js";
|
|
4
|
+
/**
|
|
5
|
+
* Python parser.
|
|
6
|
+
*
|
|
7
|
+
* Python has no packages, so a module (one file) is the namespace: its module
|
|
8
|
+
* id is derived from the project-relative path. Module-level functions are
|
|
9
|
+
* methods of the module node, which lets the resolver treat `module.fn()` and
|
|
10
|
+
* `fn()` uniformly through the same `TypeIndex.findMethods` machinery Java uses
|
|
11
|
+
* for static classes.
|
|
12
|
+
*
|
|
13
|
+
* Modelled beyond plain declarations:
|
|
14
|
+
* - decorators as annotations (framework entry points, DI, AOP-ish advice)
|
|
15
|
+
* - `self.x = Foo()` fields plus annotated class/module attributes
|
|
16
|
+
* - run-time lookups (`getattr`, `__import__`, `importlib.import_module`)
|
|
17
|
+
* - framework-injected parameters (`Depends(...)`, `Annotated[..., Depends]`)
|
|
18
|
+
* - control-flow context of every call site
|
|
19
|
+
*/
|
|
20
|
+
const PY_FLOWS = [
|
|
21
|
+
{ type: "if_statement", kind: "branch", guardField: "condition" },
|
|
22
|
+
{ type: "elif_clause", kind: "branch", guardField: "condition" },
|
|
23
|
+
{ type: "else_clause", kind: "branch", guard: "else" },
|
|
24
|
+
{ type: "conditional_expression", kind: "branch", guardField: "condition" },
|
|
25
|
+
{ type: "for_statement", kind: "loop", guardField: "right" },
|
|
26
|
+
{ type: "async_for_statement", kind: "loop", guardField: "right" },
|
|
27
|
+
{ type: "while_statement", kind: "loop", guardField: "condition" },
|
|
28
|
+
{ type: "try_statement", kind: "exception", guard: "try" },
|
|
29
|
+
{ type: "except_clause", kind: "exception", guardField: "value" },
|
|
30
|
+
{ type: "finally_clause", kind: "exception", guard: "finally" },
|
|
31
|
+
{ type: "match_statement", kind: "branch", guardField: "subject" },
|
|
32
|
+
{ type: "case_clause", kind: "branch", guard: "case" },
|
|
33
|
+
{ type: "assert_statement", kind: "predicate", guardField: "condition" },
|
|
34
|
+
{ type: "boolean_operator", kind: "predicate" },
|
|
35
|
+
];
|
|
36
|
+
/** Calls below these nodes are declarations, not executable call sites. */
|
|
37
|
+
const SKIP_CALL_ANCESTORS = new Set([
|
|
38
|
+
"decorator",
|
|
39
|
+
"import_statement",
|
|
40
|
+
"import_from_statement",
|
|
41
|
+
"future_import_statement",
|
|
42
|
+
]);
|
|
43
|
+
const HTTP_VERBS = new Set([
|
|
44
|
+
"get",
|
|
45
|
+
"post",
|
|
46
|
+
"put",
|
|
47
|
+
"delete",
|
|
48
|
+
"patch",
|
|
49
|
+
"head",
|
|
50
|
+
"options",
|
|
51
|
+
"trace",
|
|
52
|
+
"route",
|
|
53
|
+
"api_route",
|
|
54
|
+
"websocket",
|
|
55
|
+
"websocket_route",
|
|
56
|
+
]);
|
|
57
|
+
const ROUTE_RECEIVERS = new Set([
|
|
58
|
+
"app",
|
|
59
|
+
"api",
|
|
60
|
+
"router",
|
|
61
|
+
"bp",
|
|
62
|
+
"blueprint",
|
|
63
|
+
"server",
|
|
64
|
+
"application",
|
|
65
|
+
"fastify",
|
|
66
|
+
]);
|
|
67
|
+
const INJECTION_DECORATORS = new Set(["inject", "injectable", "autowired", "provide"]);
|
|
68
|
+
const BUILTIN_TYPES = new Set([
|
|
69
|
+
"str",
|
|
70
|
+
"int",
|
|
71
|
+
"float",
|
|
72
|
+
"bool",
|
|
73
|
+
"bytes",
|
|
74
|
+
"list",
|
|
75
|
+
"dict",
|
|
76
|
+
"set",
|
|
77
|
+
"tuple",
|
|
78
|
+
"object",
|
|
79
|
+
"Exception",
|
|
80
|
+
"BaseException",
|
|
81
|
+
"ValueError",
|
|
82
|
+
"TypeError",
|
|
83
|
+
"KeyError",
|
|
84
|
+
"RuntimeError",
|
|
85
|
+
"OSError",
|
|
86
|
+
]);
|
|
87
|
+
export const pythonConfig = {
|
|
88
|
+
name: "python",
|
|
89
|
+
extensions: ["py", "pyi"],
|
|
90
|
+
loadGrammar() {
|
|
91
|
+
return loadGrammar("@vscode/tree-sitter-wasm", "wasm/tree-sitter-python.wasm");
|
|
92
|
+
},
|
|
93
|
+
async parseFile(source, filePath, context) {
|
|
94
|
+
const parser = createParser((await pythonConfig.loadGrammar()));
|
|
95
|
+
const tree = parser.parse(source);
|
|
96
|
+
parser.delete();
|
|
97
|
+
if (!tree)
|
|
98
|
+
throw new Error(`failed to parse ${filePath}`);
|
|
99
|
+
const root = tree.rootNode;
|
|
100
|
+
const moduleId = pythonModuleId(filePath, context?.projectRoot);
|
|
101
|
+
const isPackageFile = /(^|[\\/])__init__\.[^\\/]+$/.test(filePath);
|
|
102
|
+
const moduleTypeId = moduleNodeId(moduleId);
|
|
103
|
+
const types = [
|
|
104
|
+
{
|
|
105
|
+
id: moduleTypeId,
|
|
106
|
+
kind: "module",
|
|
107
|
+
name: moduleId.split(".").pop() ?? moduleId,
|
|
108
|
+
extendsTypes: [],
|
|
109
|
+
implementsTypes: [],
|
|
110
|
+
startLine: 0,
|
|
111
|
+
endLine: root.endPosition.row,
|
|
112
|
+
annotations: [],
|
|
113
|
+
exported: true,
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
const methods = [];
|
|
117
|
+
const fields = [];
|
|
118
|
+
const imports = [];
|
|
119
|
+
const callSites = [];
|
|
120
|
+
const localsByMethod = new Map();
|
|
121
|
+
const scopeTypes = new Map();
|
|
122
|
+
const scopeModules = new Map();
|
|
123
|
+
const reflectiveRefs = [];
|
|
124
|
+
const injections = [];
|
|
125
|
+
const routes = [];
|
|
126
|
+
const methodRanges = [];
|
|
127
|
+
const typeRanges = [{ startLine: 0, endLine: root.endPosition.row, id: moduleTypeId }];
|
|
128
|
+
const fieldIds = new Set();
|
|
129
|
+
const typeIdOf = (name, parentId) => parentId ? `${parentId}.${name}` : `${moduleId}.${name}`;
|
|
130
|
+
const methodIdOf = (typeId, name, isCtor) => `${typeId}#${isCtor ? "<init>" : name}`;
|
|
131
|
+
// ---------------------------------------------------------------- helpers
|
|
132
|
+
function calleeName(node) {
|
|
133
|
+
if (!node)
|
|
134
|
+
return null;
|
|
135
|
+
if (node.type === "identifier")
|
|
136
|
+
return { name: node.text };
|
|
137
|
+
if (node.type === "attribute") {
|
|
138
|
+
const attribute = fieldChild(node, "attribute");
|
|
139
|
+
if (!attribute)
|
|
140
|
+
return null;
|
|
141
|
+
return { name: attribute.text, fqn: node.text };
|
|
142
|
+
}
|
|
143
|
+
if (node.type === "call")
|
|
144
|
+
return calleeName(fieldChild(node, "function"));
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
function annotationOf(decorator) {
|
|
148
|
+
const expression = decorator.namedChildren[0] ?? null;
|
|
149
|
+
if (!expression)
|
|
150
|
+
return null;
|
|
151
|
+
if (expression.type === "call") {
|
|
152
|
+
const named = calleeName(fieldChild(expression, "function"));
|
|
153
|
+
if (!named)
|
|
154
|
+
return null;
|
|
155
|
+
const argsNode = fieldChild(expression, "arguments");
|
|
156
|
+
const args = argsNode
|
|
157
|
+
? argsNode.namedChildren.filter((c) => c.type !== "comment").map((c) => c.text)
|
|
158
|
+
: [];
|
|
159
|
+
return { name: named.name, fqn: named.fqn, arguments: args, line: decorator.startPosition.row };
|
|
160
|
+
}
|
|
161
|
+
const named = calleeName(expression);
|
|
162
|
+
if (!named)
|
|
163
|
+
return null;
|
|
164
|
+
return { name: named.name, fqn: named.fqn, arguments: [], line: decorator.startPosition.row };
|
|
165
|
+
}
|
|
166
|
+
function annotationsOf(decorators) {
|
|
167
|
+
return decorators
|
|
168
|
+
.map((d) => annotationOf(d))
|
|
169
|
+
.filter((a) => a !== null);
|
|
170
|
+
}
|
|
171
|
+
function unquote(raw) {
|
|
172
|
+
const match = raw.match(/^\s*(?:[rbfu]{0,2})?["']([\s\S]*?)["']\s*$/);
|
|
173
|
+
return match ? (match[1] ?? "") : raw.trim();
|
|
174
|
+
}
|
|
175
|
+
function stringArgument(annotation) {
|
|
176
|
+
for (const arg of annotation.arguments) {
|
|
177
|
+
const value = unquote(arg);
|
|
178
|
+
if (value !== arg.trim())
|
|
179
|
+
return value;
|
|
180
|
+
}
|
|
181
|
+
return "";
|
|
182
|
+
}
|
|
183
|
+
function entryPointOf(annotations, name) {
|
|
184
|
+
for (const annotation of annotations) {
|
|
185
|
+
const simple = annotation.name;
|
|
186
|
+
const lower = simple.toLowerCase();
|
|
187
|
+
const qualified = (annotation.fqn ?? simple).toLowerCase();
|
|
188
|
+
const hasReceiver = qualified.includes(".");
|
|
189
|
+
if (HTTP_VERBS.has(lower) && (hasReceiver || lower.endsWith("route"))) {
|
|
190
|
+
const path = stringArgument(annotation);
|
|
191
|
+
const method = lower === "route" || lower === "api_route" || lower === "websocket_route" ? "" : lower.toUpperCase();
|
|
192
|
+
return { kind: "http", detail: `${method} ${path}`.trim() || qualified };
|
|
193
|
+
}
|
|
194
|
+
if (["task", "shared_task", "periodic_task", "celery_task"].includes(lower)) {
|
|
195
|
+
return { kind: "messaging", detail: qualified };
|
|
196
|
+
}
|
|
197
|
+
if (["scheduled", "cron", "interval", "periodic_task"].includes(lower)) {
|
|
198
|
+
return { kind: "scheduler", detail: qualified };
|
|
199
|
+
}
|
|
200
|
+
if (lower === "fixture" || qualified.endsWith(".fixture") || lower === "parametrize" || lower.startsWith("mark.")) {
|
|
201
|
+
return { kind: "test", detail: qualified };
|
|
202
|
+
}
|
|
203
|
+
if (["on_event", "listener", "receiver", "on_message", "subscribe", "handler"].includes(lower)) {
|
|
204
|
+
return { kind: "event", detail: qualified };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (name === "main" || name === "cli")
|
|
208
|
+
return { kind: "cli", detail: name };
|
|
209
|
+
if (/^test_/.test(name) || /_test$/.test(name))
|
|
210
|
+
return { kind: "test", detail: name };
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* FastAPI/Flask decorators register routes on the decorated function, so
|
|
215
|
+
* the handler is only known once the function is visited. `app.route(...)`
|
|
216
|
+
* without a verb also registers a route, hence the `ANY` fallback.
|
|
217
|
+
*/
|
|
218
|
+
function decoratorRoutesOf(annotations, handlerName, ownerTypeId) {
|
|
219
|
+
const found = [];
|
|
220
|
+
for (const annotation of annotations) {
|
|
221
|
+
const lower = annotation.name.toLowerCase();
|
|
222
|
+
if (!HTTP_VERBS.has(lower))
|
|
223
|
+
continue;
|
|
224
|
+
const qualified = annotation.fqn ?? annotation.name;
|
|
225
|
+
const receiver = qualified.includes(".") ? qualified.slice(0, qualified.indexOf(".")) : "";
|
|
226
|
+
if (!annotation.arguments.length && !receiver && !lower.endsWith("route"))
|
|
227
|
+
continue;
|
|
228
|
+
const verb = lower === "route" || lower === "api_route" || lower === "websocket_route"
|
|
229
|
+
? "ANY"
|
|
230
|
+
: lower.toUpperCase();
|
|
231
|
+
found.push({
|
|
232
|
+
framework: receiver || "flask",
|
|
233
|
+
httpMethod: verb,
|
|
234
|
+
path: stringArgument(annotation),
|
|
235
|
+
handlerName,
|
|
236
|
+
moduleId,
|
|
237
|
+
enclosingTypeId: ownerTypeId,
|
|
238
|
+
line: annotation.line,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return found;
|
|
242
|
+
}
|
|
243
|
+
function inferExpressionType(node) {
|
|
244
|
+
if (!node)
|
|
245
|
+
return "";
|
|
246
|
+
switch (node.type) {
|
|
247
|
+
case "call": {
|
|
248
|
+
const named = calleeName(fieldChild(node, "function"));
|
|
249
|
+
return named ? named.name : "";
|
|
250
|
+
}
|
|
251
|
+
case "string": {
|
|
252
|
+
const prefix = node.children.find((c) => c.type === "string_start")?.text ?? "";
|
|
253
|
+
if (prefix.includes("f"))
|
|
254
|
+
return "str";
|
|
255
|
+
if (prefix.includes("b"))
|
|
256
|
+
return "bytes";
|
|
257
|
+
return "str";
|
|
258
|
+
}
|
|
259
|
+
case "integer":
|
|
260
|
+
return "int";
|
|
261
|
+
case "float":
|
|
262
|
+
return "float";
|
|
263
|
+
case "true":
|
|
264
|
+
case "false":
|
|
265
|
+
return "bool";
|
|
266
|
+
case "list":
|
|
267
|
+
case "list_comprehension":
|
|
268
|
+
return "list";
|
|
269
|
+
case "dictionary":
|
|
270
|
+
case "dictionary_comprehension":
|
|
271
|
+
return "dict";
|
|
272
|
+
case "set":
|
|
273
|
+
case "set_comprehension":
|
|
274
|
+
return "set";
|
|
275
|
+
case "tuple":
|
|
276
|
+
return "tuple";
|
|
277
|
+
default:
|
|
278
|
+
return "";
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function addField(typeId, name, type, line, annotations = []) {
|
|
282
|
+
const id = `${typeId}$${name}`;
|
|
283
|
+
if (fieldIds.has(id))
|
|
284
|
+
return;
|
|
285
|
+
fieldIds.add(id);
|
|
286
|
+
fields.push({ id, typeId, name, type, startLine: line, modifiers: [], annotations });
|
|
287
|
+
}
|
|
288
|
+
/** Type text from a Python `type` node, ignoring surrounding noise. */
|
|
289
|
+
function typeText(node) {
|
|
290
|
+
if (!node)
|
|
291
|
+
return "";
|
|
292
|
+
return node.text.trim();
|
|
293
|
+
}
|
|
294
|
+
function parseParams(paramsNode) {
|
|
295
|
+
if (!paramsNode)
|
|
296
|
+
return [];
|
|
297
|
+
const out = [];
|
|
298
|
+
for (const param of paramsNode.namedChildren) {
|
|
299
|
+
switch (param.type) {
|
|
300
|
+
case "identifier":
|
|
301
|
+
out.push({ name: param.text, type: "" });
|
|
302
|
+
break;
|
|
303
|
+
case "typed_parameter": {
|
|
304
|
+
const nameNode = param.namedChildren.find((c) => c.type === "identifier");
|
|
305
|
+
if (nameNode)
|
|
306
|
+
out.push({ name: nameNode.text, type: typeText(fieldChild(param, "type")) });
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
case "default_parameter": {
|
|
310
|
+
const nameNode = fieldChild(param, "name") ?? param.namedChildren[0];
|
|
311
|
+
if (nameNode)
|
|
312
|
+
out.push({ name: nameNode.text, type: "" });
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case "typed_default_parameter": {
|
|
316
|
+
const nameNode = fieldChild(param, "name") ?? param.namedChildren.find((c) => c.type === "identifier");
|
|
317
|
+
if (nameNode)
|
|
318
|
+
out.push({ name: nameNode.text, type: typeText(fieldChild(param, "type")) });
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
case "list_splat_pattern":
|
|
322
|
+
out.push({ name: `*${param.text}`, type: "" });
|
|
323
|
+
break;
|
|
324
|
+
case "dictionary_splat_pattern":
|
|
325
|
+
out.push({ name: `**${param.text}`, type: "" });
|
|
326
|
+
break;
|
|
327
|
+
case "keyword_separator":
|
|
328
|
+
case "positional_separator":
|
|
329
|
+
break;
|
|
330
|
+
default:
|
|
331
|
+
out.push({ name: param.text.replace(/\s+/g, " "), type: "" });
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return out;
|
|
336
|
+
}
|
|
337
|
+
function paramName(param) {
|
|
338
|
+
switch (param.type) {
|
|
339
|
+
case "identifier":
|
|
340
|
+
return { name: param.text, type: "" };
|
|
341
|
+
case "typed_parameter": {
|
|
342
|
+
const nameNode = param.namedChildren.find((c) => c.type === "identifier");
|
|
343
|
+
return nameNode ? { name: nameNode.text, type: typeText(fieldChild(param, "type")) } : null;
|
|
344
|
+
}
|
|
345
|
+
case "default_parameter": {
|
|
346
|
+
const nameNode = fieldChild(param, "name") ?? param.namedChildren[0];
|
|
347
|
+
return nameNode ? { name: nameNode.text, type: "" } : null;
|
|
348
|
+
}
|
|
349
|
+
case "typed_default_parameter": {
|
|
350
|
+
const nameNode = fieldChild(param, "name") ?? param.namedChildren.find((c) => c.type === "identifier");
|
|
351
|
+
return nameNode ? { name: nameNode.text, type: typeText(fieldChild(param, "type")) } : null;
|
|
352
|
+
}
|
|
353
|
+
default:
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/** First type argument of `Wrapper[X, ...]`, or the text itself. */
|
|
358
|
+
function firstTypeArgument(text, wrapper) {
|
|
359
|
+
const match = text.match(/^([A-Za-z_][\w.]*)\[([\s\S]*)\]$/);
|
|
360
|
+
if (!match)
|
|
361
|
+
return text;
|
|
362
|
+
const base = match[1] ?? "";
|
|
363
|
+
if (wrapper && base.split(".").pop() !== wrapper)
|
|
364
|
+
return text;
|
|
365
|
+
const inner = match[2] ?? "";
|
|
366
|
+
let depth = 0;
|
|
367
|
+
for (let i = 0; i < inner.length; i++) {
|
|
368
|
+
const char = inner.charAt(i);
|
|
369
|
+
if (char === "[" || char === "(")
|
|
370
|
+
depth++;
|
|
371
|
+
else if (char === "]" || char === ")")
|
|
372
|
+
depth--;
|
|
373
|
+
else if (char === "," && depth === 0)
|
|
374
|
+
return inner.slice(0, i).trim();
|
|
375
|
+
}
|
|
376
|
+
return inner.trim();
|
|
377
|
+
}
|
|
378
|
+
// ---------------------------------------------------------- declarations
|
|
379
|
+
function superclassNames(argumentList) {
|
|
380
|
+
const out = [];
|
|
381
|
+
for (const arg of argumentList.namedChildren) {
|
|
382
|
+
if (arg.type === "keyword_argument")
|
|
383
|
+
continue;
|
|
384
|
+
if (arg.type === "subscript") {
|
|
385
|
+
const value = fieldChild(arg, "value") ?? arg.namedChildren[0];
|
|
386
|
+
if (value)
|
|
387
|
+
out.push(value.text);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
out.push(arg.text);
|
|
391
|
+
}
|
|
392
|
+
return out;
|
|
393
|
+
}
|
|
394
|
+
function visitClass(node, decorators, parentTypeId) {
|
|
395
|
+
const nameNode = fieldChild(node, "name") ?? childOfType(node, "identifier");
|
|
396
|
+
if (!nameNode)
|
|
397
|
+
return;
|
|
398
|
+
const name = nameNode.text;
|
|
399
|
+
const id = typeIdOf(name, parentTypeId);
|
|
400
|
+
const superclasses = fieldChild(node, "superclasses");
|
|
401
|
+
const annotations = decorators;
|
|
402
|
+
types.push({
|
|
403
|
+
id,
|
|
404
|
+
kind: "class",
|
|
405
|
+
name,
|
|
406
|
+
extendsTypes: superclasses ? superclassNames(superclasses) : [],
|
|
407
|
+
implementsTypes: [],
|
|
408
|
+
startLine: node.startPosition.row,
|
|
409
|
+
endLine: node.endPosition.row,
|
|
410
|
+
annotations,
|
|
411
|
+
isAspect: annotations.some((a) => a.name === "Aspect"),
|
|
412
|
+
exported: true,
|
|
413
|
+
});
|
|
414
|
+
typeRanges.push({ startLine: node.startPosition.row, endLine: node.endPosition.row, id });
|
|
415
|
+
scopeTypes.set(name, id);
|
|
416
|
+
const injectable = annotations.some((a) => INJECTION_DECORATORS.has(a.name.toLowerCase()));
|
|
417
|
+
const body = fieldChild(node, "body") ?? childOfType(node, "block");
|
|
418
|
+
if (!body)
|
|
419
|
+
return;
|
|
420
|
+
for (const entry of definitionsIn(body)) {
|
|
421
|
+
const childAnnotations = annotationsOf(entry.decorators);
|
|
422
|
+
if (entry.definition.type === "class_definition") {
|
|
423
|
+
visitClass(entry.definition, childAnnotations, id);
|
|
424
|
+
}
|
|
425
|
+
else if (entry.definition.type === "function_definition") {
|
|
426
|
+
visitFunction(entry.definition, childAnnotations, id, injectable);
|
|
427
|
+
}
|
|
428
|
+
else if (entry.definition.type === "expression_statement") {
|
|
429
|
+
collectAttributeFields(entry.definition, id);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function definitionsIn(block) {
|
|
434
|
+
const out = [];
|
|
435
|
+
for (const child of block.namedChildren) {
|
|
436
|
+
if (child.type === "decorated_definition") {
|
|
437
|
+
const definition = fieldChild(child, "definition");
|
|
438
|
+
if (definition)
|
|
439
|
+
out.push({ definition, decorators: childrenOfType(child, "decorator") });
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
out.push({ definition: child, decorators: [] });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return out;
|
|
446
|
+
}
|
|
447
|
+
/** Class/module level `name: Type = ...` attributes. */
|
|
448
|
+
function collectAttributeFields(statement, ownerId) {
|
|
449
|
+
for (const assignment of statement.namedChildren) {
|
|
450
|
+
if (assignment.type !== "assignment")
|
|
451
|
+
continue;
|
|
452
|
+
const left = fieldChild(assignment, "left") ?? assignment.namedChildren[0];
|
|
453
|
+
if (!left || left.type !== "identifier")
|
|
454
|
+
continue;
|
|
455
|
+
const typeNode = fieldChild(assignment, "type");
|
|
456
|
+
const right = fieldChild(assignment, "right");
|
|
457
|
+
const type = typeNode ? typeText(typeNode) : inferExpressionType(right);
|
|
458
|
+
addField(ownerId, left.text, type, assignment.startPosition.row);
|
|
459
|
+
if (ownerId === moduleTypeId && type && type !== left.text)
|
|
460
|
+
scopeTypes.set(left.text, type);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function visitFunction(node, annotations, ownerTypeId, ownerInjectable) {
|
|
464
|
+
const nameNode = fieldChild(node, "name") ?? childOfType(node, "identifier");
|
|
465
|
+
if (!nameNode)
|
|
466
|
+
return;
|
|
467
|
+
const rawName = nameNode.text;
|
|
468
|
+
const owner = ownerTypeId ?? moduleTypeId;
|
|
469
|
+
const isCtor = rawName === "__init__";
|
|
470
|
+
const id = methodIdOf(owner, rawName, isCtor);
|
|
471
|
+
const paramsNode = fieldChild(node, "parameters");
|
|
472
|
+
const params = parseParams(paramsNode);
|
|
473
|
+
const returnType = typeText(fieldChild(node, "return_type"));
|
|
474
|
+
const modifiers = [];
|
|
475
|
+
if (/^\s*async\b/.test(node.text))
|
|
476
|
+
modifiers.push("async");
|
|
477
|
+
for (const annotation of annotations) {
|
|
478
|
+
const lower = annotation.name.toLowerCase();
|
|
479
|
+
if (lower === "staticmethod" || lower === "classmethod" || lower === "property" || lower === "abstractmethod") {
|
|
480
|
+
modifiers.push(lower);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
methods.push({
|
|
484
|
+
id,
|
|
485
|
+
typeId: owner,
|
|
486
|
+
name: isCtor ? "<init>" : rawName,
|
|
487
|
+
isConstructor: isCtor,
|
|
488
|
+
params,
|
|
489
|
+
startLine: node.startPosition.row,
|
|
490
|
+
endLine: node.endPosition.row,
|
|
491
|
+
returnType,
|
|
492
|
+
modifiers,
|
|
493
|
+
annotations,
|
|
494
|
+
entryPoint: entryPointOf(annotations, rawName),
|
|
495
|
+
});
|
|
496
|
+
methodRanges.push({ startLine: node.startPosition.row, endLine: node.endPosition.row, id, typeId: owner });
|
|
497
|
+
const isHandler = annotations.some((a) => HTTP_VERBS.has(a.name.toLowerCase()));
|
|
498
|
+
if (isHandler)
|
|
499
|
+
routes.push(...decoratorRoutesOf(annotations, rawName, owner));
|
|
500
|
+
const locals = new Map();
|
|
501
|
+
for (const param of params) {
|
|
502
|
+
if (param.type)
|
|
503
|
+
locals.set(param.name.replace(/^\*+/, ""), param.type);
|
|
504
|
+
}
|
|
505
|
+
// Parameters supplied by the framework rather than by the caller.
|
|
506
|
+
if (paramsNode) {
|
|
507
|
+
const methodInjects = annotations.some((a) => INJECTION_DECORATORS.has(a.name.toLowerCase()));
|
|
508
|
+
for (const param of paramsNode.namedChildren) {
|
|
509
|
+
const named = paramName(param);
|
|
510
|
+
if (!named)
|
|
511
|
+
continue;
|
|
512
|
+
const raw = param.text;
|
|
513
|
+
const depends = /Depends\s*\(/.test(raw);
|
|
514
|
+
const injectDecorator = /@?Inject\s*\(/.test(raw);
|
|
515
|
+
let declared = named.type;
|
|
516
|
+
if (depends && declared)
|
|
517
|
+
declared = firstTypeArgument(declared, "Annotated");
|
|
518
|
+
if (depends || injectDecorator) {
|
|
519
|
+
injections.push({
|
|
520
|
+
kind: ownerTypeId ? (isCtor ? "constructor-parameter" : "parameter") : "parameter",
|
|
521
|
+
ownerId: owner,
|
|
522
|
+
name: named.name,
|
|
523
|
+
type: declared || (depends ? "Depends" : "Inject"),
|
|
524
|
+
framework: depends ? "fastapi" : "dependency-injector",
|
|
525
|
+
line: param.startPosition.row,
|
|
526
|
+
optional: /Optional\s*\[/.test(declared) || /=\s*None/.test(raw),
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
else if (methodInjects && declared) {
|
|
530
|
+
injections.push({
|
|
531
|
+
kind: ownerTypeId ? (isCtor ? "constructor-parameter" : "parameter") : "parameter",
|
|
532
|
+
ownerId: owner,
|
|
533
|
+
name: named.name,
|
|
534
|
+
type: declared,
|
|
535
|
+
framework: "dependency-injector",
|
|
536
|
+
line: param.startPosition.row,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
else if (ownerInjectable && isCtor && declared) {
|
|
540
|
+
injections.push({
|
|
541
|
+
kind: "constructor-parameter",
|
|
542
|
+
ownerId: owner,
|
|
543
|
+
name: named.name,
|
|
544
|
+
type: declared,
|
|
545
|
+
framework: "dependency-injector",
|
|
546
|
+
line: param.startPosition.row,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const body = fieldChild(node, "body") ?? childOfType(node, "block");
|
|
552
|
+
if (body)
|
|
553
|
+
collectLocalsAndFields(body, locals, owner, ownerTypeId, params);
|
|
554
|
+
localsByMethod.set(id, locals);
|
|
555
|
+
}
|
|
556
|
+
/** Container type text -> the type yielded when iterating it. */
|
|
557
|
+
function elementTypeOf(containerType) {
|
|
558
|
+
const text = (containerType ?? "").trim();
|
|
559
|
+
const match = /^([A-Za-z_][\w.]*)\s*[[(](.+)[\])]$/.exec(text);
|
|
560
|
+
if (!match)
|
|
561
|
+
return "";
|
|
562
|
+
const base = (match[1] ?? "").split(".").pop() ?? "";
|
|
563
|
+
const first = (match[2] ?? "").split(",")[0]?.trim() ?? "";
|
|
564
|
+
const yieldsFirstArg = new Set([
|
|
565
|
+
"list",
|
|
566
|
+
"List",
|
|
567
|
+
"set",
|
|
568
|
+
"Set",
|
|
569
|
+
"frozenset",
|
|
570
|
+
"FrozenSet",
|
|
571
|
+
"Sequence",
|
|
572
|
+
"Iterable",
|
|
573
|
+
"Iterator",
|
|
574
|
+
"Collection",
|
|
575
|
+
"Generator",
|
|
576
|
+
"deque",
|
|
577
|
+
"Deque",
|
|
578
|
+
"tuple",
|
|
579
|
+
"Tuple",
|
|
580
|
+
"dict",
|
|
581
|
+
"Dict",
|
|
582
|
+
"Mapping",
|
|
583
|
+
"defaultdict",
|
|
584
|
+
]);
|
|
585
|
+
return yieldsFirstArg.has(base) ? first : "";
|
|
586
|
+
}
|
|
587
|
+
/** Type text of the value an expression iterates over. */
|
|
588
|
+
function iterableTypeOf(node, locals, ownerTypeId) {
|
|
589
|
+
if (node.type === "identifier")
|
|
590
|
+
return locals.get(node.text) ?? "";
|
|
591
|
+
if (node.type !== "attribute")
|
|
592
|
+
return "";
|
|
593
|
+
const object = fieldChild(node, "object");
|
|
594
|
+
const attribute = fieldChild(node, "attribute");
|
|
595
|
+
if (!object || !attribute)
|
|
596
|
+
return "";
|
|
597
|
+
if (object.text !== "self" && object.text !== "cls")
|
|
598
|
+
return "";
|
|
599
|
+
const scoped = locals.get(`${object.text}.${attribute.text}`);
|
|
600
|
+
if (scoped)
|
|
601
|
+
return scoped;
|
|
602
|
+
if (!ownerTypeId)
|
|
603
|
+
return "";
|
|
604
|
+
return fields.find((f) => f.typeId === ownerTypeId && f.name === attribute.text)?.type ?? "";
|
|
605
|
+
}
|
|
606
|
+
function collectLocalsAndFields(body, locals, ownerId, ownerTypeId, params) {
|
|
607
|
+
const paramNames = new Set(params.map((p) => p.name));
|
|
608
|
+
walk(body, (node) => {
|
|
609
|
+
if (node.type === "for_statement") {
|
|
610
|
+
// `for row in self.rows:` -- the loop variable has the container's
|
|
611
|
+
// element type, which is what makes `row.get(...)` resolvable.
|
|
612
|
+
const left = fieldChild(node, "left");
|
|
613
|
+
const iterable = fieldChild(node, "right");
|
|
614
|
+
const element = elementTypeOf(iterable ? inferExpressionType(iterable) || iterableTypeOf(iterable, locals, ownerTypeId) : "");
|
|
615
|
+
if (left && element) {
|
|
616
|
+
const targets = left.type === "identifier" ? [left] : left.namedChildren;
|
|
617
|
+
for (const target of targets) {
|
|
618
|
+
if (target.type === "identifier")
|
|
619
|
+
locals.set(target.text, element);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
if (node.type !== "assignment")
|
|
625
|
+
return;
|
|
626
|
+
const left = fieldChild(node, "left") ?? node.namedChildren[0];
|
|
627
|
+
if (!left)
|
|
628
|
+
return;
|
|
629
|
+
const typeNode = fieldChild(node, "type");
|
|
630
|
+
const right = fieldChild(node, "right");
|
|
631
|
+
let inferred = typeNode ? typeText(typeNode) : inferExpressionType(right);
|
|
632
|
+
// `self.repo = repo` -- the assigned value's type is the parameter's
|
|
633
|
+
// declared type, which is how injected dependencies are propagated.
|
|
634
|
+
if (!inferred && right && right.type === "identifier") {
|
|
635
|
+
inferred = locals.get(right.text) ?? "";
|
|
636
|
+
}
|
|
637
|
+
if (left.type === "identifier") {
|
|
638
|
+
if (!paramNames.has(left.text) && !locals.has(left.text)) {
|
|
639
|
+
if (inferred)
|
|
640
|
+
locals.set(left.text, inferred);
|
|
641
|
+
}
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (left.type === "attribute" && ownerTypeId) {
|
|
645
|
+
const object = fieldChild(left, "object");
|
|
646
|
+
const attribute = fieldChild(left, "attribute");
|
|
647
|
+
if (!object || !attribute)
|
|
648
|
+
return;
|
|
649
|
+
if (object.type !== "identifier" || (object.text !== "self" && object.text !== "cls"))
|
|
650
|
+
return;
|
|
651
|
+
addField(ownerTypeId, attribute.text, inferred, node.startPosition.row);
|
|
652
|
+
if (inferred)
|
|
653
|
+
locals.set(`self.${attribute.text}`, inferred);
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
void ownerId;
|
|
657
|
+
}
|
|
658
|
+
// -------------------------------------------------------------- imports
|
|
659
|
+
function moduleBindingOf(moduleText, alias, isNamespace) {
|
|
660
|
+
if (!moduleText)
|
|
661
|
+
return;
|
|
662
|
+
const binding = alias ?? (isNamespace ? (moduleText.split(".")[0] ?? moduleText) : (moduleText.split(".").pop() ?? moduleText));
|
|
663
|
+
scopeModules.set(binding, moduleText);
|
|
664
|
+
}
|
|
665
|
+
function symbolBindingOf(moduleText, importedName, alias) {
|
|
666
|
+
const binding = alias ?? importedName;
|
|
667
|
+
if (moduleText)
|
|
668
|
+
scopeTypes.set(binding, `${moduleText}.${importedName}`);
|
|
669
|
+
else
|
|
670
|
+
scopeTypes.set(binding, importedName);
|
|
671
|
+
}
|
|
672
|
+
for (const statement of root.namedChildren) {
|
|
673
|
+
if (statement.type === "import_statement") {
|
|
674
|
+
for (const nameNode of statement.namedChildren) {
|
|
675
|
+
if (nameNode.type === "aliased_import") {
|
|
676
|
+
const moduleNode = fieldChild(nameNode, "name");
|
|
677
|
+
const aliasNode = fieldChild(nameNode, "alias");
|
|
678
|
+
if (!moduleNode)
|
|
679
|
+
continue;
|
|
680
|
+
moduleBindingOf(moduleNode.text, aliasNode?.text, true);
|
|
681
|
+
imports.push({
|
|
682
|
+
fqn: moduleNode.text,
|
|
683
|
+
simpleName: aliasNode?.text ?? moduleNode.text.split(".").pop() ?? moduleNode.text,
|
|
684
|
+
isStatic: false,
|
|
685
|
+
module: moduleNode.text,
|
|
686
|
+
isNamespace: true,
|
|
687
|
+
alias: aliasNode?.text,
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
else if (nameNode.type === "dotted_name" || nameNode.type === "identifier") {
|
|
691
|
+
moduleBindingOf(nameNode.text, undefined, true);
|
|
692
|
+
imports.push({
|
|
693
|
+
fqn: nameNode.text,
|
|
694
|
+
simpleName: nameNode.text.split(".")[0] ?? nameNode.text,
|
|
695
|
+
isStatic: false,
|
|
696
|
+
module: nameNode.text,
|
|
697
|
+
isNamespace: true,
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
if (statement.type !== "import_from_statement")
|
|
704
|
+
continue;
|
|
705
|
+
let level = 0;
|
|
706
|
+
let moduleText = "";
|
|
707
|
+
const relative = childOfType(statement, "relative_import");
|
|
708
|
+
if (relative) {
|
|
709
|
+
const prefix = childOfType(relative, "import_prefix");
|
|
710
|
+
level = prefix ? prefix.text.length : 1;
|
|
711
|
+
moduleText = childOfType(relative, "dotted_name")?.text ?? "";
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
const prefix = childOfType(statement, "import_prefix");
|
|
715
|
+
if (prefix)
|
|
716
|
+
level = prefix.text.length;
|
|
717
|
+
const moduleNode = fieldChild(statement, "module_name") ?? childOfType(statement, "dotted_name");
|
|
718
|
+
moduleText = moduleNode?.text ?? "";
|
|
719
|
+
}
|
|
720
|
+
if (level > 0)
|
|
721
|
+
moduleText = resolvePythonRelative(moduleId, isPackageFile, level, moduleText);
|
|
722
|
+
const moduleNode = fieldChild(statement, "module_name") ?? relative;
|
|
723
|
+
const wildcard = childOfType(statement, "wildcard_import");
|
|
724
|
+
if (wildcard) {
|
|
725
|
+
imports.push({ fqn: `${moduleText}.*`, simpleName: "*", isStatic: false, module: moduleText });
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
for (const nameNode of statement.namedChildren) {
|
|
729
|
+
if (sameNode(nameNode, moduleNode))
|
|
730
|
+
continue;
|
|
731
|
+
if (nameNode.type === "import_prefix" || nameNode.type === "wildcard_import")
|
|
732
|
+
continue;
|
|
733
|
+
if (nameNode.type === "aliased_import") {
|
|
734
|
+
const imported = fieldChild(nameNode, "name");
|
|
735
|
+
const alias = fieldChild(nameNode, "alias");
|
|
736
|
+
if (!imported)
|
|
737
|
+
continue;
|
|
738
|
+
symbolBindingOf(moduleText, imported.text, alias?.text);
|
|
739
|
+
imports.push({
|
|
740
|
+
fqn: `${moduleText}.${imported.text}`,
|
|
741
|
+
simpleName: alias?.text ?? imported.text,
|
|
742
|
+
isStatic: false,
|
|
743
|
+
module: moduleText,
|
|
744
|
+
importedName: imported.text,
|
|
745
|
+
alias: alias?.text,
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
else if (nameNode.type === "dotted_name" || nameNode.type === "identifier") {
|
|
749
|
+
symbolBindingOf(moduleText, nameNode.text, undefined);
|
|
750
|
+
imports.push({
|
|
751
|
+
fqn: `${moduleText}.${nameNode.text}`,
|
|
752
|
+
simpleName: nameNode.text,
|
|
753
|
+
isStatic: false,
|
|
754
|
+
module: moduleText,
|
|
755
|
+
importedName: nameNode.text,
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
// ------------------------------------------------------- module structure
|
|
761
|
+
// Runs before call-site collection so `enclosingOf` can see the type and
|
|
762
|
+
// method ranges that calls are attributed to.
|
|
763
|
+
for (const entry of definitionsIn(root)) {
|
|
764
|
+
const annotations = annotationsOf(entry.decorators);
|
|
765
|
+
if (entry.definition.type === "class_definition") {
|
|
766
|
+
visitClass(entry.definition, annotations, null);
|
|
767
|
+
}
|
|
768
|
+
else if (entry.definition.type === "function_definition") {
|
|
769
|
+
visitFunction(entry.definition, annotations, null, false);
|
|
770
|
+
}
|
|
771
|
+
else if (entry.definition.type === "expression_statement") {
|
|
772
|
+
collectAttributeFields(entry.definition, moduleTypeId);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
// ------------------------------------------------------------ call sites
|
|
776
|
+
function enclosingOf(line) {
|
|
777
|
+
let typeId = moduleTypeId;
|
|
778
|
+
let typeStart = -1;
|
|
779
|
+
for (const range of typeRanges) {
|
|
780
|
+
if (line >= range.startLine && line <= range.endLine && range.startLine >= typeStart) {
|
|
781
|
+
typeStart = range.startLine;
|
|
782
|
+
typeId = range.id;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
let methodId = typeId;
|
|
786
|
+
let methodStart = -1;
|
|
787
|
+
for (const range of methodRanges) {
|
|
788
|
+
if (line >= range.startLine && line <= range.endLine && range.startLine >= methodStart) {
|
|
789
|
+
methodStart = range.startLine;
|
|
790
|
+
methodId = range.id;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return { typeId, methodId };
|
|
794
|
+
}
|
|
795
|
+
function recordReflection(call, args, enclosing, mechanism, targetKind, targetIndex, receiverIndex) {
|
|
796
|
+
const raw = args[targetIndex];
|
|
797
|
+
if (!raw)
|
|
798
|
+
return;
|
|
799
|
+
const literal = reflectionTargetOf(raw);
|
|
800
|
+
if (literal === null)
|
|
801
|
+
return;
|
|
802
|
+
reflectiveRefs.push({
|
|
803
|
+
mechanism,
|
|
804
|
+
targetKind,
|
|
805
|
+
target: literal,
|
|
806
|
+
receiver: receiverIndex !== undefined ? args[receiverIndex] : undefined,
|
|
807
|
+
enclosingTypeId: enclosing.typeId,
|
|
808
|
+
enclosingMethodId: enclosing.methodId,
|
|
809
|
+
line: call.startPosition.row,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Extract the statically-known part of a run-time lookup target. Pure
|
|
814
|
+
* literals yield the whole value; `"pkg." + name` and `f"pkg.{x}"` yield
|
|
815
|
+
* the literal prefix, which is still useful for dependency reporting.
|
|
816
|
+
*/
|
|
817
|
+
function reflectionTargetOf(raw) {
|
|
818
|
+
const trimmed = raw.trim();
|
|
819
|
+
const quoted = unquote(trimmed);
|
|
820
|
+
if (quoted !== trimmed)
|
|
821
|
+
return quoted;
|
|
822
|
+
const prefix = trimmed.match(/^(?:[rbfu]{0,2})?["']([^"']+)["']\s*(?:\+|%|\.format)/);
|
|
823
|
+
return prefix ? (prefix[1] ?? null) : null;
|
|
824
|
+
}
|
|
825
|
+
walk(root, (node) => {
|
|
826
|
+
if (node.type !== "call")
|
|
827
|
+
return;
|
|
828
|
+
if (insideAny(node, SKIP_CALL_ANCESTORS))
|
|
829
|
+
return;
|
|
830
|
+
const fn = fieldChild(node, "function") ?? node.namedChildren[0];
|
|
831
|
+
if (!fn)
|
|
832
|
+
return;
|
|
833
|
+
const argsNode = fieldChild(node, "arguments");
|
|
834
|
+
const args = argsNode
|
|
835
|
+
? argsNode.namedChildren.filter((c) => c.type !== "comment").map((c) => c.text)
|
|
836
|
+
: [];
|
|
837
|
+
const enclosing = enclosingOf(node.startPosition.row);
|
|
838
|
+
const controlFlow = controlFlowOf(node, PY_FLOWS);
|
|
839
|
+
const base = {
|
|
840
|
+
line: node.startPosition.row,
|
|
841
|
+
argumentTexts: args,
|
|
842
|
+
enclosingTypeId: enclosing.typeId,
|
|
843
|
+
enclosingMethodId: enclosing.methodId,
|
|
844
|
+
controlFlow,
|
|
845
|
+
};
|
|
846
|
+
if (fn.type === "identifier") {
|
|
847
|
+
const name = fn.text;
|
|
848
|
+
const builtin = BUILTIN_TYPES.has(name);
|
|
849
|
+
callSites.push({
|
|
850
|
+
...base,
|
|
851
|
+
receiver: null,
|
|
852
|
+
name,
|
|
853
|
+
isConstructorCall: builtin || /^[A-Z][A-Za-z0-9_]*$/.test(name),
|
|
854
|
+
qualifiedReceiver: false,
|
|
855
|
+
});
|
|
856
|
+
if (name === "getattr" || name === "hasattr") {
|
|
857
|
+
recordReflection(node, args, enclosing, name, "method", 1, 0);
|
|
858
|
+
}
|
|
859
|
+
else if (name === "__import__") {
|
|
860
|
+
recordReflection(node, args, enclosing, name, "module", 0);
|
|
861
|
+
}
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
if (fn.type === "attribute") {
|
|
865
|
+
const object = fieldChild(fn, "object");
|
|
866
|
+
const attribute = fieldChild(fn, "attribute");
|
|
867
|
+
if (!attribute)
|
|
868
|
+
return;
|
|
869
|
+
const receiver = object ? object.text : null;
|
|
870
|
+
const name = attribute.text;
|
|
871
|
+
callSites.push({
|
|
872
|
+
...base,
|
|
873
|
+
receiver,
|
|
874
|
+
name,
|
|
875
|
+
isConstructorCall: false,
|
|
876
|
+
qualifiedReceiver: !!receiver && /^[A-Z]/.test(receiver),
|
|
877
|
+
});
|
|
878
|
+
const rootReceiver = object ? (object.type === "identifier" ? object.text : "") : "";
|
|
879
|
+
if (receiver === "importlib" && name === "import_module") {
|
|
880
|
+
recordReflection(node, args, enclosing, "importlib.import_module", "module", 0);
|
|
881
|
+
}
|
|
882
|
+
if (receiver === "sys.modules" || receiver === "sys.modules[__name__]") {
|
|
883
|
+
// `getattr(sys.modules[...], name)` is handled through getattr above.
|
|
884
|
+
}
|
|
885
|
+
if (HTTP_VERBS.has(name.toLowerCase()) && ROUTE_RECEIVERS.has(rootReceiver)) {
|
|
886
|
+
const path = args[0] ? unquote(args[0]) : "";
|
|
887
|
+
const handler = args[1] ? args[1].split(".").pop() ?? "" : "";
|
|
888
|
+
if (handler) {
|
|
889
|
+
routes.push({
|
|
890
|
+
framework: "http",
|
|
891
|
+
httpMethod: name.toUpperCase(),
|
|
892
|
+
path,
|
|
893
|
+
handlerName: handler,
|
|
894
|
+
moduleId,
|
|
895
|
+
enclosingTypeId: enclosing.typeId,
|
|
896
|
+
line: node.startPosition.row,
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
if (name === "add_url_rule" && args.length >= 3) {
|
|
901
|
+
const path = unquote(args[0] ?? "");
|
|
902
|
+
const handler = (args[2] ?? "").split(".").pop() ?? "";
|
|
903
|
+
if (handler) {
|
|
904
|
+
routes.push({
|
|
905
|
+
framework: "flask",
|
|
906
|
+
httpMethod: "ANY",
|
|
907
|
+
path,
|
|
908
|
+
handlerName: handler,
|
|
909
|
+
moduleId,
|
|
910
|
+
enclosingTypeId: enclosing.typeId,
|
|
911
|
+
line: node.startPosition.row,
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
callSites.push({
|
|
918
|
+
...base,
|
|
919
|
+
receiver: fn.text,
|
|
920
|
+
name: fn.text,
|
|
921
|
+
isConstructorCall: false,
|
|
922
|
+
qualifiedReceiver: true,
|
|
923
|
+
});
|
|
924
|
+
});
|
|
925
|
+
let entryPoint;
|
|
926
|
+
for (const statement of root.namedChildren) {
|
|
927
|
+
if (statement.type !== "if_statement")
|
|
928
|
+
continue;
|
|
929
|
+
const condition = fieldChild(statement, "condition");
|
|
930
|
+
if (condition && /__name__\s*==\s*["']__main__["']/.test(condition.text)) {
|
|
931
|
+
entryPoint = { kind: "cli", detail: "__main__" };
|
|
932
|
+
break;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
tree.delete();
|
|
936
|
+
return {
|
|
937
|
+
filePath,
|
|
938
|
+
language: "python",
|
|
939
|
+
packageName: null,
|
|
940
|
+
imports,
|
|
941
|
+
types,
|
|
942
|
+
methods,
|
|
943
|
+
fields,
|
|
944
|
+
callSites,
|
|
945
|
+
localsByMethod,
|
|
946
|
+
moduleId,
|
|
947
|
+
scopeTypes,
|
|
948
|
+
scopeModules,
|
|
949
|
+
reflectiveRefs,
|
|
950
|
+
injections,
|
|
951
|
+
routes,
|
|
952
|
+
entryPoint,
|
|
953
|
+
};
|
|
954
|
+
},
|
|
955
|
+
};
|
|
956
|
+
//# sourceMappingURL=python.js.map
|