devlensio 1.0.4 → 1.1.1

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.
Files changed (73) hide show
  1. package/dist/extractors/runner.js +1 -0
  2. package/dist/extractors/types.d.ts +2 -1
  3. package/dist/graph/edges/callEdges.js +97 -5
  4. package/dist/graph/edges/callEdges.test.js +154 -0
  5. package/dist/parser/exportsMap.d.ts +9 -0
  6. package/dist/parser/exportsMap.js +349 -0
  7. package/dist/parser/exportsMap.test.d.ts +1 -0
  8. package/dist/parser/exportsMap.test.js +324 -0
  9. package/dist/parser/extractors/classes.js +23 -4
  10. package/dist/parser/extractors/functions.d.ts +10 -3
  11. package/dist/parser/extractors/functions.js +135 -65
  12. package/dist/parser/extractors/hooks.js +5 -29
  13. package/dist/parser/extractors/objectMethods.js +3 -4
  14. package/dist/parser/index.d.ts +2 -1
  15. package/dist/parser/index.js +44 -6
  16. package/dist/parser/overloads.d.ts +2 -0
  17. package/dist/parser/overloads.js +96 -0
  18. package/dist/parser/overloads.test.d.ts +1 -0
  19. package/dist/parser/overloads.test.js +175 -0
  20. package/dist/parser/typeUtils.d.ts +2 -0
  21. package/dist/parser/typeUtils.js +2 -0
  22. package/dist/pipeline/index.d.ts +2 -1
  23. package/dist/pipeline/index.js +1 -0
  24. package/dist/storage/fileStorage.d.ts +2 -1
  25. package/dist/storage/fileStorage.js +5 -1
  26. package/dist/storage/fileStorage.test.d.ts +1 -0
  27. package/dist/storage/fileStorage.test.js +112 -0
  28. package/dist/types.d.ts +10 -0
  29. package/extractors/go/bin/darwin-amd64/devlens_go_extractor +0 -0
  30. package/extractors/go/bin/darwin-arm64/devlens_go_extractor +0 -0
  31. package/extractors/go/bin/linux-amd64/devlens_go_extractor +0 -0
  32. package/extractors/go/bin/linux-arm64/devlens_go_extractor +0 -0
  33. package/extractors/go/bin/windows-amd64/devlens_go_extractor.exe +0 -0
  34. package/extractors/go/calls.go +134 -2
  35. package/extractors/go/contract.go +1 -0
  36. package/extractors/go/exports.go +107 -0
  37. package/extractors/go/exports_test.go +64 -0
  38. package/extractors/go/extractor.go +1 -0
  39. package/extractors/go/lookup.go +63 -7
  40. package/extractors/go/nodes.go +28 -0
  41. package/extractors/go/parser.go +51 -8
  42. package/extractors/java/devlens_java_extractor.jar +0 -0
  43. package/extractors/java/src/devlens/extractor/ExportsMapBuilder.java +148 -0
  44. package/extractors/java/src/devlens/extractor/Extractor.java +34 -0
  45. package/extractors/java/src/devlens/extractor/LookupMaps.java +12 -0
  46. package/extractors/java/src/devlens/extractor/Overloads.java +290 -0
  47. package/extractors/java/src/devlens/extractor/Parser.java +39 -0
  48. package/extractors/java/src/devlens/extractor/TypeSolverFactory.java +9 -0
  49. package/extractors/java/src/devlens/extractor/edges/Calls.java +42 -36
  50. package/extractors/java/src/devlens/extractor/edges/Routes.java +10 -3
  51. package/extractors/python/devlens_extractors_python/edges/calls.py +127 -26
  52. package/extractors/python/devlens_extractors_python/exports_map.py +263 -0
  53. package/extractors/python/devlens_extractors_python/extractor.py +7 -1
  54. package/extractors/python/devlens_extractors_python/lookup.py +6 -0
  55. package/extractors/python/devlens_extractors_python/parser/__init__.py +6 -0
  56. package/extractors/python/devlens_extractors_python/parser/functions.py +103 -6
  57. package/extractors/python/devlens_extractors_python/parser/overloads.py +105 -0
  58. package/extractors/rust/bin/darwin-amd64/devlens_rust_extractor +0 -0
  59. package/extractors/rust/bin/darwin-arm64/devlens_rust_extractor +0 -0
  60. package/extractors/rust/bin/linux-amd64/devlens_rust_extractor +0 -0
  61. package/extractors/rust/bin/linux-arm64/devlens_rust_extractor +0 -0
  62. package/extractors/rust/bin/windows-amd64/devlens_rust_extractor.exe +0 -0
  63. package/extractors/rust/src/calls.rs +27 -16
  64. package/extractors/rust/src/contract.rs +2 -0
  65. package/extractors/rust/src/exports.rs +387 -0
  66. package/extractors/rust/src/exports_test.rs +105 -0
  67. package/extractors/rust/src/extractor.rs +2 -0
  68. package/extractors/rust/src/lookup.rs +60 -0
  69. package/extractors/rust/src/main.rs +4 -0
  70. package/extractors/rust/src/nodes.rs +19 -0
  71. package/extractors/rust/src/parser.rs +66 -1
  72. package/extractors/rust/src/routes.rs +2 -1
  73. package/package.json +1 -1
