@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.
Files changed (55) hide show
  1. package/dist/changes/index.test.js +2 -6
  2. package/dist/changes/index.test.js.map +1 -1
  3. package/dist/cli/index.js +184 -4
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/git/index.test.js +4 -6
  6. package/dist/git/index.test.js.map +1 -1
  7. package/dist/opencode/index.js +1 -1
  8. package/dist/opencode/index.js.map +1 -1
  9. package/dist/opencode/index.test.js +2 -2
  10. package/dist/opencode/index.test.js.map +1 -1
  11. package/dist/search/index.d.ts.map +1 -1
  12. package/dist/search/index.js +12 -4
  13. package/dist/search/index.js.map +1 -1
  14. package/package.json +16 -1
  15. package/AGENTS.md +0 -64
  16. package/TODOS.md +0 -61
  17. package/opencode.json +0 -24
  18. package/src/analysis/analysis.test.ts +0 -405
  19. package/src/analysis/complexity.ts +0 -107
  20. package/src/analysis/coupling.ts +0 -106
  21. package/src/analysis/hotspot.ts +0 -52
  22. package/src/analysis/index.ts +0 -17
  23. package/src/boundaries/index.test.ts +0 -335
  24. package/src/boundaries/index.ts +0 -137
  25. package/src/changes/index.test.ts +0 -114
  26. package/src/changes/index.ts +0 -95
  27. package/src/cli/index.ts +0 -736
  28. package/src/git/index.test.ts +0 -92
  29. package/src/git/index.ts +0 -86
  30. package/src/graph/types.test.ts +0 -383
  31. package/src/graph/types.ts +0 -353
  32. package/src/mcp/mcp.test.ts +0 -176
  33. package/src/mcp/server.ts +0 -217
  34. package/src/nextjs/index.ts +0 -23
  35. package/src/nextjs/nextjs.test.ts +0 -233
  36. package/src/nextjs/pages.ts +0 -43
  37. package/src/nextjs/react.ts +0 -100
  38. package/src/nextjs/router.ts +0 -102
  39. package/src/nextjs/routes.ts +0 -69
  40. package/src/opencode/index.test.ts +0 -90
  41. package/src/opencode/index.ts +0 -83
  42. package/src/parser/index.ts +0 -339
  43. package/src/parser/parser.test.ts +0 -282
  44. package/src/plan/index.test.ts +0 -162
  45. package/src/plan/index.ts +0 -161
  46. package/src/report/index.ts +0 -128
  47. package/src/scanner/index.ts +0 -97
  48. package/src/scanner/scanner.test.ts +0 -135
  49. package/src/search/index.ts +0 -163
  50. package/src/search/search.test.ts +0 -512
  51. package/src/traversal/index.ts +0 -5
  52. package/src/traversal/traversal.test.ts +0 -266
  53. package/src/traversal/traversal.ts +0 -185
  54. package/tsconfig.json +0 -20
  55. package/vitest.config.ts +0 -7
