@shvmgyl15/tsgraph 0.1.0 → 0.2.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/changes/index.test.js +2 -6
- package/dist/changes/index.test.js.map +1 -1
- package/dist/cli/index.js +184 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/git/index.test.js +4 -6
- package/dist/git/index.test.js.map +1 -1
- package/dist/opencode/index.js +1 -1
- package/dist/opencode/index.js.map +1 -1
- package/dist/opencode/index.test.js +2 -2
- package/dist/opencode/index.test.js.map +1 -1
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/index.js +12 -4
- package/dist/search/index.js.map +1 -1
- package/package.json +16 -1
- package/AGENTS.md +0 -64
- package/TODOS.md +0 -61
- package/opencode.json +0 -24
- package/src/analysis/analysis.test.ts +0 -405
- package/src/analysis/complexity.ts +0 -107
- package/src/analysis/coupling.ts +0 -106
- package/src/analysis/hotspot.ts +0 -52
- package/src/analysis/index.ts +0 -17
- package/src/boundaries/index.test.ts +0 -335
- package/src/boundaries/index.ts +0 -137
- package/src/changes/index.test.ts +0 -114
- package/src/changes/index.ts +0 -95
- package/src/cli/index.ts +0 -736
- package/src/git/index.test.ts +0 -92
- package/src/git/index.ts +0 -86
- package/src/graph/types.test.ts +0 -383
- package/src/graph/types.ts +0 -353
- package/src/mcp/mcp.test.ts +0 -176
- package/src/mcp/server.ts +0 -217
- package/src/nextjs/index.ts +0 -23
- package/src/nextjs/nextjs.test.ts +0 -233
- package/src/nextjs/pages.ts +0 -43
- package/src/nextjs/react.ts +0 -100
- package/src/nextjs/router.ts +0 -102
- package/src/nextjs/routes.ts +0 -69
- package/src/opencode/index.test.ts +0 -90
- package/src/opencode/index.ts +0 -83
- package/src/parser/index.ts +0 -339
- package/src/parser/parser.test.ts +0 -282
- package/src/plan/index.test.ts +0 -162
- package/src/plan/index.ts +0 -161
- package/src/report/index.ts +0 -128
- package/src/scanner/index.ts +0 -97
- package/src/scanner/scanner.test.ts +0 -135
- package/src/search/index.ts +0 -163
- package/src/search/search.test.ts +0 -512
- package/src/traversal/index.ts +0 -5
- package/src/traversal/traversal.test.ts +0 -266
- package/src/traversal/traversal.ts +0 -185
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -7
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import {
|
|
6
|
-
makeGraph,
|
|
7
|
-
makeSymbolNode,
|
|
8
|
-
makeCallEdge,
|
|
9
|
-
makeTestEdge,
|
|
10
|
-
makeFileNode,
|
|
11
|
-
} from "../graph/types.js";
|
|
12
|
-
import { impact, findPath, findOrphans, trace } from "./traversal.js";
|
|
13
|
-
|
|
14
|
-
function createTempDir(): string {
|
|
15
|
-
return fs.mkdtempSync(path.join(os.tmpdir(), "tsgraph-test-"));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
describe("impact", () => {
|
|
19
|
-
it("finds direct callers", () => {
|
|
20
|
-
const graph = makeGraph({
|
|
21
|
-
symbols: [
|
|
22
|
-
makeSymbolNode({ id: "a", name: "a", kind: "function", file: "a.ts", line: 1, endLine: 1 }),
|
|
23
|
-
makeSymbolNode({ id: "b", name: "b", kind: "function", file: "b.ts", line: 1, endLine: 1 }),
|
|
24
|
-
makeSymbolNode({ id: "c", name: "c", kind: "function", file: "c.ts", line: 1, endLine: 1 }),
|
|
25
|
-
],
|
|
26
|
-
calls: [
|
|
27
|
-
makeCallEdge({ callerSymbolId: "b", callerName: "b", calleeRaw: "a", file: "b.ts", line: 1 }),
|
|
28
|
-
makeCallEdge({ callerSymbolId: "c", callerName: "c", calleeRaw: "a", file: "c.ts", line: 1 }),
|
|
29
|
-
],
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const results = impact(graph, "a");
|
|
33
|
-
expect(results).toHaveLength(2);
|
|
34
|
-
const names = results.map((r) => r.symbol.name).sort();
|
|
35
|
-
expect(names).toEqual(["b", "c"]);
|
|
36
|
-
expect(results.every((r) => r.depth === 1)).toBe(true);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("traverses multiple levels", () => {
|
|
40
|
-
const graph = makeGraph({
|
|
41
|
-
symbols: [
|
|
42
|
-
makeSymbolNode({ id: "a", name: "a", kind: "function", file: "a.ts", line: 1, endLine: 1 }),
|
|
43
|
-
makeSymbolNode({ id: "b", name: "b", kind: "function", file: "b.ts", line: 1, endLine: 1 }),
|
|
44
|
-
makeSymbolNode({ id: "c", name: "c", kind: "function", file: "c.ts", line: 1, endLine: 1 }),
|
|
45
|
-
],
|
|
46
|
-
calls: [
|
|
47
|
-
makeCallEdge({ callerSymbolId: "b", callerName: "b", calleeRaw: "a", file: "b.ts", line: 1 }),
|
|
48
|
-
makeCallEdge({ callerSymbolId: "c", callerName: "c", calleeRaw: "b", file: "c.ts", line: 1 }),
|
|
49
|
-
],
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const results = impact(graph, "a");
|
|
53
|
-
expect(results).toHaveLength(2);
|
|
54
|
-
const b = results.find((r) => r.symbol.name === "b");
|
|
55
|
-
const c = results.find((r) => r.symbol.name === "c");
|
|
56
|
-
expect(b).toBeTruthy();
|
|
57
|
-
expect(c).toBeTruthy();
|
|
58
|
-
expect(b!.depth).toBe(1);
|
|
59
|
-
expect(c!.depth).toBe(2);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("respects max depth", () => {
|
|
63
|
-
const graph = makeGraph({
|
|
64
|
-
symbols: [
|
|
65
|
-
makeSymbolNode({ id: "a", name: "a", kind: "function", file: "a.ts", line: 1, endLine: 1 }),
|
|
66
|
-
makeSymbolNode({ id: "b", name: "b", kind: "function", file: "b.ts", line: 1, endLine: 1 }),
|
|
67
|
-
makeSymbolNode({ id: "c", name: "c", kind: "function", file: "c.ts", line: 1, endLine: 1 }),
|
|
68
|
-
],
|
|
69
|
-
calls: [
|
|
70
|
-
makeCallEdge({ callerSymbolId: "b", callerName: "b", calleeRaw: "a", file: "b.ts", line: 1 }),
|
|
71
|
-
makeCallEdge({ callerSymbolId: "c", callerName: "c", calleeRaw: "b", file: "c.ts", line: 1 }),
|
|
72
|
-
],
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const results = impact(graph, "a", 1);
|
|
76
|
-
expect(results).toHaveLength(1);
|
|
77
|
-
expect(results[0].symbol.name).toBe("b");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("returns empty for symbol with no callers", () => {
|
|
81
|
-
const graph = makeGraph({
|
|
82
|
-
symbols: [makeSymbolNode({ id: "a", name: "a", kind: "function", file: "a.ts", line: 1, endLine: 1 })],
|
|
83
|
-
});
|
|
84
|
-
const results = impact(graph, "a");
|
|
85
|
-
expect(results).toHaveLength(0);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("returns empty for unknown symbol", () => {
|
|
89
|
-
const graph = makeGraph();
|
|
90
|
-
const results = impact(graph, "noop");
|
|
91
|
-
expect(results).toHaveLength(0);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe("findPath", () => {
|
|
96
|
-
it("finds a direct path between two symbols", () => {
|
|
97
|
-
const graph = makeGraph({
|
|
98
|
-
symbols: [
|
|
99
|
-
makeSymbolNode({ id: "a", name: "a", kind: "function", file: "a.ts", line: 1, endLine: 1 }),
|
|
100
|
-
makeSymbolNode({ id: "b", name: "b", kind: "function", file: "b.ts", line: 1, endLine: 1 }),
|
|
101
|
-
],
|
|
102
|
-
calls: [makeCallEdge({ callerSymbolId: "a", callerName: "a", calleeRaw: "b", file: "a.ts", line: 1 })],
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const p = findPath(graph, "a", "b");
|
|
106
|
-
expect(p).toBeTruthy();
|
|
107
|
-
expect(p!.map((n) => n.name)).toEqual(["a", "b"]);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("finds a multi-hop path", () => {
|
|
111
|
-
const graph = makeGraph({
|
|
112
|
-
symbols: [
|
|
113
|
-
makeSymbolNode({ id: "a", name: "a", kind: "function", file: "a.ts", line: 1, endLine: 1 }),
|
|
114
|
-
makeSymbolNode({ id: "b", name: "b", kind: "function", file: "b.ts", line: 1, endLine: 1 }),
|
|
115
|
-
makeSymbolNode({ id: "c", name: "c", kind: "function", file: "c.ts", line: 1, endLine: 1 }),
|
|
116
|
-
],
|
|
117
|
-
calls: [
|
|
118
|
-
makeCallEdge({ callerSymbolId: "a", callerName: "a", calleeRaw: "b", file: "a.ts", line: 1 }),
|
|
119
|
-
makeCallEdge({ callerSymbolId: "b", callerName: "b", calleeRaw: "c", file: "b.ts", line: 1 }),
|
|
120
|
-
],
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
const p = findPath(graph, "a", "c");
|
|
124
|
-
expect(p).toBeTruthy();
|
|
125
|
-
expect(p!.map((n) => n.name)).toEqual(["a", "b", "c"]);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("returns self-path when from === to", () => {
|
|
129
|
-
const graph = makeGraph({
|
|
130
|
-
symbols: [makeSymbolNode({ id: "a", name: "a", kind: "function", file: "a.ts", line: 1, endLine: 1 })],
|
|
131
|
-
});
|
|
132
|
-
const p = findPath(graph, "a", "a");
|
|
133
|
-
expect(p).toBeTruthy();
|
|
134
|
-
expect(p!.map((n) => n.name)).toEqual(["a"]);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it("returns undefined when no path exists", () => {
|
|
138
|
-
const graph = makeGraph({
|
|
139
|
-
symbols: [
|
|
140
|
-
makeSymbolNode({ id: "a", name: "a", kind: "function", file: "a.ts", line: 1, endLine: 1 }),
|
|
141
|
-
makeSymbolNode({ id: "b", name: "b", kind: "function", file: "b.ts", line: 1, endLine: 1 }),
|
|
142
|
-
],
|
|
143
|
-
});
|
|
144
|
-
const p = findPath(graph, "a", "b");
|
|
145
|
-
expect(p).toBeUndefined();
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it("returns undefined for unknown symbols", () => {
|
|
149
|
-
const graph = makeGraph();
|
|
150
|
-
expect(findPath(graph, "a", "b")).toBeUndefined();
|
|
151
|
-
expect(findPath(graph, "a", "a")).toBeUndefined();
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
describe("findOrphans", () => {
|
|
156
|
-
it("skips unexported symbols that are called", () => {
|
|
157
|
-
const graph = makeGraph({
|
|
158
|
-
symbols: [
|
|
159
|
-
makeSymbolNode({ id: "a", name: "a", kind: "function", isExported: false }),
|
|
160
|
-
makeSymbolNode({ id: "b", name: "b", kind: "function", isExported: true }),
|
|
161
|
-
],
|
|
162
|
-
calls: [makeCallEdge({ callerSymbolId: "b", callerName: "b", calleeRaw: "a", file: "b.ts", line: 1 })],
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
const orphans = findOrphans(graph);
|
|
166
|
-
const deadNames = orphans.map((o) => o.symbol.name);
|
|
167
|
-
expect(deadNames).not.toContain("a"); // "a" is called by "b"
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it("flags unexported symbols with no callers", () => {
|
|
171
|
-
const graph = makeGraph({
|
|
172
|
-
symbols: [
|
|
173
|
-
makeSymbolNode({ id: "dead", name: "dead", kind: "function", isExported: false }),
|
|
174
|
-
makeSymbolNode({ id: "alive", name: "alive", kind: "function", isExported: false }),
|
|
175
|
-
],
|
|
176
|
-
calls: [makeCallEdge({ callerSymbolId: "alive", callerName: "alive", calleeRaw: "nonexistent", file: "x.ts", line: 1 })],
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
const orphans = findOrphans(graph);
|
|
180
|
-
expect(orphans.some((o) => o.symbol.name === "dead")).toBe(true);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it("flags exported symbols with no callers or tests", () => {
|
|
184
|
-
const graph = makeGraph({
|
|
185
|
-
symbols: [makeSymbolNode({ id: "unused", name: "unused", kind: "function", isExported: true })],
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const orphans = findOrphans(graph);
|
|
189
|
-
expect(orphans).toHaveLength(1);
|
|
190
|
-
expect(orphans[0].symbol.name).toBe("unused");
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it("excludes symbols referenced in tests", () => {
|
|
194
|
-
const graph = makeGraph({
|
|
195
|
-
symbols: [makeSymbolNode({ id: "tested", name: "tested", kind: "function", isExported: false })],
|
|
196
|
-
testEdges: [makeTestEdge({ testFunc: "test.ts", target: "tested", file: "test.ts", line: 1 })],
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
const orphans = findOrphans(graph);
|
|
200
|
-
expect(orphans).toHaveLength(0);
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
describe("trace", () => {
|
|
205
|
-
it("finds string matches in symbol bodies and their callers", () => {
|
|
206
|
-
const dir = createTempDir();
|
|
207
|
-
const filePath = path.join(dir, "src/lib.ts");
|
|
208
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
209
|
-
fs.writeFileSync(
|
|
210
|
-
filePath,
|
|
211
|
-
[
|
|
212
|
-
'function doStuff() {',
|
|
213
|
-
' throw new Error("boom");',
|
|
214
|
-
'}',
|
|
215
|
-
'function caller() {',
|
|
216
|
-
' doStuff();',
|
|
217
|
-
'}',
|
|
218
|
-
].join("\n"),
|
|
219
|
-
"utf-8",
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
const graph = makeGraph({
|
|
223
|
-
root: dir,
|
|
224
|
-
symbols: [
|
|
225
|
-
makeSymbolNode({ id: "doStuff", name: "doStuff", kind: "function", file: "src/lib.ts", line: 1, endLine: 3 }),
|
|
226
|
-
makeSymbolNode({ id: "caller", name: "caller", kind: "function", file: "src/lib.ts", line: 4, endLine: 6 }),
|
|
227
|
-
],
|
|
228
|
-
calls: [
|
|
229
|
-
makeCallEdge({ callerSymbolId: "caller", callerName: "caller", calleeRaw: "doStuff", file: "src/lib.ts", line: 5 }),
|
|
230
|
-
],
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
const results = trace(graph, "boom");
|
|
234
|
-
expect(results).toHaveLength(1);
|
|
235
|
-
expect(results[0].match.symbol.name).toBe("doStuff");
|
|
236
|
-
expect(results[0].match.contextLine).toContain("boom");
|
|
237
|
-
expect(results[0].callers).toHaveLength(1);
|
|
238
|
-
expect(results[0].callers[0].symbol.name).toBe("caller");
|
|
239
|
-
|
|
240
|
-
fs.rmSync(dir, { recursive: true });
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it("returns empty for no match", () => {
|
|
244
|
-
const graph = makeGraph({
|
|
245
|
-
symbols: [makeSymbolNode({ id: "foo", name: "foo", kind: "function", file: "x.ts", line: 1, endLine: 1 })],
|
|
246
|
-
});
|
|
247
|
-
const results = trace(graph, "nonexistent");
|
|
248
|
-
expect(results).toHaveLength(0);
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
it("is case-insensitive", () => {
|
|
252
|
-
const dir = createTempDir();
|
|
253
|
-
const filePath = path.join(dir, "x.ts");
|
|
254
|
-
fs.writeFileSync(filePath, 'function foo() { return "HELLO"; }', "utf-8");
|
|
255
|
-
|
|
256
|
-
const graph = makeGraph({
|
|
257
|
-
root: dir,
|
|
258
|
-
symbols: [makeSymbolNode({ id: "foo", name: "foo", kind: "function", file: "x.ts", line: 1, endLine: 1 })],
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
const results = trace(graph, "hello");
|
|
262
|
-
expect(results).toHaveLength(1);
|
|
263
|
-
|
|
264
|
-
fs.rmSync(dir, { recursive: true });
|
|
265
|
-
});
|
|
266
|
-
});
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import type { Graph, SymbolNode } from "../graph/types.js";
|
|
4
|
-
|
|
5
|
-
export interface ImpactNode {
|
|
6
|
-
symbol: SymbolNode;
|
|
7
|
-
depth: number;
|
|
8
|
-
callChain: string[];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function impact(
|
|
12
|
-
graph: Graph,
|
|
13
|
-
symbolName: string,
|
|
14
|
-
maxDepth: number = 5,
|
|
15
|
-
): ImpactNode[] {
|
|
16
|
-
const start = graph.symbols.find((s) => s.name === symbolName);
|
|
17
|
-
if (!start) return [];
|
|
18
|
-
|
|
19
|
-
const results: ImpactNode[] = [];
|
|
20
|
-
const visited = new Set<string>();
|
|
21
|
-
const queue: { sym: SymbolNode; depth: number; chain: string[] }[] = [
|
|
22
|
-
{ sym: start, depth: 0, chain: [start.name] },
|
|
23
|
-
];
|
|
24
|
-
visited.add(start.id);
|
|
25
|
-
|
|
26
|
-
while (queue.length > 0) {
|
|
27
|
-
const { sym, depth, chain } = queue.shift()!;
|
|
28
|
-
if (depth > 0) {
|
|
29
|
-
results.push({ symbol: sym, depth, callChain: chain });
|
|
30
|
-
}
|
|
31
|
-
if (depth >= maxDepth) continue;
|
|
32
|
-
|
|
33
|
-
const callers = graph.calls.filter((c) => c.calleeRaw === sym.name);
|
|
34
|
-
for (const edge of callers) {
|
|
35
|
-
const caller = graph.symbols.find((s) => s.id === edge.callerSymbolId);
|
|
36
|
-
if (!caller || visited.has(caller.id)) continue;
|
|
37
|
-
visited.add(caller.id);
|
|
38
|
-
queue.push({
|
|
39
|
-
sym: caller,
|
|
40
|
-
depth: depth + 1,
|
|
41
|
-
chain: [...chain, caller.name],
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return results;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface PathNode {
|
|
50
|
-
name: string;
|
|
51
|
-
kind: string;
|
|
52
|
-
file: string;
|
|
53
|
-
line: number;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function findPath(
|
|
57
|
-
graph: Graph,
|
|
58
|
-
fromName: string,
|
|
59
|
-
toName: string,
|
|
60
|
-
maxDepth: number = 10,
|
|
61
|
-
): PathNode[] | undefined {
|
|
62
|
-
const from = graph.symbols.find((s) => s.name === fromName);
|
|
63
|
-
const to = graph.symbols.find((s) => s.name === toName);
|
|
64
|
-
if (!from || !to) return undefined;
|
|
65
|
-
if (from.id === to.id) return [{ name: from.name, kind: from.kind, file: from.file, line: from.line }];
|
|
66
|
-
|
|
67
|
-
const visited = new Set<string>();
|
|
68
|
-
const parent = new Map<string, { prev: string; edge: { name: string; kind: string; file: string; line: number } }>();
|
|
69
|
-
const queue: string[] = [from.id];
|
|
70
|
-
visited.add(from.id);
|
|
71
|
-
|
|
72
|
-
while (queue.length > 0) {
|
|
73
|
-
const currentId = queue.shift()!;
|
|
74
|
-
const current = graph.symbols.find((s) => s.id === currentId);
|
|
75
|
-
if (!current) continue;
|
|
76
|
-
|
|
77
|
-
const calleeEdges = graph.calls.filter((c) => c.callerSymbolId === currentId);
|
|
78
|
-
for (const edge of calleeEdges) {
|
|
79
|
-
const callee = graph.symbols.find(
|
|
80
|
-
(s) => s.name === edge.calleeRaw,
|
|
81
|
-
);
|
|
82
|
-
if (!callee || visited.has(callee.id)) continue;
|
|
83
|
-
visited.add(callee.id);
|
|
84
|
-
parent.set(callee.id, {
|
|
85
|
-
prev: currentId,
|
|
86
|
-
edge: { name: callee.name, kind: callee.kind, file: callee.file, line: callee.line },
|
|
87
|
-
});
|
|
88
|
-
if (callee.id === to.id) {
|
|
89
|
-
const pathNodes: PathNode[] = [];
|
|
90
|
-
let step: string | undefined = to.id;
|
|
91
|
-
while (step) {
|
|
92
|
-
const sym = graph.symbols.find((s) => s.id === step)!;
|
|
93
|
-
pathNodes.unshift({ name: sym.name, kind: sym.kind, file: sym.file, line: sym.line });
|
|
94
|
-
const p = parent.get(step);
|
|
95
|
-
step = p?.prev;
|
|
96
|
-
}
|
|
97
|
-
return pathNodes;
|
|
98
|
-
}
|
|
99
|
-
queue.push(callee.id);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return undefined;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export interface OrphanResult {
|
|
107
|
-
symbol: SymbolNode;
|
|
108
|
-
reason: string;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function findOrphans(graph: Graph): OrphanResult[] {
|
|
112
|
-
const calledNames = new Set(graph.calls.map((c) => c.calleeRaw));
|
|
113
|
-
const testTargets = new Set(graph.testEdges.map((t) => t.target));
|
|
114
|
-
|
|
115
|
-
const results: OrphanResult[] = [];
|
|
116
|
-
|
|
117
|
-
for (const sym of graph.symbols) {
|
|
118
|
-
if (sym.isExported) {
|
|
119
|
-
const incomingCallers = graph.calls.filter((c) => c.calleeRaw === sym.name);
|
|
120
|
-
if (incomingCallers.length === 0 && !testTargets.has(sym.name)) {
|
|
121
|
-
results.push({ symbol: sym, reason: "exported but no callers or tests" });
|
|
122
|
-
}
|
|
123
|
-
} else {
|
|
124
|
-
if (!calledNames.has(sym.name) && !testTargets.has(sym.name)) {
|
|
125
|
-
results.push({ symbol: sym, reason: "unexported, no callers or tests" });
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return results;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export interface TraceMatch {
|
|
134
|
-
symbol: SymbolNode;
|
|
135
|
-
file: string;
|
|
136
|
-
line: number;
|
|
137
|
-
contextLine: string;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export interface TraceResult {
|
|
141
|
-
match: TraceMatch;
|
|
142
|
-
callers: ImpactNode[];
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export function trace(
|
|
146
|
-
graph: Graph,
|
|
147
|
-
searchString: string,
|
|
148
|
-
maxDepth: number = 5,
|
|
149
|
-
): TraceResult[] {
|
|
150
|
-
const lowerSearch = searchString.toLowerCase();
|
|
151
|
-
const results: TraceResult[] = [];
|
|
152
|
-
|
|
153
|
-
for (const sym of graph.symbols) {
|
|
154
|
-
if (sym.kind !== "function" && sym.kind !== "method") continue;
|
|
155
|
-
|
|
156
|
-
const filePath = path.join(graph.root, sym.file);
|
|
157
|
-
let content: string;
|
|
158
|
-
try {
|
|
159
|
-
content = fs.readFileSync(filePath, "utf-8");
|
|
160
|
-
} catch {
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const lines = content.split("\n");
|
|
165
|
-
const start = Math.max(0, sym.line - 1);
|
|
166
|
-
const end = Math.min(lines.length, sym.endLine);
|
|
167
|
-
|
|
168
|
-
for (let i = start; i < end; i++) {
|
|
169
|
-
if (lines[i].toLowerCase().includes(lowerSearch)) {
|
|
170
|
-
const impactNodes = impact(graph, sym.name, maxDepth);
|
|
171
|
-
results.push({
|
|
172
|
-
match: {
|
|
173
|
-
symbol: sym,
|
|
174
|
-
file: sym.file,
|
|
175
|
-
line: i + 1,
|
|
176
|
-
contextLine: lines[i].trim(),
|
|
177
|
-
},
|
|
178
|
-
callers: impactNodes,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return results;
|
|
185
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ESNext",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"lib": ["ESNext"],
|
|
7
|
-
"strict": true,
|
|
8
|
-
"esModuleInterop": true,
|
|
9
|
-
"skipLibCheck": true,
|
|
10
|
-
"forceConsistentCasingInFileNames": true,
|
|
11
|
-
"resolveJsonModule": true,
|
|
12
|
-
"declaration": true,
|
|
13
|
-
"declarationMap": true,
|
|
14
|
-
"sourceMap": true,
|
|
15
|
-
"outDir": "dist",
|
|
16
|
-
"rootDir": "src"
|
|
17
|
-
},
|
|
18
|
-
"include": ["src/**/*.ts"],
|
|
19
|
-
"exclude": ["node_modules", "dist"]
|
|
20
|
-
}
|