@@ -0,0 +1,324 @@
1
+ import fs from "fs";
2
+ import os from "os";
3
+ import path from "path";
4
+ import { parseRepo } from "./index.js";
5
+ import { buildLookupMaps } from "../graph/buildLookup.js";
6
+ import { detectImportEdges } from "../graph/edges/importEdges.js";
7
+ import { detectCallEdges } from "../graph/edges/callEdges.js";
8
+ function createFakeRepo(files) {
9
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "devlens-exports-map-test-"));
10
+ for (const [filePath, content] of Object.entries(files)) {
11
+ const fullPath = path.join(tmpDir, filePath);
12
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
13
+ fs.writeFileSync(fullPath, content);
14
+ }
15
+ return tmpDir;
16
+ }
17
+ function deleteFakeRepo(repoPath) {
18
+ fs.rmSync(repoPath, { recursive: true, force: true });
19
+ }
20
+ describe("exports map (cross-repo linkage)", () => {
21
+ it("returns undefined when the repo has no package.json", () => {
22
+ const repo = createFakeRepo({
23
+ "src/a.ts": `export function alpha() { return 1; }\n`,
24
+ });
25
+ const result = parseRepo(repo);
26
+ expect(result.exports).toBeUndefined();
27
+ deleteFakeRepo(repo);
28
+ });
29
+ it("maps direct exports to single-element arrays of proper nodeIds", () => {
30
+ const repo = createFakeRepo({
31
+ "package.json": JSON.stringify({ name: "fakepkg", main: "./src/index.ts" }),
32
+ "src/index.ts": `export { alpha } from './a';\n`,
33
+ "src/a.ts": `export function alpha() { return 1; }\n`,
34
+ });
35
+ const { exports } = parseRepo(repo);
36
+ expect(exports).toBeDefined();
37
+ expect(exports.exports["."]["alpha"]).toEqual(["src/a.ts::alpha"]);
38
+ expect(exports.ambiguousNames).toEqual({});
39
+ deleteFakeRepo(repo);
40
+ });
41
+ it("flattens chained re-exports to the final definition node", () => {
42
+ const repo = createFakeRepo({
43
+ "package.json": JSON.stringify({ name: "fakepkg", main: "./src/index.ts" }),
44
+ "src/index.ts": `export { beta } from './helpers';\n`,
45
+ "src/helpers.ts": `export { beta } from './dateUtils';\n`,
46
+ "src/dateUtils.ts": `export function beta(d: string) { return d; }\n`,
47
+ });
48
+ const { exports } = parseRepo(repo);
49
+ expect(exports.exports["."]["beta"]).toEqual(["src/dateUtils.ts::beta"]);
50
+ deleteFakeRepo(repo);
51
+ });
52
+ it("keys the map by the name the importer sees when aliases are involved", () => {
53
+ const repo = createFakeRepo({
54
+ "package.json": JSON.stringify({ name: "fakepkg", main: "./src/index.ts" }),
55
+ "src/index.ts": `export { formatDate as fmtDate } from './dateUtils';\n`,
56
+ "src/dateUtils.ts": `export function formatDate(d: string) { return d; }\n`,
57
+ });
58
+ const { exports } = parseRepo(repo);
59
+ expect(exports.exports["."]["fmtDate"]).toEqual(["src/dateUtils.ts::formatDate"]);
60
+ expect(exports.exports["."]["formatDate"]).toBeUndefined();
61
+ deleteFakeRepo(repo);
62
+ });
63
+ it("handles the name-swap case via bindings, never by string matching", () => {
64
+ const repo = createFakeRepo({
65
+ "package.json": JSON.stringify({ name: "fakepkg", main: "./src/index.ts" }),
66
+ "src/index.ts": `
67
+ export { formatDate as fmtDate } from './dateUtils';
68
+ export { fmtDate as formatDate } from './misc';
69
+ `,
70
+ "src/dateUtils.ts": `export function formatDate(d: string) { return d; }\n`,
71
+ "src/misc.ts": `export function fmtDate(x: number) { return x; }\n`,
72
+ });
73
+ const { exports } = parseRepo(repo);
74
+ expect(exports.exports["."]["fmtDate"]).toEqual(["src/dateUtils.ts::formatDate"]);
75
+ expect(exports.exports["."]["formatDate"]).toEqual(["src/misc.ts::fmtDate"]);
76
+ expect(exports.ambiguousNames).toEqual({});
77
+ deleteFakeRepo(repo);
78
+ });
79
+ it("emits one map key per package.json exports subpath", () => {
80
+ const repo = createFakeRepo({
81
+ "package.json": JSON.stringify({
82
+ name: "fakepkg",
83
+ exports: {
84
+ ".": "./src/index.ts",
85
+ "./server": "./src/server/index.ts",
86
+ },
87
+ }),
88
+ "src/index.ts": `export { alpha } from './a';\n`,
89
+ "src/a.ts": `export function alpha() { return 1; }\n`,
90
+ "src/server/index.ts": `export { serve } from './serve';\n`,
91
+ "src/server/serve.ts": `export function serve() { return 2; }\n`,
92
+ });
93
+ const { exports } = parseRepo(repo);
94
+ expect(exports.exports["."]["alpha"]).toEqual(["src/a.ts::alpha"]);
95
+ expect(exports.exports["./server"]["serve"]).toEqual(["src/server/serve.ts::serve"]);
96
+ deleteFakeRepo(repo);
97
+ });
98
+ it("resolves dist entry targets back to src (same coordinate system as nodes)", () => {
99
+ const repo = createFakeRepo({
100
+ "package.json": JSON.stringify({
101
+ name: "fakepkg",
102
+ exports: { ".": { import: "./dist/index.js", types: "./dist/index.d.ts" } },
103
+ }),
104
+ "src/index.ts": `export { alpha } from './a';\n`,
105
+ "src/a.ts": `export function alpha() { return 1; }\n`,
106
+ });
107
+ const { exports } = parseRepo(repo);
108
+ expect(exports.exports["."]["alpha"]).toEqual(["src/a.ts::alpha"]);
109
+ deleteFakeRepo(repo);
110
+ });
111
+ it("falls back to main/module when the exports field is absent", () => {
112
+ const repo = createFakeRepo({
113
+ "package.json": JSON.stringify({ name: "fakepkg", main: "./dist/index.js" }),
114
+ "src/index.ts": `export { alpha } from './a';\n`,
115
+ "src/a.ts": `export function alpha() { return 1; }\n`,
116
+ });
117
+ const { exports } = parseRepo(repo);
118
+ expect(exports.exports["."]["alpha"]).toEqual(["src/a.ts::alpha"]);
119
+ deleteFakeRepo(repo);
120
+ });
121
+ it("emits distinct #arity suffixed nodeIds for same-name different-arity exports", () => {
122
+ const repo = createFakeRepo({
123
+ "package.json": JSON.stringify({ name: "fakepkg", main: "./src/index.ts" }),
124
+ "src/index.ts": `export { formatValue } from './arity';\n`,
125
+ "src/arity.ts": `
126
+ export function formatValue(v: string) { return v.trim(); }
127
+ export function formatValue(v: string, n: number) { return v.repeat(n); }
128
+ `,
129
+ });
130
+ const { exports } = parseRepo(repo);
131
+ const ids = exports.exports["."]["formatValue"].sort();
132
+ expect(ids).toEqual(["src/arity.ts::formatValue#1", "src/arity.ts::formatValue#2"]);
133
+ expect(exports.ambiguousNames["."]).toBeUndefined();
134
+ deleteFakeRepo(repo);
135
+ });
136
+ it("emits distinct sig8-suffixed nodeIds for same-arity different-type exports", () => {
137
+ const repo = createFakeRepo({
138
+ "package.json": JSON.stringify({ name: "fakepkg", main: "./src/index.ts" }),
139
+ "src/index.ts": `export { decode } from './types';\n`,
140
+ "src/types.ts": `
141
+ export function decode(input: string, mode: string) { return input; }
142
+ export function decode(input: number, mode: string) { return String(input); }
143
+ `,
144
+ });
145
+ const { exports } = parseRepo(repo);
146
+ const ids = exports.exports["."]["decode"].sort();
147
+ expect(ids.length).toBe(2);
148
+ expect(ids[0]).toMatch(/^src\/types\.ts::decode#2#[0-9a-f]{8}$/);
149
+ expect(ids[1]).toMatch(/^src\/types\.ts::decode#2#[0-9a-f]{8}$/);
150
+ expect(ids[0]).not.toBe(ids[1]);
151
+ deleteFakeRepo(repo);
152
+ });
153
+ it("excludes star re-export conflicts per the JS module spec (name absent, not guessed)", () => {
154
+ const repo = createFakeRepo({
155
+ "package.json": JSON.stringify({ name: "fakepkg", main: "./src/index.ts" }),
156
+ "src/index.ts": `export * from './a';\nexport * from './b';\n`,
157
+ "src/a.ts": `export function clash() { return 1; }\nexport function onlyA() { return 2; }\n`,
158
+ "src/b.ts": `export function clash() { return 3; }\n`,
159
+ });
160
+ const { exports } = parseRepo(repo);
161
+ expect(exports.exports["."]["clash"]).toBeUndefined();
162
+ expect(exports.exports["."]["onlyA"]).toEqual(["src/a.ts::onlyA"]);
163
+ expect(exports.ambiguousNames["."]).toEqual(["clash"]);
164
+ deleteFakeRepo(repo);
165
+ });
166
+ it("resolves star re-exports like named ones", () => {
167
+ const repo = createFakeRepo({
168
+ "package.json": JSON.stringify({ name: "fakepkg", main: "./src/index.ts" }),
169
+ "src/index.ts": `export * from './barrel';\n`,
170
+ "src/barrel.ts": `export * from './a';\n`,
171
+ "src/a.ts": `export function alpha() { return 1; }\n`,
172
+ });
173
+ const { exports } = parseRepo(repo);
174
+ expect(exports.exports["."]["alpha"]).toEqual(["src/a.ts::alpha"]);
175
+ deleteFakeRepo(repo);
176
+ });
177
+ it("maps named default exports and omits anonymous ones", () => {
178
+ const repo = createFakeRepo({
179
+ "package.json": JSON.stringify({ name: "fakepkg", main: "./src/index.ts" }),
180
+ "src/index.ts": `
181
+ export { default as named } from './named';
182
+ export { default as anon } from './anon';
183
+ `,
184
+ "src/named.ts": `export default function loader() { return 1; }\n`,
185
+ "src/anon.ts": `export default () => 2;\n`,
186
+ });
187
+ const { exports } = parseRepo(repo);
188
+ expect(exports.exports["."]["named"]).toEqual(["src/named.ts::loader"]);
189
+ expect(exports.exports["."]["anon"]).toBeUndefined();
190
+ deleteFakeRepo(repo);
191
+ });
192
+ it("omits exports that have no node representation (consts, types, interfaces)", () => {
193
+ const repo = createFakeRepo({
194
+ "package.json": JSON.stringify({ name: "fakepkg", main: "./src/index.ts" }),
195
+ "src/index.ts": `export * from './mixed';\n`,
196
+ "src/mixed.ts": `
197
+ export const config = { a: 1 };
198
+ export type Maybe = string | null;
199
+ export interface Opts { a: number }
200
+ export function run(o: Opts) { return o.a; }
201
+ `,
202
+ });
203
+ const { exports } = parseRepo(repo);
204
+ expect(exports.exports["."]["run"]).toEqual(["src/mixed.ts::run"]);
205
+ expect(exports.exports["."]["config"]).toBeUndefined();
206
+ expect(exports.exports["."]["Maybe"]).toBeUndefined();
207
+ deleteFakeRepo(repo);
208
+ });
209
+ it("survives re-export cycles without hanging and omits the undefined name", () => {
210
+ const repo = createFakeRepo({
211
+ "package.json": JSON.stringify({ name: "fakepkg", main: "./src/index.ts" }),
212
+ "src/index.ts": `export { gone } from './a';\n`,
213
+ "src/a.ts": `export { gone } from './b';\n`,
214
+ "src/b.ts": `export { gone } from './a';\n`,
215
+ });
216
+ const { exports } = parseRepo(repo);
217
+ const gone = exports ? exports.exports["."]?.["gone"] : undefined;
218
+ expect(gone).toBeUndefined();
219
+ deleteFakeRepo(repo);
220
+ });
221
+ it("maps exported classes and exported const arrow functions to their nodes", () => {
222
+ const repo = createFakeRepo({
223
+ "package.json": JSON.stringify({ name: "fakepkg", main: "./src/index.ts" }),
224
+ "src/index.ts": `export * from './things';\n`,
225
+ "src/things.ts": `
226
+ export class Widget { render() { return 1; } }
227
+ export const handler = () => 2;
228
+ `,
229
+ });
230
+ const { exports } = parseRepo(repo);
231
+ expect(exports.exports["."]["Widget"]).toEqual(["src/things.ts::Widget"]);
232
+ expect(exports.exports["."]["handler"]).toEqual(["src/things.ts::handler"]);
233
+ deleteFakeRepo(repo);
234
+ });
235
+ it("never references a nodeId that is absent from the nodes array", () => {
236
+ const repo = createFakeRepo({
237
+ "package.json": JSON.stringify({ name: "fakepkg", main: "./src/index.ts" }),
238
+ "src/index.ts": `export * from './a';\nexport { beta } from './b';\n`,
239
+ "src/a.ts": `export function alpha() { return 1; }\n`,
240
+ "src/b.ts": `export function beta() { return 2; }\n`,
241
+ });
242
+ const result = parseRepo(repo);
243
+ const nodeIds = new Set(result.nodes.map((n) => n.id));
244
+ for (const perPath of Object.values(result.exports.exports)) {
245
+ for (const ids of Object.values(perPath)) {
246
+ for (const id of ids)
247
+ expect(nodeIds.has(id)).toBe(true);
248
+ }
249
+ }
250
+ deleteFakeRepo(repo);
251
+ });
252
+ it("resolves one condition per subpath: import beats require and types", () => {
253
+ const repo = createFakeRepo({
254
+ "package.json": JSON.stringify({
255
+ name: "fakepkg",
256
+ exports: {
257
+ ".": { import: "./src/index.ts", require: "./src/legacy.ts", types: "./src/index.d.ts" },
258
+ "./legacy": { require: "./src/legacy.ts" },
259
+ "./types-only": { types: "./src/index.d.ts" },
260
+ },
261
+ }),
262
+ "src/index.ts": `export { alpha } from './a';\n`,
263
+ "src/a.ts": `export function alpha() { return 1; }\n`,
264
+ "src/legacy.ts": `export function alpha() { return 2; }\n`,
265
+ "src/index.d.ts": `export declare function alpha(): number;\n`,
266
+ });
267
+ const { exports } = parseRepo(repo);
268
+ expect(exports.exports["."]["alpha"]).toEqual(["src/a.ts::alpha"]);
269
+ expect(exports.exports["./legacy"]["alpha"]).toEqual(["src/legacy.ts::alpha"]);
270
+ expect(exports.exports["./types-only"]).toBeUndefined();
271
+ deleteFakeRepo(repo);
272
+ });
273
+ it("skips unparseable files and still builds the map from valid ones", () => {
274
+ const repo = createFakeRepo({
275
+ "package.json": JSON.stringify({ name: "fakepkg", main: "./src/index.ts" }),
276
+ "src/index.ts": `export { alpha } from './a';\n`,
277
+ "src/a.ts": `export function alpha() { return 1; }\n`,
278
+ "src/broken.ts": `export function broken( {\n`,
279
+ });
280
+ const result = parseRepo(repo);
281
+ expect(result.exports).toBeDefined();
282
+ expect(result.exports.exports["."]["alpha"]).toEqual(["src/a.ts::alpha"]);
283
+ expect(result.exports.exports["."]["broken"]).toBeUndefined();
284
+ deleteFakeRepo(repo);
285
+ });
286
+ it("call-site narrowing picks within an exported overload group (map ids = edge targets)", () => {
287
+ const repo = createFakeRepo({
288
+ "package.json": JSON.stringify({ name: "fakepkg", main: "./src/index.ts" }),
289
+ "src/index.ts": `export { formatValue } from './overloads';\n`,
290
+ "src/overloads.ts": `
291
+ export function formatValue(v: string) { return v.trim(); }
292
+ export function formatValue(v: string, n: number) { return v.repeat(n); }
293
+ `,
294
+ "src/caller.ts": `
295
+ import { formatValue } from './index';
296
+ export function useOne() { return formatValue("x"); }
297
+ export function useTwo() { return formatValue("x", 3); }
298
+ `,
299
+ });
300
+ const parsed = parseRepo(repo);
301
+ const lookup = buildLookupMaps(parsed.nodes);
302
+ detectImportEdges(lookup, repo);
303
+ const callResult = detectCallEdges(parsed.nodes, lookup);
304
+ const { exports } = parsed;
305
+ const group = exports.exports["."]["formatValue"];
306
+ expect(group.length).toBe(2);
307
+ const targets = callResult.edges
308
+ .filter((e) => e.type === "CALLS" && e.from === "src/caller.ts::useOne")
309
+ .map((e) => e.to);
310
+ expect(targets.length).toBe(1);
311
+ expect(group).toContain(targets[0]);
312
+ const targetsTwo = callResult.edges
313
+ .filter((e) => e.type === "CALLS" && e.from === "src/caller.ts::useTwo")
314
+ .map((e) => e.to);
315
+ expect(targetsTwo.length).toBe(1);
316
+ expect(group).toContain(targetsTwo[0]);
317
+ expect(targets[0]).not.toBe(targetsTwo[0]);
318
+ // every narrowed target is a real node the map points at — no phantom ids
319
+ const nodeIds = new Set(parsed.nodes.map((n) => n.id));
320
+ for (const t of [...targets, ...targetsTwo])
321
+ expect(nodeIds.has(t)).toBe(true);
322
+ deleteFakeRepo(repo);
323
+ });
324
+ });
@@ -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 { extractFunctionCalls, extractHookCalls, extractApiCalls, hasErrorHandling, extractThrowStatements, } from "./functions.js";
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 = extractFunctionCalls(method);
100
- const hookCalls = extractHookCalls(method);
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[];