context-slice 1.8.2
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/LICENSE +21 -0
- package/README.md +349 -0
- package/dist/src/cli.js +199 -0
- package/dist/src/indexer/index.js +325 -0
- package/dist/src/languages/adapter.js +23 -0
- package/dist/src/languages/go/index.js +15 -0
- package/dist/src/languages/go/parse.js +443 -0
- package/dist/src/languages/go/resolve.js +313 -0
- package/dist/src/languages/java/enterprise/dependency-injection.js +175 -0
- package/dist/src/languages/java/enterprise/jpa-entity.js +89 -0
- package/dist/src/languages/java/enterprise/registry.js +22 -0
- package/dist/src/languages/java/enterprise/spring-data.js +239 -0
- package/dist/src/languages/java/enterprise/spring-mvc.js +183 -0
- package/dist/src/languages/java/enterprise/transactions.js +110 -0
- package/dist/src/languages/java.js +84 -0
- package/dist/src/languages/javascript/index.js +25 -0
- package/dist/src/languages/python/index.js +29 -0
- package/dist/src/languages/python/parse.js +415 -0
- package/dist/src/languages/python/resolve.js +413 -0
- package/dist/src/languages/rust/calls-resolve.js +1405 -0
- package/dist/src/languages/rust/index.js +12 -0
- package/dist/src/languages/rust/parse.js +545 -0
- package/dist/src/languages/rust/resolve.js +284 -0
- package/dist/src/languages/typescript/index.js +25 -0
- package/dist/src/languages/typescript/parse.js +793 -0
- package/dist/src/languages/typescript/resolve.js +463 -0
- package/dist/src/package-info.js +16 -0
- package/dist/src/parser/java-parser.js +339 -0
- package/dist/src/planner/budget.js +1 -0
- package/dist/src/planner/composition.js +372 -0
- package/dist/src/planner/rank.js +10 -0
- package/dist/src/render/compact-context.js +11 -0
- package/dist/src/server/mcp-server.js +154 -0
- package/dist/src/storage/sqlite.js +105 -0
- package/dist/src/types/enterprise.js +1 -0
- package/dist/src/types/model.js +1 -0
- package/dist/src/workflow/errors.js +10 -0
- package/dist/src/workflow/preview.js +220 -0
- package/dist/src/workflow/repository.js +56 -0
- package/mcp.json +10 -0
- package/package.json +76 -0
- package/plugin.json +15 -0
- package/queries/java/annotations.scm +1 -0
- package/queries/java/calls.scm +1 -0
- package/queries/java/imports.scm +1 -0
- package/queries/java/symbols.scm +2 -0
- package/skills/context-slice/SKILL.md +60 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
2
|
+
const PACKAGE_MARKER = "__init__.py";
|
|
3
|
+
/**
|
|
4
|
+
* Map every indexed file to the dotted module path an import would use.
|
|
5
|
+
* A directory is part of a package while it holds `__init__.py`; everything
|
|
6
|
+
* above that is a source root, which covers the common `src/` layout.
|
|
7
|
+
*/
|
|
8
|
+
export function moduleIndex(files) {
|
|
9
|
+
const packageDirectories = new Set(files
|
|
10
|
+
.filter((file) => file.endsWith(`/${PACKAGE_MARKER}`) || file === PACKAGE_MARKER)
|
|
11
|
+
.map((file) => posix.dirname(file)));
|
|
12
|
+
const byModule = new Map();
|
|
13
|
+
const byFile = new Map();
|
|
14
|
+
for (const file of files) {
|
|
15
|
+
const directory = posix.dirname(file);
|
|
16
|
+
const segments = directory === "." ? [] : directory.split("/");
|
|
17
|
+
// Anchor at the highest ancestor that is a package. Directories below it
|
|
18
|
+
// without `__init__.py` still count, which covers namespace-style layouts
|
|
19
|
+
// and keeps `src/` out of the module path.
|
|
20
|
+
let anchor = -1;
|
|
21
|
+
for (let at = 1; at <= segments.length; at++)
|
|
22
|
+
if (packageDirectories.has(segments.slice(0, at).join("/"))) {
|
|
23
|
+
anchor = at - 1;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
const packageSegments = anchor < 0 ? [] : segments.slice(anchor);
|
|
27
|
+
const base = posix.basename(file, ".py");
|
|
28
|
+
const parts = base === "__init__" ? packageSegments : [...packageSegments, base];
|
|
29
|
+
const module = parts.join(".");
|
|
30
|
+
if (module) {
|
|
31
|
+
byModule.set(module, file);
|
|
32
|
+
byFile.set(file, { module, packagePath: packageSegments });
|
|
33
|
+
}
|
|
34
|
+
else
|
|
35
|
+
byFile.set(file, { module: "", packagePath: [] });
|
|
36
|
+
}
|
|
37
|
+
return { byModule, byFile };
|
|
38
|
+
}
|
|
39
|
+
/** Resolve an import specifier to an indexed file, or report it external. */
|
|
40
|
+
export function resolveModule(specifier, fromFile, index) {
|
|
41
|
+
const relativeLevel = specifier.match(/^\.+/)?.[0].length ?? 0;
|
|
42
|
+
if (relativeLevel > 0) {
|
|
43
|
+
const here = index.byFile.get(fromFile);
|
|
44
|
+
if (!here)
|
|
45
|
+
return {};
|
|
46
|
+
const isPackageInit = posix.basename(fromFile) === PACKAGE_MARKER;
|
|
47
|
+
const base = isPackageInit
|
|
48
|
+
? here.packagePath
|
|
49
|
+
: here.packagePath.slice(0, here.packagePath.length);
|
|
50
|
+
const up = base.slice(0, Math.max(0, base.length - (relativeLevel - 1)));
|
|
51
|
+
const rest = specifier.slice(relativeLevel).split(".").filter(Boolean);
|
|
52
|
+
const module = [...up, ...rest].join(".");
|
|
53
|
+
const file = index.byModule.get(module);
|
|
54
|
+
return file ? { file } : {};
|
|
55
|
+
}
|
|
56
|
+
const file = index.byModule.get(specifier);
|
|
57
|
+
if (file)
|
|
58
|
+
return { file };
|
|
59
|
+
// `package.module` where only `package` is indexed is still repository source.
|
|
60
|
+
const head = specifier.split(".")[0];
|
|
61
|
+
if (index.byModule.has(head))
|
|
62
|
+
return {};
|
|
63
|
+
return { externalPackage: head };
|
|
64
|
+
}
|
|
65
|
+
/** Common builtins: unresolved, but not evidence of dynamic uncertainty. */
|
|
66
|
+
const BUILTINS = new Set([
|
|
67
|
+
"bool",
|
|
68
|
+
"bytes",
|
|
69
|
+
"dict",
|
|
70
|
+
"enumerate",
|
|
71
|
+
"float",
|
|
72
|
+
"format",
|
|
73
|
+
"frozenset",
|
|
74
|
+
"getattr",
|
|
75
|
+
"hasattr",
|
|
76
|
+
"int",
|
|
77
|
+
"isinstance",
|
|
78
|
+
"issubclass",
|
|
79
|
+
"iter",
|
|
80
|
+
"len",
|
|
81
|
+
"list",
|
|
82
|
+
"map",
|
|
83
|
+
"max",
|
|
84
|
+
"min",
|
|
85
|
+
"next",
|
|
86
|
+
"open",
|
|
87
|
+
"print",
|
|
88
|
+
"range",
|
|
89
|
+
"repr",
|
|
90
|
+
"reversed",
|
|
91
|
+
"round",
|
|
92
|
+
"set",
|
|
93
|
+
"setattr",
|
|
94
|
+
"sorted",
|
|
95
|
+
"str",
|
|
96
|
+
"sum",
|
|
97
|
+
"super",
|
|
98
|
+
"tuple",
|
|
99
|
+
"type",
|
|
100
|
+
"zip",
|
|
101
|
+
]);
|
|
102
|
+
const CALLABLE = new Set([
|
|
103
|
+
"function",
|
|
104
|
+
"method",
|
|
105
|
+
"constructor",
|
|
106
|
+
"getter",
|
|
107
|
+
"setter",
|
|
108
|
+
]);
|
|
109
|
+
export function pythonGraph(context) {
|
|
110
|
+
const { symbols, imports, exports } = context;
|
|
111
|
+
const files = [...new Set(symbols.map((symbol) => symbol.filePath))];
|
|
112
|
+
for (const record of [...imports, ...exports])
|
|
113
|
+
files.push(record.filePath);
|
|
114
|
+
const modules = moduleIndex([...new Set(files)]);
|
|
115
|
+
const symbolsById = new Map(symbols.map((symbol) => [symbol.id, symbol]));
|
|
116
|
+
const symbolsByFile = new Map();
|
|
117
|
+
for (const symbol of symbols) {
|
|
118
|
+
const list = symbolsByFile.get(symbol.filePath) ?? [];
|
|
119
|
+
list.push(symbol);
|
|
120
|
+
symbolsByFile.set(symbol.filePath, list);
|
|
121
|
+
}
|
|
122
|
+
const importsByFile = new Map();
|
|
123
|
+
const unresolvedImports = [];
|
|
124
|
+
for (const record of imports) {
|
|
125
|
+
const resolved = resolveModule(record.module, record.filePath, modules);
|
|
126
|
+
record.resolvedFile = resolved.file;
|
|
127
|
+
record.externalPackage = resolved.externalPackage;
|
|
128
|
+
if (!resolved.file && !resolved.externalPackage)
|
|
129
|
+
unresolvedImports.push(record);
|
|
130
|
+
const list = importsByFile.get(record.filePath) ?? [];
|
|
131
|
+
list.push(record);
|
|
132
|
+
importsByFile.set(record.filePath, list);
|
|
133
|
+
}
|
|
134
|
+
for (const record of exports)
|
|
135
|
+
if (record.fromModule)
|
|
136
|
+
record.resolvedFile = resolveModule(record.fromModule, record.filePath, modules).file;
|
|
137
|
+
const exportsByFileRaw = new Map();
|
|
138
|
+
for (const record of exports) {
|
|
139
|
+
const list = exportsByFileRaw.get(record.filePath) ?? [];
|
|
140
|
+
list.push(record);
|
|
141
|
+
exportsByFileRaw.set(record.filePath, list);
|
|
142
|
+
}
|
|
143
|
+
const cyclicExports = [];
|
|
144
|
+
/** Follow `__init__.py` re-export chains; `seen` breaks package cycles. */
|
|
145
|
+
const lookup = (file, name, seen) => {
|
|
146
|
+
const key = `${file}#${name}`;
|
|
147
|
+
if (seen.has(key)) {
|
|
148
|
+
cyclicExports.push(key);
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
seen.add(key);
|
|
152
|
+
const records = exportsByFileRaw.get(file) ?? [];
|
|
153
|
+
const direct = records.find((record) => record.exportedName === name && record.symbolId);
|
|
154
|
+
if (direct?.symbolId)
|
|
155
|
+
return direct.symbolId;
|
|
156
|
+
const reexport = records.find((record) => record.exportedName === name && record.fromModule);
|
|
157
|
+
if (reexport?.resolvedFile)
|
|
158
|
+
return lookup(reexport.resolvedFile, reexport.sourceName ?? name, seen);
|
|
159
|
+
for (const wildcard of records.filter((record) => record.wildcard))
|
|
160
|
+
if (wildcard.resolvedFile) {
|
|
161
|
+
const found = lookup(wildcard.resolvedFile, name, seen);
|
|
162
|
+
if (found)
|
|
163
|
+
return found;
|
|
164
|
+
}
|
|
165
|
+
return undefined;
|
|
166
|
+
};
|
|
167
|
+
return {
|
|
168
|
+
modules,
|
|
169
|
+
symbolsById,
|
|
170
|
+
symbolsByFile,
|
|
171
|
+
importsByFile,
|
|
172
|
+
lookup,
|
|
173
|
+
cyclicExports: [...new Set(cyclicExports)],
|
|
174
|
+
unresolvedImports,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
export const ALL_PYTHON_RULES = {
|
|
178
|
+
selfFieldReceiver: true,
|
|
179
|
+
instanceReceiver: true,
|
|
180
|
+
classReceiver: true,
|
|
181
|
+
};
|
|
182
|
+
export function resolvePythonCalls(context, rules = ALL_PYTHON_RULES) {
|
|
183
|
+
const graph = pythonGraph(context);
|
|
184
|
+
const { symbolsById, symbolsByFile, importsByFile, lookup } = graph;
|
|
185
|
+
const ownerChain = (symbol) => {
|
|
186
|
+
const chain = [];
|
|
187
|
+
let current = symbol;
|
|
188
|
+
while (current?.parentId) {
|
|
189
|
+
current = symbolsById.get(current.parentId);
|
|
190
|
+
if (current)
|
|
191
|
+
chain.push(current);
|
|
192
|
+
}
|
|
193
|
+
return chain;
|
|
194
|
+
};
|
|
195
|
+
const memberOf = (container, name) => (symbolsByFile.get(container.filePath) ?? []).find((candidate) => candidate.parentId === container.id && candidate.name === name);
|
|
196
|
+
const moduleLevel = (file, name) => (symbolsByFile.get(file) ?? []).find((candidate) => candidate.name === name && !candidate.parentId);
|
|
197
|
+
/** Nearest lexical scope first, then module level. */
|
|
198
|
+
const localCallable = (file, name, caller) => {
|
|
199
|
+
const candidates = (symbolsByFile.get(file) ?? []).filter((candidate) => candidate.name === name &&
|
|
200
|
+
(CALLABLE.has(candidate.kind) || candidate.kind === "class"));
|
|
201
|
+
return (candidates.find((candidate) => candidate.parentId === caller?.id) ??
|
|
202
|
+
candidates.find((candidate) => Boolean(caller?.parentId) && candidate.parentId === caller?.parentId) ??
|
|
203
|
+
candidates.find((candidate) => !candidate.parentId));
|
|
204
|
+
};
|
|
205
|
+
const importFor = (file, localName) => (importsByFile.get(file) ?? []).find((record) => record.localName === localName);
|
|
206
|
+
const importedSymbol = (record, name) => record.resolvedFile
|
|
207
|
+
? symbolsById.get(lookup(record.resolvedFile, name, new Set()) ?? "")
|
|
208
|
+
: undefined;
|
|
209
|
+
/** A class named here or imported. */
|
|
210
|
+
const classNamed = (file, name) => {
|
|
211
|
+
const local = moduleLevel(file, name);
|
|
212
|
+
if (local?.kind === "class")
|
|
213
|
+
return local;
|
|
214
|
+
const record = importFor(file, name);
|
|
215
|
+
const target = record && importedSymbol(record, record.importedName ?? name);
|
|
216
|
+
return target?.kind === "class" ? target : undefined;
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* Type of a local binding, from `x = ClassName(...)` or an annotation.
|
|
220
|
+
* Reassignment to anything else drops the evidence.
|
|
221
|
+
*/
|
|
222
|
+
const bindingClass = (caller, receiver) => {
|
|
223
|
+
const body = caller.body ?? caller.source;
|
|
224
|
+
const assignments = [
|
|
225
|
+
...body.matchAll(new RegExp(`(?<![\\w.])${receiver}\\s*(?::\\s*([A-Za-z_][\\w.]*))?\\s*=\\s*([A-Za-z_][\\w.]*)?\\s*\\(?`, "g")),
|
|
226
|
+
];
|
|
227
|
+
if (assignments.length !== 1)
|
|
228
|
+
return undefined;
|
|
229
|
+
const [, annotation, constructed] = assignments[0];
|
|
230
|
+
const name = (annotation ?? constructed)?.split(".").pop();
|
|
231
|
+
return name ? classNamed(caller.filePath, name) : undefined;
|
|
232
|
+
};
|
|
233
|
+
/** `self.repo` typed by the constructor's annotation or assignment. */
|
|
234
|
+
const selfFieldClass = (caller, fieldName) => {
|
|
235
|
+
const [owner] = ownerChain(caller);
|
|
236
|
+
if (!owner)
|
|
237
|
+
return undefined;
|
|
238
|
+
const constructor = memberOf(owner, "__init__");
|
|
239
|
+
if (!constructor)
|
|
240
|
+
return undefined;
|
|
241
|
+
const annotation = constructor.signature?.match(new RegExp(`${fieldName}\\s*:\\s*([A-Za-z_][\\w.]*)`))?.[1];
|
|
242
|
+
const assigned = (constructor.body ?? "").match(new RegExp(`self\\.${fieldName}\\s*=\\s*([A-Za-z_][\\w.]*)\\s*\\(`))?.[1];
|
|
243
|
+
const name = (annotation ?? assigned)?.split(".").pop();
|
|
244
|
+
return name ? classNamed(caller.filePath, name) : undefined;
|
|
245
|
+
};
|
|
246
|
+
const settle = (call, target, kind, evidence, confidence = "exact") => {
|
|
247
|
+
call.declaredTargetId = target.id;
|
|
248
|
+
call.resolvedTargetId = target.id;
|
|
249
|
+
call.resolutionKind = kind;
|
|
250
|
+
call.confidence = confidence;
|
|
251
|
+
call.evidence = [evidence];
|
|
252
|
+
};
|
|
253
|
+
for (const call of context.calls) {
|
|
254
|
+
const caller = symbolsById.get(call.callerId);
|
|
255
|
+
if (!caller)
|
|
256
|
+
continue;
|
|
257
|
+
const file = caller.filePath;
|
|
258
|
+
const receiver = call.receiverText;
|
|
259
|
+
// Dynamic constructs never produce an exact edge.
|
|
260
|
+
if (["getattr", "setattr", "__import__"].includes(call.calleeName)) {
|
|
261
|
+
call.evidence = ["dynamic attribute access"];
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (receiver === "importlib") {
|
|
265
|
+
call.externalPackage = "importlib";
|
|
266
|
+
call.resolutionKind = "external-package";
|
|
267
|
+
call.evidence = ["dynamic import"];
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (receiver === "self" || receiver === "cls") {
|
|
271
|
+
for (const container of ownerChain(caller)) {
|
|
272
|
+
const target = memberOf(container, call.calleeName);
|
|
273
|
+
if (target) {
|
|
274
|
+
settle(call, target, "this-member", `${receiver}.${call.calleeName} resolves within ${container.name}`);
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (!call.resolvedTargetId)
|
|
279
|
+
call.evidence = [`${receiver} has no member of that name in scope`];
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (receiver?.startsWith("self.") && rules.selfFieldReceiver) {
|
|
283
|
+
const fieldName = receiver.slice("self.".length);
|
|
284
|
+
const container = selfFieldClass(caller, fieldName);
|
|
285
|
+
const target = container && memberOf(container, call.calleeName);
|
|
286
|
+
if (target)
|
|
287
|
+
settle(call, target, "declared-type", `self.${fieldName} is ${container.name} by constructor evidence`);
|
|
288
|
+
else
|
|
289
|
+
call.evidence = ["self attribute type is not determined statically"];
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (receiver) {
|
|
293
|
+
// `import package.module as mod` then `mod.call()`
|
|
294
|
+
const moduleImport = (importsByFile.get(file) ?? []).find((record) => record.kind === "namespace" && record.localName === receiver);
|
|
295
|
+
if (moduleImport) {
|
|
296
|
+
if (moduleImport.resolvedFile) {
|
|
297
|
+
const target = symbolsById.get(lookup(moduleImport.resolvedFile, call.calleeName, new Set()) ?? "");
|
|
298
|
+
if (target) {
|
|
299
|
+
settle(call, target, "namespace-import", `module ${moduleImport.module} exposes ${call.calleeName}`);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
else if (moduleImport.externalPackage) {
|
|
304
|
+
call.externalPackage = moduleImport.externalPackage;
|
|
305
|
+
call.resolutionKind = "external-package";
|
|
306
|
+
call.evidence = [`module from ${moduleImport.externalPackage}`];
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// `service = OrderService(); service.create()`
|
|
311
|
+
const container = rules.instanceReceiver
|
|
312
|
+
? bindingClass(caller, receiver)
|
|
313
|
+
: undefined;
|
|
314
|
+
const target = container && memberOf(container, call.calleeName);
|
|
315
|
+
if (target) {
|
|
316
|
+
settle(call, target, "declared-type", `${receiver} is ${container.name} at its only binding`);
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
// `OrderService.create()` on a known class.
|
|
320
|
+
const classReceiver = rules.classReceiver && /^[A-Z]/.test(receiver)
|
|
321
|
+
? classNamed(file, receiver)
|
|
322
|
+
: undefined;
|
|
323
|
+
const classTarget = classReceiver && memberOf(classReceiver, call.calleeName);
|
|
324
|
+
if (classTarget) {
|
|
325
|
+
settle(call, classTarget, "static", `class member of ${classReceiver.name}`);
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
const record = importFor(file, receiver);
|
|
329
|
+
if (record?.externalPackage) {
|
|
330
|
+
call.externalPackage = record.externalPackage;
|
|
331
|
+
call.resolutionKind = "external-package";
|
|
332
|
+
call.evidence = [`receiver from ${record.externalPackage}`];
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
call.evidence = ["receiver type is not determined statically"];
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
// `cls(...)` in a classmethod constructs the enclosing class.
|
|
339
|
+
if (call.calleeName === "cls") {
|
|
340
|
+
const [owner] = ownerChain(caller);
|
|
341
|
+
const constructor = owner && memberOf(owner, "__init__");
|
|
342
|
+
if (owner) {
|
|
343
|
+
settle(call, constructor ?? owner, "constructor", `cls(...) constructs ${owner.name}`);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// Bare call: lexical scope, then imports.
|
|
348
|
+
const local = localCallable(file, call.calleeName, caller);
|
|
349
|
+
if (local) {
|
|
350
|
+
const kind = local.kind === "class"
|
|
351
|
+
? "constructor"
|
|
352
|
+
: "same-file";
|
|
353
|
+
const constructorTarget = local.kind === "class" ? memberOf(local, "__init__") : undefined;
|
|
354
|
+
settle(call, constructorTarget ?? local, kind, local.kind === "class"
|
|
355
|
+
? `constructs ${local.name}`
|
|
356
|
+
: `unique same-module callable ${local.name}`);
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const record = importFor(file, call.calleeName);
|
|
360
|
+
if (record) {
|
|
361
|
+
const target = importedSymbol(record, record.importedName ?? call.calleeName);
|
|
362
|
+
if (target) {
|
|
363
|
+
const aliased = record.importedName !== record.localName;
|
|
364
|
+
const constructorTarget = target.kind === "class" ? memberOf(target, "__init__") : undefined;
|
|
365
|
+
settle(call, constructorTarget ?? target, aliased ? "aliased-import" : "imported", aliased
|
|
366
|
+
? `${record.localName} is an alias of ${record.importedName} from ${record.resolvedFile}`
|
|
367
|
+
: `imported ${record.importedName} from ${record.resolvedFile}`);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (record.externalPackage) {
|
|
371
|
+
call.externalPackage = record.externalPackage;
|
|
372
|
+
call.resolutionKind = "external-package";
|
|
373
|
+
call.evidence = [`imported from ${record.externalPackage}`];
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (!receiver && BUILTINS.has(call.calleeName)) {
|
|
378
|
+
call.externalPackage = "builtins";
|
|
379
|
+
call.resolutionKind = "external-package";
|
|
380
|
+
call.evidence = ["Python builtin"];
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
call.evidence = ["no same-module or imported callable of that name"];
|
|
384
|
+
}
|
|
385
|
+
return graph;
|
|
386
|
+
}
|
|
387
|
+
const rate = (part, total) => total === 0 ? 1 : Number((part / total).toFixed(4));
|
|
388
|
+
export function pythonDiagnostics(context) {
|
|
389
|
+
const graph = pythonGraph(context);
|
|
390
|
+
const imports = context.imports;
|
|
391
|
+
const relative = imports.filter((record) => record.module.startsWith("."));
|
|
392
|
+
const reexports = context.exports.filter((record) => record.fromModule);
|
|
393
|
+
const calls = context.calls;
|
|
394
|
+
const kind = (name) => calls.filter((call) => call.resolutionKind === name).length;
|
|
395
|
+
const selfCalls = calls.filter((call) => call.receiverText === "self" || call.receiverText === "cls");
|
|
396
|
+
return {
|
|
397
|
+
importsTotal: imports.length,
|
|
398
|
+
importsResolved: imports.filter((record) => record.resolvedFile).length,
|
|
399
|
+
relativeImportResolutionRate: rate(relative.filter((record) => record.resolvedFile).length, relative.length),
|
|
400
|
+
packageReexportResolutionRate: rate(reexports.filter((record) => record.resolvedFile).length, reexports.length),
|
|
401
|
+
externalImports: imports.filter((record) => record.externalPackage).length,
|
|
402
|
+
unresolvedImports: graph.unresolvedImports.length,
|
|
403
|
+
cyclicExportChains: graph.cyclicExports.length,
|
|
404
|
+
selfMethodResolutionRate: rate(selfCalls.filter((call) => call.resolvedTargetId).length, selfCalls.length),
|
|
405
|
+
clsMethodResolutionRate: rate(calls.filter((call) => call.receiverText === "cls" && call.resolvedTargetId).length, calls.filter((call) => call.receiverText === "cls").length),
|
|
406
|
+
instanceReceiverResolutionRate: rate(kind("declared-type"), calls.filter((call) => call.receiverText &&
|
|
407
|
+
call.receiverText !== "self" &&
|
|
408
|
+
call.receiverText !== "cls").length),
|
|
409
|
+
dynamicUnresolvedRate: rate(calls.filter((call) => !call.resolvedTargetId && !call.externalPackage)
|
|
410
|
+
.length, calls.length),
|
|
411
|
+
decoratedSymbols: context.symbols.filter((symbol) => symbol.annotations.length > 0).length,
|
|
412
|
+
};
|
|
413
|
+
}
|