devlensio 1.0.3 → 1.1.0
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/config/backcompat.test.d.ts +1 -0
- package/dist/config/backcompat.test.js +188 -0
- package/dist/config/index.d.ts +5 -3
- package/dist/config/index.js +37 -54
- package/dist/config/providers/file.d.ts +3 -1
- package/dist/config/providers/file.js +47 -53
- package/dist/config/providers/providers.default.js +0 -1
- package/dist/config/providers/request.js +1 -1
- package/dist/config/types.d.ts +0 -1
- package/dist/config/types.js +2 -18
- package/dist/graph/edges/callEdges.js +97 -5
- package/dist/graph/edges/callEdges.test.js +154 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/jobs/queue/memory.js +12 -1
- package/dist/parser/extractors/classes.js +23 -4
- package/dist/parser/extractors/functions.d.ts +10 -3
- package/dist/parser/extractors/functions.js +135 -65
- package/dist/parser/extractors/hooks.js +5 -29
- package/dist/parser/extractors/objectMethods.js +3 -4
- package/dist/parser/index.js +11 -6
- package/dist/parser/overloads.d.ts +2 -0
- package/dist/parser/overloads.js +96 -0
- package/dist/parser/overloads.test.d.ts +1 -0
- package/dist/parser/overloads.test.js +175 -0
- package/dist/parser/typeUtils.d.ts +2 -0
- package/dist/parser/typeUtils.js +2 -0
- package/dist/summarizer/providers/index.js +4 -4
- package/dist/summarizer/providers/openai.js +1 -1
- package/dist/types.d.ts +6 -0
- package/extractors/go/bin/darwin-amd64/devlens_go_extractor +0 -0
- package/extractors/go/bin/darwin-arm64/devlens_go_extractor +0 -0
- package/extractors/go/bin/linux-amd64/devlens_go_extractor +0 -0
- package/extractors/go/bin/linux-arm64/devlens_go_extractor +0 -0
- package/extractors/go/bin/windows-amd64/devlens_go_extractor.exe +0 -0
- package/extractors/go/calls.go +134 -2
- package/extractors/go/contract.go +1 -0
- package/extractors/go/exports.go +107 -0
- package/extractors/go/exports_test.go +64 -0
- package/extractors/go/extractor.go +1 -0
- package/extractors/go/lookup.go +63 -7
- package/extractors/go/nodes.go +28 -0
- package/extractors/go/parser.go +51 -8
- package/extractors/java/devlens_java_extractor.jar +0 -0
- package/extractors/java/src/devlens/extractor/ExportsMapBuilder.java +148 -0
- package/extractors/java/src/devlens/extractor/Extractor.java +34 -0
- package/extractors/java/src/devlens/extractor/LookupMaps.java +12 -0
- package/extractors/java/src/devlens/extractor/Overloads.java +290 -0
- package/extractors/java/src/devlens/extractor/Parser.java +39 -0
- package/extractors/java/src/devlens/extractor/TypeSolverFactory.java +9 -0
- package/extractors/java/src/devlens/extractor/edges/Calls.java +42 -36
- package/extractors/java/src/devlens/extractor/edges/Routes.java +10 -3
- package/extractors/python/devlens_extractors_python/edges/calls.py +127 -26
- package/extractors/python/devlens_extractors_python/exports_map.py +263 -0
- package/extractors/python/devlens_extractors_python/extractor.py +7 -1
- package/extractors/python/devlens_extractors_python/lookup.py +6 -0
- package/extractors/python/devlens_extractors_python/parser/__init__.py +6 -0
- package/extractors/python/devlens_extractors_python/parser/functions.py +103 -6
- package/extractors/python/devlens_extractors_python/parser/overloads.py +105 -0
- package/extractors/rust/bin/darwin-amd64/devlens_rust_extractor +0 -0
- package/extractors/rust/bin/darwin-arm64/devlens_rust_extractor +0 -0
- package/extractors/rust/bin/linux-amd64/devlens_rust_extractor +0 -0
- package/extractors/rust/bin/linux-arm64/devlens_rust_extractor +0 -0
- package/extractors/rust/bin/windows-amd64/devlens_rust_extractor.exe +0 -0
- package/extractors/rust/src/calls.rs +27 -16
- package/extractors/rust/src/contract.rs +2 -0
- package/extractors/rust/src/exports.rs +387 -0
- package/extractors/rust/src/exports_test.rs +105 -0
- package/extractors/rust/src/extractor.rs +2 -0
- package/extractors/rust/src/lookup.rs +60 -0
- package/extractors/rust/src/main.rs +4 -0
- package/extractors/rust/src/nodes.rs +19 -0
- package/extractors/rust/src/parser.rs +66 -1
- package/extractors/rust/src/routes.rs +2 -1
- package/package.json +10 -3
|
@@ -1,8 +1,89 @@
|
|
|
1
1
|
import { closestByPath } from "./utils.js";
|
|
2
|
+
// Arity compatibility: a candidate matches when the call provides at least all
|
|
3
|
+
// required (non-optional, non-rest) params, and does not exceed the declared
|
|
4
|
+
// count unless the candidate declares a rest parameter. Candidates without
|
|
5
|
+
// parameters metadata are permissive (unknown shape).
|
|
6
|
+
function arityMatches(candidate, argCount) {
|
|
7
|
+
const parameters = candidate.metadata?.parameters;
|
|
8
|
+
const params = candidate.metadata?.params;
|
|
9
|
+
if (!params && !parameters)
|
|
10
|
+
return true;
|
|
11
|
+
const total = parameters?.length ?? params?.length ?? 0;
|
|
12
|
+
const hasRest = parameters?.some((p) => p.isRest) ?? false;
|
|
13
|
+
const required = parameters
|
|
14
|
+
? parameters.filter((p) => !p.isOptional && !p.isRest).length
|
|
15
|
+
: total;
|
|
16
|
+
return argCount >= required && (hasRest || argCount <= total);
|
|
17
|
+
}
|
|
18
|
+
// Loose type agreement between a call-site arg type tag and a declared param
|
|
19
|
+
// type text. Both sides are cheap/heuristic (no type checker), so this scores
|
|
20
|
+
// rather than gates: unknown tags never match, text containment counts.
|
|
21
|
+
function typeAgrees(argType, paramType) {
|
|
22
|
+
if (!argType || argType === "unknown" || !paramType)
|
|
23
|
+
return false;
|
|
24
|
+
const p = paramType.trim();
|
|
25
|
+
return p === argType || p.includes(argType);
|
|
26
|
+
}
|
|
27
|
+
function typeScore(candidate, argTypes) {
|
|
28
|
+
const parameters = candidate.metadata?.parameters;
|
|
29
|
+
if (!parameters)
|
|
30
|
+
return 0;
|
|
31
|
+
let score = 0;
|
|
32
|
+
for (let i = 0; i < argTypes.length; i++) {
|
|
33
|
+
if (parameters[i] && typeAgrees(argTypes[i], parameters[i].type))
|
|
34
|
+
score++;
|
|
35
|
+
}
|
|
36
|
+
return score;
|
|
37
|
+
}
|
|
38
|
+
// Picks the right overload among same-name candidates using the call site's
|
|
39
|
+
// argument shape. Ladder:
|
|
40
|
+
// 1. no usable call shape (USES edges, spread calls, missing callSites)
|
|
41
|
+
// → legacy closestByPath behavior
|
|
42
|
+
// 2. arity filter — survivors whose declared params accept argCount
|
|
43
|
+
// 3. type tiebreak — among arity survivors, prefer the candidate with the
|
|
44
|
+
// highest arg-type agreement (handles same arity, different types)
|
|
45
|
+
// 4. still ambiguous → closestByPath among survivors
|
|
46
|
+
function resolveOverloadTarget(candidates, caller, callSite) {
|
|
47
|
+
if (candidates.length === 1)
|
|
48
|
+
return { target: candidates[0], matchedBy: "name" };
|
|
49
|
+
const usableShape = callSite
|
|
50
|
+
&& typeof callSite.argCount === "number"
|
|
51
|
+
&& !callSite.hasSpread;
|
|
52
|
+
if (!usableShape)
|
|
53
|
+
return { target: closestByPath(candidates, caller.filePath), matchedBy: "name" };
|
|
54
|
+
let pool = candidates.filter((c) => arityMatches(c, callSite.argCount));
|
|
55
|
+
if (pool.length === 0)
|
|
56
|
+
pool = candidates; // nothing accepts this arity — keep all
|
|
57
|
+
if (pool.length === 1)
|
|
58
|
+
return { target: pool[0], matchedBy: "arity" };
|
|
59
|
+
const argTypes = callSite.argTypes ?? [];
|
|
60
|
+
if (argTypes.some((t) => t && t !== "unknown")) {
|
|
61
|
+
let best = pool[0];
|
|
62
|
+
let bestScore = typeScore(pool[0], argTypes);
|
|
63
|
+
let tieAtBest = 1;
|
|
64
|
+
for (let i = 1; i < pool.length; i++) {
|
|
65
|
+
const s = typeScore(pool[i], argTypes);
|
|
66
|
+
if (s > bestScore) {
|
|
67
|
+
best = pool[i];
|
|
68
|
+
bestScore = s;
|
|
69
|
+
tieAtBest = 1;
|
|
70
|
+
}
|
|
71
|
+
else if (s === bestScore)
|
|
72
|
+
tieAtBest++;
|
|
73
|
+
}
|
|
74
|
+
if (bestScore > 0 && tieAtBest === 1) {
|
|
75
|
+
return { target: best, matchedBy: "signature" };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { target: closestByPath(pool, caller.filePath), matchedBy: "name" };
|
|
79
|
+
}
|
|
2
80
|
export function detectCallEdges(nodes, lookupMp) {
|
|
3
81
|
const edges = [];
|
|
4
82
|
// Dedup for THIRD_PARTY CALLS edges — one per (caller, target) pair
|
|
5
83
|
const createdThirdPartyEdges = new Set();
|
|
84
|
+
// Dedup for local CALLS/USES edges — overload call sites can resolve to
|
|
85
|
+
// the same target through different (name, arity) pairs
|
|
86
|
+
const createdLocalEdges = new Set();
|
|
6
87
|
// Accumulate resolved calls per node — written back to metadata after edge loop
|
|
7
88
|
const resolvedCallsMap = new Map();
|
|
8
89
|
// Lazily-created method nodes for default/namespace import member-access calls
|
|
@@ -34,7 +115,16 @@ export function detectCallEdges(nodes, lookupMp) {
|
|
|
34
115
|
if (!hasPrimary && !hasHooks)
|
|
35
116
|
continue;
|
|
36
117
|
// ── Primary names: both third-party and local edges ──────────────
|
|
37
|
-
|
|
118
|
+
// Structured callSites carry the argument shape for overload-aware
|
|
119
|
+
// resolution; fall back to bare calls/uses strings when absent.
|
|
120
|
+
const callSites = node.metadata.callSites;
|
|
121
|
+
// Each (name, arity) call site iterates separately — a caller that
|
|
122
|
+
// invokes two overloads of one name gets two edges.
|
|
123
|
+
const primaryCallSites = (callSites && callSites.length > 0)
|
|
124
|
+
? callSites
|
|
125
|
+
: (primaryNames ?? []).map((name) => ({ name, argCount: -1 }));
|
|
126
|
+
for (const callSite of primaryCallSites) {
|
|
127
|
+
const calledName = callSite.name;
|
|
38
128
|
// ── Third-party guard ─────────────────────────────────────────
|
|
39
129
|
// The alias map is keyed by node.filePath (relative) and populated
|
|
40
130
|
// by importEdges.ts (which runs first).
|
|
@@ -120,16 +210,18 @@ export function detectCallEdges(nodes, lookupMp) {
|
|
|
120
210
|
}
|
|
121
211
|
if (!targets || targets.length === 0)
|
|
122
212
|
continue;
|
|
123
|
-
const target = targets.
|
|
124
|
-
? targets[0]
|
|
125
|
-
: closestByPath(targets, node.filePath);
|
|
213
|
+
const { target, matchedBy } = resolveOverloadTarget(targets, node, callSite.argCount >= 0 ? callSite : undefined);
|
|
126
214
|
if (target.id === node.id)
|
|
127
215
|
continue; // skip self-reference
|
|
216
|
+
const localEdgeKey = `${node.id}→${target.id}:${edgeType}`;
|
|
217
|
+
if (createdLocalEdges.has(localEdgeKey))
|
|
218
|
+
continue;
|
|
219
|
+
createdLocalEdges.add(localEdgeKey);
|
|
128
220
|
edges.push({
|
|
129
221
|
from: node.id,
|
|
130
222
|
to: target.id,
|
|
131
223
|
type: edgeType,
|
|
132
|
-
metadata: { calledName: resolvedName },
|
|
224
|
+
metadata: { calledName: resolvedName, matchedBy },
|
|
133
225
|
});
|
|
134
226
|
if (!resolvedCallsMap.has(node.id))
|
|
135
227
|
resolvedCallsMap.set(node.id, []);
|
|
@@ -198,3 +198,157 @@ describe("detectCallEdges with class methods", () => {
|
|
|
198
198
|
deleteFakeRepo(repo);
|
|
199
199
|
});
|
|
200
200
|
});
|
|
201
|
+
// ─── Overload-aware resolution (same-name candidates in one file) ─────────────
|
|
202
|
+
describe("detectCallEdges with overloads", () => {
|
|
203
|
+
// Arity disambiguates same-name functions in one file
|
|
204
|
+
it("routes each call to the overload matching its argument count", () => {
|
|
205
|
+
const repo = createFakeRepo({
|
|
206
|
+
"src/fmt.ts": `
|
|
207
|
+
export function formatValue(v: string) { return v.trim(); }
|
|
208
|
+
export function formatValue(v: string, n: number) { return v.repeat(n); }
|
|
209
|
+
export function caller() {
|
|
210
|
+
formatValue("a");
|
|
211
|
+
formatValue("a", 3);
|
|
212
|
+
}
|
|
213
|
+
`,
|
|
214
|
+
});
|
|
215
|
+
const { edges } = detectCalls(repo);
|
|
216
|
+
const callEdges = edges.filter((e) => e.type === "CALLS" && e.metadata?.calledName === "formatValue");
|
|
217
|
+
const targets = callEdges.map((e) => e.to).sort();
|
|
218
|
+
expect(targets).toEqual([
|
|
219
|
+
"src/fmt.ts::formatValue#1",
|
|
220
|
+
"src/fmt.ts::formatValue#2",
|
|
221
|
+
]);
|
|
222
|
+
// resolution confidence recorded on the edge
|
|
223
|
+
expect(callEdges.every((e) => e.metadata?.matchedBy === "arity")).toBe(true);
|
|
224
|
+
deleteFakeRepo(repo);
|
|
225
|
+
});
|
|
226
|
+
// Same arity, different param types — literal arg types break the tie
|
|
227
|
+
it("resolves same-arity overloads via argument type tags", () => {
|
|
228
|
+
const repo = createFakeRepo({
|
|
229
|
+
"src/dec.ts": `
|
|
230
|
+
export function decode(input: string, mode: string) { return input; }
|
|
231
|
+
export function decode(input: number, mode: string) { return String(input); }
|
|
232
|
+
export function caller() {
|
|
233
|
+
decode("abc", "utf8");
|
|
234
|
+
decode(42, "utf8");
|
|
235
|
+
}
|
|
236
|
+
`,
|
|
237
|
+
});
|
|
238
|
+
const { edges } = detectCalls(repo);
|
|
239
|
+
const callEdges = edges.filter((e) => e.type === "CALLS" && e.metadata?.calledName === "decode");
|
|
240
|
+
// first call (string literal) → string overload; second (number literal) → number overload
|
|
241
|
+
const stringTargets = callEdges.filter((e) => e.metadata?.matchedBy === "signature").map((e) => e.to).sort();
|
|
242
|
+
expect(stringTargets.length).toBe(2);
|
|
243
|
+
expect(stringTargets[0]).not.toBe(stringTargets[1]);
|
|
244
|
+
deleteFakeRepo(repo);
|
|
245
|
+
});
|
|
246
|
+
// A caller can invoke two overloads of one name — two distinct edges
|
|
247
|
+
it("emits one edge per overload call site, not one per name", () => {
|
|
248
|
+
const repo = createFakeRepo({
|
|
249
|
+
"src/multi.ts": `
|
|
250
|
+
export function send(x: string) { return x; }
|
|
251
|
+
export function send(x: string, y: string) { return x + y; }
|
|
252
|
+
export function client() {
|
|
253
|
+
send("a");
|
|
254
|
+
send("a", "b");
|
|
255
|
+
}
|
|
256
|
+
`,
|
|
257
|
+
});
|
|
258
|
+
const { edges } = detectCalls(repo);
|
|
259
|
+
const callEdges = edges.filter((e) => e.type === "CALLS" && e.metadata?.calledName === "send");
|
|
260
|
+
expect(callEdges.length).toBe(2);
|
|
261
|
+
expect(new Set(callEdges.map((e) => e.to)).size).toBe(2);
|
|
262
|
+
deleteFakeRepo(repo);
|
|
263
|
+
});
|
|
264
|
+
// Spread calls have unknowable arity — must NOT confidently pick an overload
|
|
265
|
+
it("falls back to path proximity for spread calls", () => {
|
|
266
|
+
const repo = createFakeRepo({
|
|
267
|
+
"src/spread.ts": `
|
|
268
|
+
export function combine(a: string) { return a; }
|
|
269
|
+
export function combine(a: string, b: string) { return a + b; }
|
|
270
|
+
export function caller(...args: string[]) {
|
|
271
|
+
return combine(...args);
|
|
272
|
+
}
|
|
273
|
+
`,
|
|
274
|
+
});
|
|
275
|
+
const { edges } = detectCalls(repo);
|
|
276
|
+
const callEdges = edges.filter((e) => e.type === "CALLS" && e.metadata?.calledName === "combine");
|
|
277
|
+
expect(callEdges.length).toBe(1);
|
|
278
|
+
expect(callEdges[0].metadata?.matchedBy).toBe("name");
|
|
279
|
+
deleteFakeRepo(repo);
|
|
280
|
+
});
|
|
281
|
+
// Optional params: a 1-arg call may hit a 2-param overload with optional 2nd
|
|
282
|
+
it("lets optional parameters accept shorter calls via arity fallback", () => {
|
|
283
|
+
const repo = createFakeRepo({
|
|
284
|
+
"src/opt.ts": `
|
|
285
|
+
export function render(template: string, opts?: object) { return template; }
|
|
286
|
+
export function render(template: string, opts: object, mode: string) { return template; }
|
|
287
|
+
export function caller() {
|
|
288
|
+
render("t");
|
|
289
|
+
}
|
|
290
|
+
`,
|
|
291
|
+
});
|
|
292
|
+
const { edges } = detectCalls(repo);
|
|
293
|
+
const callEdges = edges.filter((e) => e.type === "CALLS" && e.metadata?.calledName === "render");
|
|
294
|
+
// 1-arg call: renderStrict requires 3 → excluded; render (optional) → match
|
|
295
|
+
expect(callEdges.length).toBe(1);
|
|
296
|
+
expect(callEdges[0].to).toBe("src/opt.ts::render#2");
|
|
297
|
+
deleteFakeRepo(repo);
|
|
298
|
+
});
|
|
299
|
+
// Rest params accept any extra arguments
|
|
300
|
+
it("lets rest parameters accept longer calls", () => {
|
|
301
|
+
const repo = createFakeRepo({
|
|
302
|
+
"src/rest.ts": `
|
|
303
|
+
export function sum(first: number, ...rest: number[]) { return first; }
|
|
304
|
+
export function pair(a: number, b: number) { return a; }
|
|
305
|
+
export function caller() {
|
|
306
|
+
sum(1, 2, 3, 4);
|
|
307
|
+
}
|
|
308
|
+
`,
|
|
309
|
+
});
|
|
310
|
+
const { edges } = detectCalls(repo);
|
|
311
|
+
const callEdges = edges.filter((e) => e.type === "CALLS" && e.metadata?.calledName === "sum");
|
|
312
|
+
expect(callEdges.length).toBe(1);
|
|
313
|
+
expect(callEdges[0].to).toContain("sum");
|
|
314
|
+
deleteFakeRepo(repo);
|
|
315
|
+
});
|
|
316
|
+
// Legacy behavior preserved when no overload shape exists
|
|
317
|
+
it("still resolves plain single-candidate calls without callSites metadata", () => {
|
|
318
|
+
const repo = createFakeRepo({
|
|
319
|
+
"src/plain.ts": `
|
|
320
|
+
export function helper(x: number) { return x; }
|
|
321
|
+
export function caller() { return helper(1); }
|
|
322
|
+
`,
|
|
323
|
+
});
|
|
324
|
+
const { edges } = detectCalls(repo);
|
|
325
|
+
expect(edges).toContainEqual(expect.objectContaining({
|
|
326
|
+
from: "src/plain.ts::caller",
|
|
327
|
+
to: "src/plain.ts::helper",
|
|
328
|
+
type: "CALLS",
|
|
329
|
+
}));
|
|
330
|
+
deleteFakeRepo(repo);
|
|
331
|
+
});
|
|
332
|
+
// Class method overloads resolve through dotted names
|
|
333
|
+
it("resolves class method overloads by arity through dotted names", () => {
|
|
334
|
+
const repo = createFakeRepo({
|
|
335
|
+
"src/svc.ts": `
|
|
336
|
+
export class Repo {
|
|
337
|
+
find(id: string) { return id; }
|
|
338
|
+
find(id: string, depth: number) { return id; }
|
|
339
|
+
}
|
|
340
|
+
export function caller() {
|
|
341
|
+
Repo.find("a");
|
|
342
|
+
Repo.find("a", 2);
|
|
343
|
+
}
|
|
344
|
+
`,
|
|
345
|
+
});
|
|
346
|
+
const { edges } = detectCalls(repo);
|
|
347
|
+
const callEdges = edges.filter((e) => e.type === "CALLS" && e.metadata?.calledName === "Repo.find");
|
|
348
|
+
expect(callEdges.map((e) => e.to).sort()).toEqual([
|
|
349
|
+
"src/svc.ts::Repo.find#1",
|
|
350
|
+
"src/svc.ts::Repo.find#2",
|
|
351
|
+
]);
|
|
352
|
+
deleteFakeRepo(repo);
|
|
353
|
+
});
|
|
354
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export * from "./pipeline/index.js";
|
|
|
9
9
|
export * from "./config/types.js";
|
|
10
10
|
export { queue } from "./jobs/index.js";
|
|
11
11
|
export { storage } from "./storage/index.js";
|
|
12
|
-
export { resolveConfig, initConfig, maskConfig, writeConfig, resolveAllProviders, setActiveProvider, removeProvider as removeProviderConfig, } from "./config/index.js";
|
|
12
|
+
export { resolveConfig, hasSummarizationConfigured, initConfig, maskConfig, writeConfig, resolveAllProviders, setActiveProvider, removeProvider as removeProviderConfig, } from "./config/index.js";
|
|
13
13
|
export type { AllProvidersResult } from "./config/index.js";
|
|
14
14
|
export { readPackageDependencies, categorizeLibrary } from "./graph/thirdPartyLibs.js";
|
|
15
15
|
export { analyzePipeline } from "./pipeline/index.js";
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ export * from "./config/types.js";
|
|
|
11
11
|
export { queue } from "./jobs/index.js";
|
|
12
12
|
export { storage } from "./storage/index.js";
|
|
13
13
|
// Config helpers
|
|
14
|
-
export { resolveConfig, initConfig, maskConfig, writeConfig, resolveAllProviders, setActiveProvider, removeProvider as removeProviderConfig, } from "./config/index.js";
|
|
14
|
+
export { resolveConfig, hasSummarizationConfigured, initConfig, maskConfig, writeConfig, resolveAllProviders, setActiveProvider, removeProvider as removeProviderConfig, } from "./config/index.js";
|
|
15
15
|
// Pre-scan helpers
|
|
16
16
|
export { readPackageDependencies, categorizeLibrary } from "./graph/thirdPartyLibs.js";
|
|
17
17
|
// Pipeline & analysis
|
|
@@ -116,7 +116,18 @@ export class InMemoryQueue {
|
|
|
116
116
|
this._markCancelled(jobId, true);
|
|
117
117
|
return true;
|
|
118
118
|
}
|
|
119
|
-
//
|
|
119
|
+
// Paused: NO runner loop is active (the summarizer returned at the
|
|
120
|
+
// pause checkpoint), so a cancelRequested flag would sit forever —
|
|
121
|
+
// resumeJob() even clears it. Mark cancelled right here. The
|
|
122
|
+
// checkpoint file stays on disk (cleanedUp=false), so a later
|
|
123
|
+
// summarize resumes from where it stopped.
|
|
124
|
+
if (job.status === "paused") {
|
|
125
|
+
job.cancelRequested = true;
|
|
126
|
+
job.pauseRequested = false;
|
|
127
|
+
this._markCancelled(jobId, false);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
// Running — signal the runner (checked between batches)
|
|
120
131
|
job.cancelRequested = true;
|
|
121
132
|
job.pauseRequested = false; // clear pause signal if both were set
|
|
122
133
|
console.log(`🚫 Cancel requested for job ${jobId}`);
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// Out of scope (deliberate): INTERFACE / ENUM / STRUCT nodes for JS/TS.
|
|
18
18
|
import { SyntaxKind } from "ts-morph";
|
|
19
19
|
import { detectFunctionDirective } from "../directives.js";
|
|
20
|
-
import {
|
|
20
|
+
import { extractCallsWithSites, hasErrorHandling, extractThrowStatements, } from "./functions.js";
|
|
21
21
|
import { extractParams, extractReturnTypeAnnotation, extractBareTypeNames, extractReferencedInterfaces, } from "../typeUtils.js";
|
|
22
22
|
// React lifecycle methods (incl. render) — flagged so consumers can tell
|
|
23
23
|
// framework-registered callbacks apart from ordinary methods.
|
|
@@ -79,6 +79,12 @@ function rewriteThisCalls(calls, className, methodNames) {
|
|
|
79
79
|
return `${className}.${rest.replace(/^#/, "")}`;
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
|
+
// Same rewrite as rewriteThisCalls but for structured CallSite records —
|
|
83
|
+
// the name must match the dotted METHOD node names for overload-aware
|
|
84
|
+
// resolution in callEdges.
|
|
85
|
+
function rewriteThisCallSites(callSites, className, methodNames) {
|
|
86
|
+
return callSites.map((cs) => ({ ...cs, name: rewriteThisCalls([cs.name], className, methodNames)[0] }));
|
|
87
|
+
}
|
|
82
88
|
function extractDecoratorNames(cls) {
|
|
83
89
|
if (typeof cls.getDecorators !== "function")
|
|
84
90
|
return [];
|
|
@@ -96,9 +102,8 @@ function buildMethodNode(file, className, methodName, method, methodNameSet, fil
|
|
|
96
102
|
const filePath = file.getFilePath();
|
|
97
103
|
const dotted = `${className}.${methodName}`;
|
|
98
104
|
const typedParams = extractParams(method);
|
|
99
|
-
const calls =
|
|
100
|
-
const
|
|
101
|
-
const apiCalls = extractApiCalls(method);
|
|
105
|
+
const { calls, callSites: rawSites, hookCalls, apiCalls } = extractCallsWithSites(method);
|
|
106
|
+
const callSites = rewriteThisCallSites(rawSites, className, methodNameSet);
|
|
102
107
|
const isAsync = typeof method.isAsync === "function" && method.isAsync();
|
|
103
108
|
const hasErrors = hasErrorHandling(method);
|
|
104
109
|
const throws = extractThrowStatements(method);
|
|
@@ -106,6 +111,7 @@ function buildMethodNode(file, className, methodName, method, methodNameSet, fil
|
|
|
106
111
|
const returnType = extractReturnTypeAnnotation(method);
|
|
107
112
|
const bareTypeNames = extractBareTypeNames([...typedParams.map((p) => p.type), returnType]);
|
|
108
113
|
const referencedTypes = extractReferencedInterfaces(file, bareTypeNames);
|
|
114
|
+
const isOverloadSignature = typeof method.getBody === "function" ? !method.getBody() : false;
|
|
109
115
|
return {
|
|
110
116
|
id: makeId(filePath, dotted),
|
|
111
117
|
name: dotted,
|
|
@@ -121,6 +127,8 @@ function buildMethodNode(file, className, methodName, method, methodNameSet, fil
|
|
|
121
127
|
returnType,
|
|
122
128
|
referencedTypes,
|
|
123
129
|
calls: rewriteThisCalls(calls, className, methodNameSet),
|
|
130
|
+
callSites,
|
|
131
|
+
...(isOverloadSignature && { isOverloadSignature: true }),
|
|
124
132
|
hookCalls,
|
|
125
133
|
apiCalls,
|
|
126
134
|
isAsync,
|
|
@@ -216,9 +224,20 @@ export function extractClasses(file, fileDirective = null) {
|
|
|
216
224
|
if (isHashPrivate)
|
|
217
225
|
node.metadata.isPrivate = true;
|
|
218
226
|
nodes.push(node);
|
|
227
|
+
// Overload signatures — getMethods() returns only implementations;
|
|
228
|
+
// signatures hang off getOverloads() and become body-less METHOD nodes.
|
|
229
|
+
for (const overload of m.getOverloads?.() ?? []) {
|
|
230
|
+
const ovNode = buildMethodNode(file, name, methodName, overload, methodNameSet, fileDirective);
|
|
231
|
+
if (isHashPrivate)
|
|
232
|
+
ovNode.metadata.isPrivate = true;
|
|
233
|
+
nodes.push(ovNode);
|
|
234
|
+
}
|
|
219
235
|
}
|
|
220
236
|
for (const c of cls.getConstructors()) {
|
|
221
237
|
nodes.push(buildMethodNode(file, name, "constructor", c, methodNameSet, fileDirective));
|
|
238
|
+
for (const overload of c.getOverloads?.() ?? []) {
|
|
239
|
+
nodes.push(buildMethodNode(file, name, "constructor", overload, methodNameSet, fileDirective));
|
|
240
|
+
}
|
|
222
241
|
}
|
|
223
242
|
for (const acc of cls.getGetAccessors()) {
|
|
224
243
|
const accName = acc.getName();
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { SourceFile } from "ts-morph";
|
|
2
|
-
import type { CodeNode } from "../../types.js";
|
|
2
|
+
import type { CallSite, CodeNode } from "../../types.js";
|
|
3
3
|
import { type RenderingBoundary } from "../directives.js";
|
|
4
4
|
export declare const JS_BUILTINS: Set<string>;
|
|
5
|
+
export interface ExtractedCallData {
|
|
6
|
+
calls: string[];
|
|
7
|
+
callSites: CallSite[];
|
|
8
|
+
hookCalls: string[];
|
|
9
|
+
apiCalls: string[];
|
|
10
|
+
dependencyNames: string[];
|
|
11
|
+
contextRefs: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare function extractCallsWithSites(node: any): ExtractedCallData;
|
|
5
14
|
export declare function extractFunctionCalls(node: any): string[];
|
|
6
|
-
export declare function extractHookCalls(node: any): string[];
|
|
7
|
-
export declare function extractApiCalls(node: any): string[];
|
|
8
15
|
export declare function hasErrorHandling(node: any): boolean;
|
|
9
16
|
export declare function extractThrowStatements(node: any): boolean;
|
|
10
17
|
export declare function extractFunctions(file: SourceFile, fileDirective?: RenderingBoundary): CodeNode[];
|