@@ -1,512 +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
- makeImportEdge,
10
- makePackageNode,
11
- makeFileNode,
12
- serialize,
13
- } from "../graph/types.js";
14
- import {
15
- loadGraph,
16
- findCallers,
17
- findCallees,
18
- findNode,
19
- readSource,
20
- querySymbols,
21
- findImports,
22
- findPublic,
23
- focusPackage,
24
- context,
25
- } from "./index.js";
26
-
27
- function createTempDir(): string {
28
- return fs.mkdtempSync(path.join(os.tmpdir(), "tsgraph-test-"));
29
- }
30
-
31
- const baseSymbols = [
32
- makeSymbolNode({
33
- id: "src/main.ts::serve",
34
- name: "serve",
35
- kind: "function",
36
- file: "src/main.ts",
37
- line: 10,
38
- endLine: 20,
39
- packageName: "app",
40
- isExported: true,
41
- }),
42
- makeSymbolNode({
43
- id: "src/main.ts::greet",
44
- name: "greet",
45
- kind: "function",
46
- file: "src/main.ts",
47
- line: 22,
48
- endLine: 25,
49
- packageName: "app",
50
- isExported: true,
51
- }),
52
- makeSymbolNode({
53
- id: "src/main.ts::log",
54
- name: "log",
55
- kind: "function",
56
- file: "src/main.ts",
57
- line: 27,
58
- endLine: 29,
59
- packageName: "app",
60
- isExported: false,
61
- }),
62
- makeSymbolNode({
63
- id: "src/utils.ts::helper",
64
- name: "helper",
65
- kind: "function",
66
- file: "src/utils.ts",
67
- line: 5,
68
- endLine: 7,
69
- packageName: "app",
70
- isExported: false,
71
- }),
72
- ];
73
-
74
- const baseCalls = [
75
- makeCallEdge({
76
- callerSymbolId: "src/main.ts::serve",
77
- callerName: "serve",
78
- calleeRaw: "greet",
79
- file: "src/main.ts",
80
- line: 12,
81
- }),
82
- makeCallEdge({
83
- callerSymbolId: "src/main.ts::serve",
84
- callerName: "serve",
85
- calleeRaw: "log",
86
- file: "src/main.ts",
87
- line: 14,
88
- }),
89
- makeCallEdge({
90
- callerSymbolId: "src/main.ts::greet",
91
- callerName: "greet",
92
- calleeRaw: "log",
93
- file: "src/main.ts",
94
- line: 23,
95
- }),
96
- ];
97
-
98
- const baseImports = [
99
- makeImportEdge({
100
- fromFile: "src/main.ts",
101
- fromPackage: "app",
102
- importPath: "react",
103
- alias: "React",
104
- isDefault: true,
105
- }),
106
- makeImportEdge({
107
- fromFile: "src/main.ts",
108
- fromPackage: "app",
109
- importPath: "react",
110
- alias: "useState",
111
- isDefault: false,
112
- }),
113
- makeImportEdge({
114
- fromFile: "src/utils.ts",
115
- fromPackage: "app",
116
- importPath: "lodash",
117
- alias: "_",
118
- isDefault: true,
119
- }),
120
- ];
121
-
122
- function buildTestGraph() {
123
- return makeGraph({
124
- root: "/test",
125
- packages: [
126
- makePackageNode({ name: "app" }),
127
- makePackageNode({ name: "lib" }),
128
- ],
129
- files: [
130
- makeFileNode({ path: "src/main.ts", packageName: "app" }),
131
- makeFileNode({ path: "src/utils.ts", packageName: "app" }),
132
- ],
133
- symbols: baseSymbols,
134
- calls: baseCalls,
135
- imports: baseImports,
136
- });
137
- }
138
-
139
- describe("loadGraph", () => {
140
- it("round-trips from serialized file", () => {
141
- const dir = createTempDir();
142
- const graphPath = path.join(dir, "graph.json");
143
- const original = buildTestGraph();
144
- fs.writeFileSync(graphPath, serialize(original), "utf-8");
145
-
146
- const loaded = loadGraph(graphPath);
147
- expect(loaded.version).toBe(original.version);
148
- expect(loaded.symbols).toHaveLength(original.symbols.length);
149
- expect(loaded.calls).toHaveLength(original.calls.length);
150
- fs.rmSync(dir, { recursive: true });
151
- });
152
-
153
- it("throws on missing file", () => {
154
- expect(() => loadGraph("/nonexistent/graph.json")).toThrow();
155
- });
156
- });
157
-
158
- describe("findCallers", () => {
159
- it("finds callers of a referenced symbol", () => {
160
- const graph = buildTestGraph();
161
- const results = findCallers(graph, "greet");
162
- expect(results).toHaveLength(1);
163
- expect(results[0].callerSymbol.name).toBe("serve");
164
- expect(results[0].edges).toHaveLength(1);
165
- });
166
-
167
- it("finds multiple callers of a symbol", () => {
168
- const graph = buildTestGraph();
169
- const results = findCallers(graph, "log");
170
- expect(results).toHaveLength(2);
171
- const names = results.map((r) => r.callerSymbol.name).sort();
172
- expect(names).toEqual(["greet", "serve"]);
173
- });
174
-
175
- it("returns empty for unreferenced symbol", () => {
176
- const graph = buildTestGraph();
177
- const results = findCallers(graph, "helper");
178
- expect(results).toHaveLength(0);
179
- });
180
-
181
- it("returns empty for unknown symbol", () => {
182
- const graph = buildTestGraph();
183
- const results = findCallers(graph, "nonexistent");
184
- expect(results).toHaveLength(0);
185
- });
186
- });
187
-
188
- describe("findCallees", () => {
189
- it("finds callees of a function", () => {
190
- const graph = buildTestGraph();
191
- const results = findCallees(graph, "serve");
192
- expect(results).toHaveLength(2);
193
- const names = results.map((r) => r.calleeRaw).sort();
194
- expect(names).toEqual(["greet", "log"]);
195
- });
196
-
197
- it("finds callees with multiple call sites", () => {
198
- const graph = buildTestGraph();
199
- const results = findCallees(graph, "greet");
200
- expect(results).toHaveLength(1);
201
- expect(results[0].calleeRaw).toBe("log");
202
- expect(results[0].edges).toHaveLength(1);
203
- });
204
-
205
- it("returns empty for leaf function", () => {
206
- const graph = buildTestGraph();
207
- const results = findCallees(graph, "log");
208
- expect(results).toHaveLength(0);
209
- });
210
-
211
- it("returns empty for unknown symbol", () => {
212
- const graph = buildTestGraph();
213
- const results = findCallees(graph, "noop");
214
- expect(results).toHaveLength(0);
215
- });
216
- });
217
-
218
- describe("findNode", () => {
219
- it("finds a symbol by exact name", () => {
220
- const graph = buildTestGraph();
221
- const n = findNode(graph, "serve");
222
- expect(n).toBeTruthy();
223
- expect(n!.kind).toBe("function");
224
- expect(n!.file).toBe("src/main.ts");
225
- });
226
-
227
- it("returns undefined for missing symbol", () => {
228
- const graph = buildTestGraph();
229
- const n = findNode(graph, "noop");
230
- expect(n).toBeUndefined();
231
- });
232
-
233
- it("returns first match when names collide", () => {
234
- const graph = buildTestGraph();
235
- // Two symbols with same name in different files
236
- graph.symbols.push(
237
- makeSymbolNode({
238
- id: "src/other.ts::serve",
239
- name: "serve",
240
- file: "src/other.ts",
241
- }),
242
- );
243
- const n = findNode(graph, "serve");
244
- expect(n).toBeTruthy();
245
- expect(n!.file).toBe("src/main.ts"); // first in array
246
- });
247
- });
248
-
249
- describe("readSource", () => {
250
- it("extracts the correct line range from a file", () => {
251
- const dir = createTempDir();
252
- const root = dir;
253
- const filePath = path.join(root, "src/main.ts");
254
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
255
- fs.writeFileSync(filePath, [
256
- "line 1",
257
- "line 2",
258
- "line 3",
259
- "line 4",
260
- "line 5",
261
- ].join("\n"), "utf-8");
262
-
263
- const graph = makeGraph({
264
- root,
265
- files: [makeFileNode({ path: "src/main.ts" })],
266
- symbols: [
267
- makeSymbolNode({
268
- id: "src/main.ts::foo",
269
- name: "foo",
270
- file: "src/main.ts",
271
- line: 2,
272
- endLine: 4,
273
- }),
274
- ],
275
- });
276
-
277
- const sym = graph.symbols[0];
278
- const src = readSource(graph, sym);
279
- expect(src).toContain("line 2");
280
- expect(src).toContain("line 3");
281
- expect(src).toContain("line 4");
282
- expect(src).not.toContain("line 1");
283
- expect(src).not.toContain("line 5");
284
-
285
- // should include line numbers
286
- expect(src).toMatch(/^2 /m);
287
- expect(src).toMatch(/^3 /m);
288
- expect(src).toMatch(/^4 /m);
289
-
290
- fs.rmSync(dir, { recursive: true });
291
- });
292
-
293
- it("throws on missing file", () => {
294
- const graph = makeGraph({
295
- root: "/nonexistent",
296
- symbols: [
297
- makeSymbolNode({ name: "foo", file: "foo.ts", line: 1, endLine: 3 }),
298
- ],
299
- });
300
- expect(() => readSource(graph, graph.symbols[0])).toThrow();
301
- });
302
- });
303
-
304
- describe("querySymbols", () => {
305
- it("matches symbol name case-insensitively", () => {
306
- const graph = buildTestGraph();
307
- const results = querySymbols(graph, "GREET");
308
- expect(results).toHaveLength(1);
309
- expect(results[0].name).toBe("greet");
310
- });
311
-
312
- it("matches file path", () => {
313
- const graph = buildTestGraph();
314
- const results = querySymbols(graph, "utils");
315
- expect(results).toHaveLength(1);
316
- expect(results[0].name).toBe("helper");
317
- });
318
-
319
- it("matches package name", () => {
320
- const graph = buildTestGraph();
321
- const results = querySymbols(graph, "app");
322
- // all 4 symbols are in package "app"
323
- expect(results.length).toBeGreaterThanOrEqual(4);
324
- });
325
-
326
- it("returns all symbols for empty pattern", () => {
327
- const graph = buildTestGraph();
328
- const results = querySymbols(graph, "");
329
- expect(results).toHaveLength(graph.symbols.length);
330
- });
331
-
332
- it("returns empty for no match", () => {
333
- const graph = buildTestGraph();
334
- const results = querySymbols(graph, "zzznone");
335
- expect(results).toHaveLength(0);
336
- });
337
- });
338
-
339
- describe("findImports", () => {
340
- it("matches import path substring", () => {
341
- const graph = buildTestGraph();
342
- const results = findImports(graph, "react");
343
- expect(results).toHaveLength(2);
344
- });
345
-
346
- it("returns empty for no match", () => {
347
- const graph = buildTestGraph();
348
- const results = findImports(graph, "noop");
349
- expect(results).toHaveLength(0);
350
- });
351
-
352
- it("is case-insensitive", () => {
353
- const graph = buildTestGraph();
354
- const results = findImports(graph, "LODASH");
355
- expect(results).toHaveLength(1);
356
- expect(results[0].importPath).toBe("lodash");
357
- });
358
- });
359
-
360
- describe("findPublic", () => {
361
- it("returns only exported symbols", () => {
362
- const graph = buildTestGraph();
363
- const results = findPublic(graph);
364
- expect(results).toHaveLength(2);
365
- expect(results.every((s) => s.isExported)).toBe(true);
366
- const names = results.map((s) => s.name).sort();
367
- expect(names).toEqual(["greet", "serve"]);
368
- });
369
-
370
- it("scopes to a package when specified", () => {
371
- const graph = buildTestGraph();
372
- // Add an exported symbol in package "lib"
373
- graph.symbols.push(
374
- makeSymbolNode({
375
- id: "lib/helper.ts::run",
376
- name: "run",
377
- kind: "function",
378
- packageName: "lib",
379
- isExported: true,
380
- }),
381
- );
382
- const results = findPublic(graph, "lib");
383
- expect(results).toHaveLength(1);
384
- expect(results[0].name).toBe("run");
385
- });
386
-
387
- it("returns empty when no exported symbols in package", () => {
388
- const graph = buildTestGraph();
389
- const results = findPublic(graph, "missing-pkg");
390
- expect(results).toHaveLength(0);
391
- });
392
- });
393
-
394
- describe("focusPackage", () => {
395
- it("returns package assets", () => {
396
- const graph = buildTestGraph();
397
- const result = focusPackage(graph, "app");
398
- expect(result).toBeTruthy();
399
- expect(result!.pkg.name).toBe("app");
400
- expect(result!.files).toHaveLength(2);
401
- expect(result!.symbols).toHaveLength(4);
402
- expect(result!.imports).toHaveLength(3);
403
- });
404
-
405
- it("returns undefined for missing package", () => {
406
- const graph = buildTestGraph();
407
- const result = focusPackage(graph, "no-pkg");
408
- expect(result).toBeUndefined();
409
- });
410
- });
411
-
412
- describe("context", () => {
413
- it("bundles node, source, callers, and callees", () => {
414
- const dir = createTempDir();
415
- const root = dir;
416
- const filePath = path.join(root, "src/main.ts");
417
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
418
- const fileLines = [
419
- "",
420
- "",
421
- "",
422
- "",
423
- "",
424
- "",
425
- "",
426
- "",
427
- "",
428
- "function serve() {",
429
- " greet();",
430
- " log();",
431
- "}",
432
- "",
433
- "",
434
- "function greet() {",
435
- " log();",
436
- "}",
437
- ];
438
- fs.writeFileSync(filePath, fileLines.join("\n"), "utf-8");
439
-
440
- const graph = makeGraph({
441
- root,
442
- packages: [makePackageNode({ name: "app" })],
443
- files: [makeFileNode({ path: "src/main.ts", packageName: "app" })],
444
- symbols: [
445
- makeSymbolNode({
446
- id: "src/main.ts::serve",
447
- name: "serve",
448
- kind: "function",
449
- file: "src/main.ts",
450
- line: 10,
451
- endLine: 13,
452
- packageName: "app",
453
- isExported: true,
454
- }),
455
- makeSymbolNode({
456
- id: "src/main.ts::greet",
457
- name: "greet",
458
- kind: "function",
459
- file: "src/main.ts",
460
- line: 16,
461
- endLine: 18,
462
- packageName: "app",
463
- isExported: true,
464
- }),
465
- ],
466
- calls: [
467
- makeCallEdge({
468
- callerSymbolId: "src/main.ts::serve",
469
- callerName: "serve",
470
- calleeRaw: "greet",
471
- file: "src/main.ts",
472
- line: 11,
473
- }),
474
- makeCallEdge({
475
- callerSymbolId: "src/main.ts::serve",
476
- callerName: "serve",
477
- calleeRaw: "log",
478
- file: "src/main.ts",
479
- line: 12,
480
- }),
481
- ],
482
- });
483
-
484
- const ctx = context(graph, "serve");
485
- expect(ctx.node).toBeTruthy();
486
- expect(ctx.node!.name).toBe("serve");
487
- expect(ctx.source).toBeTruthy();
488
- expect(ctx.source).toContain("greet");
489
- expect(ctx.callers).toHaveLength(0);
490
- expect(ctx.callees).toHaveLength(2);
491
-
492
- fs.rmSync(dir, { recursive: true });
493
- });
494
-
495
- it("returns empty source when file is missing", () => {
496
- const graph = buildTestGraph();
497
- const ctx = context(graph, "serve");
498
- expect(ctx.node).toBeTruthy();
499
- expect(ctx.source).toBeUndefined();
500
- expect(ctx.callers).toHaveLength(0);
501
- expect(ctx.callees).toHaveLength(2);
502
- });
503
-
504
- it("returns partial result for missing symbol", () => {
505
- const graph = buildTestGraph();
506
- const ctx = context(graph, "noop");
507
- expect(ctx.node).toBeUndefined();
508
- expect(ctx.source).toBeUndefined();
509
- expect(ctx.callers).toHaveLength(0);
510
- expect(ctx.callees).toHaveLength(0);
511
- });
512
- });
@@ -1,5 +0,0 @@
1
- import { impact, findPath, findOrphans, trace } from "./traversal.js";
2
- import type { ImpactNode, PathNode, OrphanResult, TraceResult, TraceMatch } from "./traversal.js";
3
-
4
- export { impact, findPath, findOrphans, trace };
5
- export type { ImpactNode, PathNode, OrphanResult, TraceResult, TraceMatch };