@optave/codegraph 3.10.0 → 3.11.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.
- package/README.md +40 -33
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +91 -60
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/rules/index.d.ts.map +1 -1
- package/dist/ast-analysis/rules/index.js +77 -0
- package/dist/ast-analysis/rules/index.js.map +1 -1
- package/dist/ast-analysis/visitor-utils.d.ts +3 -0
- package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
- package/dist/ast-analysis/visitor-utils.js +83 -49
- package/dist/ast-analysis/visitor-utils.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +78 -62
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.js +61 -42
- package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
- package/dist/cli/commands/audit.js +1 -1
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +2 -0
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/check.js +1 -1
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/children.js +1 -1
- package/dist/cli/commands/children.js.map +1 -1
- package/dist/cli/commands/diff-impact.js +1 -1
- package/dist/cli/commands/diff-impact.js.map +1 -1
- package/dist/cli/commands/embed.d.ts.map +1 -1
- package/dist/cli/commands/embed.js +49 -4
- package/dist/cli/commands/embed.js.map +1 -1
- package/dist/cli/commands/roles.js +1 -1
- package/dist/cli/commands/roles.js.map +1 -1
- package/dist/cli/commands/structure.js +1 -1
- package/dist/cli/commands/structure.js.map +1 -1
- package/dist/cli/shared/options.js +1 -1
- package/dist/cli/shared/options.js.map +1 -1
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +8 -0
- package/dist/db/connection.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +106 -80
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +77 -52
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +132 -121
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/graph/builder/helpers.d.ts +4 -4
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +47 -33
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts +6 -6
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +148 -99
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts +1 -0
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +23 -637
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +141 -98
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.js +82 -65
- package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +84 -56
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +60 -51
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts +8 -6
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +107 -122
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts +14 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts.map +1 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.js +77 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.js.map +1 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts +62 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.js +747 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -0
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +73 -22
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/cycles.d.ts +6 -4
- package/dist/domain/graph/cycles.d.ts.map +1 -1
- package/dist/domain/graph/cycles.js +50 -55
- package/dist/domain/graph/cycles.js.map +1 -1
- package/dist/domain/graph/journal.d.ts.map +1 -1
- package/dist/domain/graph/journal.js +89 -70
- package/dist/domain/graph/journal.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +28 -20
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +12 -23
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +153 -80
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/generator.d.ts +3 -1
- package/dist/domain/search/generator.d.ts.map +1 -1
- package/dist/domain/search/generator.js +68 -45
- package/dist/domain/search/generator.js.map +1 -1
- package/dist/domain/search/models.d.ts +18 -0
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +72 -4
- package/dist/domain/search/models.js.map +1 -1
- package/dist/domain/search/search/hybrid.d.ts.map +1 -1
- package/dist/domain/search/search/hybrid.js +49 -40
- package/dist/domain/search/search/hybrid.js.map +1 -1
- package/dist/domain/search/search/semantic.d.ts.map +1 -1
- package/dist/domain/search/search/semantic.js +69 -49
- package/dist/domain/search/search/semantic.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +209 -137
- package/dist/domain/wasm-worker-entry.js.map +1 -1
- package/dist/extractors/c.js +25 -6
- package/dist/extractors/c.js.map +1 -1
- package/dist/extractors/cpp.js +47 -6
- package/dist/extractors/cpp.js.map +1 -1
- package/dist/extractors/cuda.js +90 -14
- package/dist/extractors/cuda.js.map +1 -1
- package/dist/extractors/elixir.js +108 -4
- package/dist/extractors/elixir.js.map +1 -1
- package/dist/extractors/erlang.js +56 -20
- package/dist/extractors/erlang.js.map +1 -1
- package/dist/extractors/fsharp.d.ts +7 -0
- package/dist/extractors/fsharp.d.ts.map +1 -1
- package/dist/extractors/fsharp.js +94 -0
- package/dist/extractors/fsharp.js.map +1 -1
- package/dist/extractors/gleam.d.ts.map +1 -1
- package/dist/extractors/gleam.js +29 -33
- package/dist/extractors/gleam.js.map +1 -1
- package/dist/extractors/groovy.js +41 -1
- package/dist/extractors/groovy.js.map +1 -1
- package/dist/extractors/haskell.js +48 -4
- package/dist/extractors/haskell.js.map +1 -1
- package/dist/extractors/helpers.d.ts +79 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +137 -0
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +37 -49
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.d.ts.map +1 -1
- package/dist/extractors/javascript.js +44 -44
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/julia.js +198 -74
- package/dist/extractors/julia.js.map +1 -1
- package/dist/extractors/kotlin.js +4 -0
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/objc.js +184 -47
- package/dist/extractors/objc.js.map +1 -1
- package/dist/extractors/python.js +7 -4
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/r.d.ts.map +1 -1
- package/dist/extractors/r.js +103 -87
- package/dist/extractors/r.js.map +1 -1
- package/dist/extractors/scala.d.ts.map +1 -1
- package/dist/extractors/scala.js +18 -32
- package/dist/extractors/scala.js.map +1 -1
- package/dist/extractors/solidity.d.ts.map +1 -1
- package/dist/extractors/solidity.js +55 -69
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/extractors/verilog.js +80 -15
- package/dist/extractors/verilog.js.map +1 -1
- package/dist/features/boundaries.d.ts.map +1 -1
- package/dist/features/boundaries.js +49 -39
- package/dist/features/boundaries.js.map +1 -1
- package/dist/features/cfg.d.ts.map +1 -1
- package/dist/features/cfg.js +90 -63
- package/dist/features/cfg.js.map +1 -1
- package/dist/features/check.d.ts.map +1 -1
- package/dist/features/check.js +43 -34
- package/dist/features/check.js.map +1 -1
- package/dist/features/cochange.d.ts.map +1 -1
- package/dist/features/cochange.js +68 -56
- package/dist/features/cochange.js.map +1 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +105 -75
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +37 -29
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/flow.d.ts.map +1 -1
- package/dist/features/flow.js +31 -22
- package/dist/features/flow.js.map +1 -1
- package/dist/features/graph-enrichment.d.ts.map +1 -1
- package/dist/features/graph-enrichment.js +77 -70
- package/dist/features/graph-enrichment.js.map +1 -1
- package/dist/features/owners.d.ts +17 -26
- package/dist/features/owners.d.ts.map +1 -1
- package/dist/features/owners.js +120 -109
- package/dist/features/owners.js.map +1 -1
- package/dist/features/sequence.d.ts.map +1 -1
- package/dist/features/sequence.js +59 -54
- package/dist/features/sequence.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +60 -60
- package/dist/features/structure-query.js.map +1 -1
- package/dist/features/structure.js +28 -36
- package/dist/features/structure.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +100 -69
- package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
- package/dist/graph/classifiers/roles.d.ts.map +1 -1
- package/dist/graph/classifiers/roles.js +63 -59
- package/dist/graph/classifiers/roles.js.map +1 -1
- package/dist/infrastructure/config.d.ts +1 -1
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +1 -1
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/mcp/tool-registry.d.ts.map +1 -1
- package/dist/mcp/tool-registry.js +4 -0
- package/dist/mcp/tool-registry.js.map +1 -1
- package/dist/mcp/tools/semantic-search.d.ts +1 -0
- package/dist/mcp/tools/semantic-search.d.ts.map +1 -1
- package/dist/mcp/tools/semantic-search.js +1 -0
- package/dist/mcp/tools/semantic-search.js.map +1 -1
- package/dist/presentation/cfg.d.ts.map +1 -1
- package/dist/presentation/cfg.js +44 -29
- package/dist/presentation/cfg.js.map +1 -1
- package/dist/presentation/flow.d.ts.map +1 -1
- package/dist/presentation/flow.js +58 -38
- package/dist/presentation/flow.js.map +1 -1
- package/dist/types.d.ts +16 -2
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/grammars/tree-sitter-fsharp.wasm +0 -0
- package/grammars/tree-sitter-fsharp_signature.wasm +0 -0
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/package.json +10 -10
- package/src/ast-analysis/engine.ts +145 -61
- package/src/ast-analysis/rules/index.ts +87 -0
- package/src/ast-analysis/visitor-utils.ts +86 -46
- package/src/ast-analysis/visitors/ast-store-visitor.ts +104 -69
- package/src/ast-analysis/visitors/dataflow-visitor.ts +86 -47
- package/src/cli/commands/audit.ts +1 -1
- package/src/cli/commands/build.ts +2 -0
- package/src/cli/commands/check.ts +1 -1
- package/src/cli/commands/children.ts +1 -1
- package/src/cli/commands/diff-impact.ts +1 -1
- package/src/cli/commands/embed.ts +54 -4
- package/src/cli/commands/roles.ts +1 -1
- package/src/cli/commands/structure.ts +1 -1
- package/src/cli/shared/options.ts +1 -1
- package/src/db/connection.ts +8 -0
- package/src/domain/analysis/dependencies.ts +166 -85
- package/src/domain/analysis/fn-impact.ts +120 -50
- package/src/domain/analysis/module-map.ts +175 -140
- package/src/domain/graph/builder/helpers.ts +85 -76
- package/src/domain/graph/builder/incremental.ts +223 -131
- package/src/domain/graph/builder/pipeline.ts +32 -785
- package/src/domain/graph/builder/stages/build-edges.ts +207 -142
- package/src/domain/graph/builder/stages/build-structure.ts +115 -82
- package/src/domain/graph/builder/stages/detect-changes.ts +107 -64
- package/src/domain/graph/builder/stages/finalize.ts +72 -70
- package/src/domain/graph/builder/stages/insert-nodes.ts +154 -120
- package/src/domain/graph/builder/stages/native-db-lifecycle.ts +74 -0
- package/src/domain/graph/builder/stages/native-orchestrator.ts +942 -0
- package/src/domain/graph/builder/stages/resolve-imports.ts +79 -25
- package/src/domain/graph/cycles.ts +51 -49
- package/src/domain/graph/journal.ts +84 -69
- package/src/domain/graph/watcher.ts +29 -25
- package/src/domain/parser.ts +170 -67
- package/src/domain/search/generator.ts +132 -74
- package/src/domain/search/models.ts +75 -4
- package/src/domain/search/search/hybrid.ts +53 -42
- package/src/domain/search/search/semantic.ts +105 -65
- package/src/domain/wasm-worker-entry.ts +243 -153
- package/src/extractors/c.ts +27 -8
- package/src/extractors/cpp.ts +50 -8
- package/src/extractors/cuda.ts +90 -16
- package/src/extractors/elixir.ts +103 -4
- package/src/extractors/erlang.ts +63 -20
- package/src/extractors/fsharp.ts +104 -0
- package/src/extractors/gleam.ts +40 -39
- package/src/extractors/groovy.ts +45 -1
- package/src/extractors/haskell.ts +45 -4
- package/src/extractors/helpers.ts +205 -1
- package/src/extractors/java.ts +42 -45
- package/src/extractors/javascript.ts +44 -43
- package/src/extractors/julia.ts +191 -77
- package/src/extractors/kotlin.ts +4 -0
- package/src/extractors/objc.ts +171 -47
- package/src/extractors/python.ts +5 -3
- package/src/extractors/r.ts +104 -82
- package/src/extractors/scala.ts +24 -36
- package/src/extractors/solidity.ts +59 -78
- package/src/extractors/verilog.ts +83 -15
- package/src/features/boundaries.ts +64 -46
- package/src/features/cfg.ts +145 -74
- package/src/features/check.ts +60 -43
- package/src/features/cochange.ts +95 -72
- package/src/features/complexity.ts +134 -79
- package/src/features/dataflow.ts +57 -34
- package/src/features/flow.ts +48 -24
- package/src/features/graph-enrichment.ts +105 -70
- package/src/features/owners.ts +186 -146
- package/src/features/sequence.ts +99 -69
- package/src/features/structure-query.ts +94 -79
- package/src/features/structure.ts +56 -56
- package/src/graph/algorithms/leiden/optimiser.ts +142 -87
- package/src/graph/classifiers/roles.ts +64 -54
- package/src/infrastructure/config.ts +1 -1
- package/src/mcp/tool-registry.ts +5 -0
- package/src/mcp/tools/semantic-search.ts +2 -0
- package/src/presentation/cfg.ts +48 -32
- package/src/presentation/flow.ts +100 -52
- package/src/types.ts +16 -1
|
@@ -58,9 +58,32 @@ export function fileDepsData(
|
|
|
58
58
|
*
|
|
59
59
|
* Uses Repository.findCallers() so it works with both native and WASM engines.
|
|
60
60
|
*/
|
|
61
|
+
type CallerRow = { id: number; name: string; kind: string; file: string; line: number };
|
|
62
|
+
|
|
63
|
+
/** Compute the next BFS frontier from a batched upstream-callers lookup. */
|
|
64
|
+
function buildNextCallerFrontier(
|
|
65
|
+
unvisited: CallerRow[],
|
|
66
|
+
batchCallers: Map<number, CallerRow[]>,
|
|
67
|
+
visited: Set<number>,
|
|
68
|
+
noTests: boolean,
|
|
69
|
+
): CallerRow[] {
|
|
70
|
+
const nextFrontier: CallerRow[] = [];
|
|
71
|
+
const nextFrontierIds = new Set<number>();
|
|
72
|
+
for (const f of unvisited) {
|
|
73
|
+
const upstream = batchCallers.get(f.id) || [];
|
|
74
|
+
for (const u of upstream) {
|
|
75
|
+
if (noTests && isTestFile(u.file)) continue;
|
|
76
|
+
if (visited.has(u.id) || nextFrontierIds.has(u.id)) continue;
|
|
77
|
+
nextFrontierIds.add(u.id);
|
|
78
|
+
nextFrontier.push(u);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return nextFrontier;
|
|
82
|
+
}
|
|
83
|
+
|
|
61
84
|
function buildTransitiveCallers(
|
|
62
85
|
repo: InstanceType<typeof Repository>,
|
|
63
|
-
callers:
|
|
86
|
+
callers: CallerRow[],
|
|
64
87
|
nodeId: number,
|
|
65
88
|
depth: number,
|
|
66
89
|
noTests: boolean,
|
|
@@ -81,18 +104,8 @@ function buildTransitiveCallers(
|
|
|
81
104
|
if (unvisited.length === 0) break;
|
|
82
105
|
|
|
83
106
|
const batchCallers = repo.findCallersBatch(unvisited.map((f) => f.id));
|
|
84
|
-
const nextFrontier
|
|
85
|
-
|
|
86
|
-
for (const f of unvisited) {
|
|
87
|
-
const upstream = batchCallers.get(f.id) || [];
|
|
88
|
-
for (const u of upstream) {
|
|
89
|
-
if (noTests && isTestFile(u.file)) continue;
|
|
90
|
-
if (!visited.has(u.id) && !nextFrontierIds.has(u.id)) {
|
|
91
|
-
nextFrontierIds.add(u.id);
|
|
92
|
-
nextFrontier.push(u);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
107
|
+
const nextFrontier = buildNextCallerFrontier(unvisited, batchCallers, visited, noTests);
|
|
108
|
+
|
|
96
109
|
if (nextFrontier.length > 0) {
|
|
97
110
|
transitiveCallers[d] = nextFrontier.map((n) => ({
|
|
98
111
|
name: n.name,
|
|
@@ -258,22 +271,30 @@ function resolveEndpoints(
|
|
|
258
271
|
};
|
|
259
272
|
}
|
|
260
273
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
274
|
+
type NeighborRow = {
|
|
275
|
+
id: number;
|
|
276
|
+
name: string;
|
|
277
|
+
kind: string;
|
|
278
|
+
file: string;
|
|
279
|
+
line: number;
|
|
280
|
+
edge_kind: string;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
type BfsShortestState = {
|
|
284
|
+
visited: Set<number>;
|
|
285
|
+
parent: Map<number, { parentId: number; edgeKind: string }>;
|
|
286
|
+
found: boolean;
|
|
287
|
+
foundDepth: number;
|
|
288
|
+
alternateCount: number;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
/** Build the SQL statement that yields neighbors of a node id in the requested direction. */
|
|
292
|
+
function buildNeighborStmt(
|
|
267
293
|
db: BetterSqlite3Database,
|
|
268
|
-
sourceId: number,
|
|
269
|
-
targetId: number,
|
|
270
294
|
edgeKinds: string[],
|
|
271
295
|
reverse: boolean,
|
|
272
|
-
|
|
273
|
-
noTests: boolean,
|
|
274
|
-
) {
|
|
296
|
+
): ReturnType<BetterSqlite3Database['prepare']> {
|
|
275
297
|
const kindPlaceholders = edgeKinds.map(() => '?').join(', ');
|
|
276
|
-
|
|
277
298
|
// Forward: source_id -> target_id (A calls... calls B)
|
|
278
299
|
// Reverse: target_id -> source_id (B is called by... called by A)
|
|
279
300
|
const neighborQuery = reverse
|
|
@@ -283,50 +304,78 @@ function bfsShortestPath(
|
|
|
283
304
|
: `SELECT n.id, n.name, n.kind, n.file, n.line, e.kind AS edge_kind
|
|
284
305
|
FROM edges e JOIN nodes n ON e.target_id = n.id
|
|
285
306
|
WHERE e.source_id = ? AND e.kind IN (${kindPlaceholders})`;
|
|
286
|
-
|
|
307
|
+
return db.prepare(neighborQuery);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Process a single neighbor row during BFS; returns true once the target has been reached. */
|
|
311
|
+
function visitNeighbor(
|
|
312
|
+
n: NeighborRow,
|
|
313
|
+
currentId: number,
|
|
314
|
+
depth: number,
|
|
315
|
+
targetId: number,
|
|
316
|
+
state: BfsShortestState,
|
|
317
|
+
nextQueue: number[],
|
|
318
|
+
noTests: boolean,
|
|
319
|
+
): void {
|
|
320
|
+
if (noTests && isTestFile(n.file)) return;
|
|
321
|
+
if (n.id === targetId) {
|
|
322
|
+
if (!state.found) {
|
|
323
|
+
state.found = true;
|
|
324
|
+
state.foundDepth = depth;
|
|
325
|
+
state.parent.set(n.id, { parentId: currentId, edgeKind: n.edge_kind });
|
|
326
|
+
}
|
|
327
|
+
state.alternateCount++;
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (state.visited.has(n.id)) return;
|
|
331
|
+
state.visited.add(n.id);
|
|
332
|
+
state.parent.set(n.id, { parentId: currentId, edgeKind: n.edge_kind });
|
|
333
|
+
nextQueue.push(n.id);
|
|
334
|
+
}
|
|
287
335
|
|
|
288
|
-
|
|
289
|
-
|
|
336
|
+
/**
|
|
337
|
+
* BFS from sourceId toward targetId.
|
|
338
|
+
* Returns { found, parent, alternateCount, foundDepth }.
|
|
339
|
+
* `parent` maps nodeId -> { parentId, edgeKind }.
|
|
340
|
+
*/
|
|
341
|
+
function bfsShortestPath(
|
|
342
|
+
db: BetterSqlite3Database,
|
|
343
|
+
sourceId: number,
|
|
344
|
+
targetId: number,
|
|
345
|
+
edgeKinds: string[],
|
|
346
|
+
reverse: boolean,
|
|
347
|
+
maxDepth: number,
|
|
348
|
+
noTests: boolean,
|
|
349
|
+
) {
|
|
350
|
+
const neighborStmt = buildNeighborStmt(db, edgeKinds, reverse);
|
|
351
|
+
const state: BfsShortestState = {
|
|
352
|
+
visited: new Set([sourceId]),
|
|
353
|
+
parent: new Map(),
|
|
354
|
+
found: false,
|
|
355
|
+
foundDepth: -1,
|
|
356
|
+
alternateCount: 0,
|
|
357
|
+
};
|
|
290
358
|
let queue = [sourceId];
|
|
291
|
-
let found = false;
|
|
292
|
-
let alternateCount = 0;
|
|
293
|
-
let foundDepth = -1;
|
|
294
359
|
|
|
295
360
|
for (let depth = 1; depth <= maxDepth; depth++) {
|
|
296
361
|
const nextQueue: number[] = [];
|
|
297
362
|
for (const currentId of queue) {
|
|
298
|
-
const neighbors = neighborStmt.all(currentId, ...edgeKinds) as
|
|
299
|
-
id: number;
|
|
300
|
-
name: string;
|
|
301
|
-
kind: string;
|
|
302
|
-
file: string;
|
|
303
|
-
line: number;
|
|
304
|
-
edge_kind: string;
|
|
305
|
-
}>;
|
|
363
|
+
const neighbors = neighborStmt.all(currentId, ...edgeKinds) as NeighborRow[];
|
|
306
364
|
for (const n of neighbors) {
|
|
307
|
-
|
|
308
|
-
if (n.id === targetId) {
|
|
309
|
-
if (!found) {
|
|
310
|
-
found = true;
|
|
311
|
-
foundDepth = depth;
|
|
312
|
-
parent.set(n.id, { parentId: currentId, edgeKind: n.edge_kind });
|
|
313
|
-
}
|
|
314
|
-
alternateCount++;
|
|
315
|
-
continue;
|
|
316
|
-
}
|
|
317
|
-
if (!visited.has(n.id)) {
|
|
318
|
-
visited.add(n.id);
|
|
319
|
-
parent.set(n.id, { parentId: currentId, edgeKind: n.edge_kind });
|
|
320
|
-
nextQueue.push(n.id);
|
|
321
|
-
}
|
|
365
|
+
visitNeighbor(n, currentId, depth, targetId, state, nextQueue, noTests);
|
|
322
366
|
}
|
|
323
367
|
}
|
|
324
|
-
if (found) break;
|
|
368
|
+
if (state.found) break;
|
|
325
369
|
queue = nextQueue;
|
|
326
370
|
if (queue.length === 0) break;
|
|
327
371
|
}
|
|
328
372
|
|
|
329
|
-
return {
|
|
373
|
+
return {
|
|
374
|
+
found: state.found,
|
|
375
|
+
parent: state.parent,
|
|
376
|
+
alternateCount: state.alternateCount,
|
|
377
|
+
foundDepth: state.foundDepth,
|
|
378
|
+
};
|
|
330
379
|
}
|
|
331
380
|
|
|
332
381
|
/**
|
|
@@ -474,6 +523,53 @@ export function pathData(
|
|
|
474
523
|
|
|
475
524
|
// ── File-level shortest path ────────────────────────────────────────────
|
|
476
525
|
|
|
526
|
+
type FileBfsState = {
|
|
527
|
+
visited: Set<string>;
|
|
528
|
+
parentMap: Map<string, string>;
|
|
529
|
+
found: boolean;
|
|
530
|
+
alternateCount: number;
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
/** Process a neighbor file during file-level BFS; updates state in place. */
|
|
534
|
+
function visitFileNeighbor(
|
|
535
|
+
neighborFile: string,
|
|
536
|
+
currentFile: string,
|
|
537
|
+
targetFile: string,
|
|
538
|
+
state: FileBfsState,
|
|
539
|
+
nextQueue: string[],
|
|
540
|
+
noTests: boolean,
|
|
541
|
+
): void {
|
|
542
|
+
if (noTests && isTestFile(neighborFile)) return;
|
|
543
|
+
if (neighborFile === targetFile) {
|
|
544
|
+
if (!state.found) {
|
|
545
|
+
state.found = true;
|
|
546
|
+
state.parentMap.set(neighborFile, currentFile);
|
|
547
|
+
}
|
|
548
|
+
state.alternateCount++;
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
if (state.visited.has(neighborFile)) return;
|
|
552
|
+
state.visited.add(neighborFile);
|
|
553
|
+
state.parentMap.set(neighborFile, currentFile);
|
|
554
|
+
nextQueue.push(neighborFile);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/** Reconstruct file path from target back to source using parent links. */
|
|
558
|
+
function reconstructFilePath(
|
|
559
|
+
parentMap: Map<string, string>,
|
|
560
|
+
sourceFile: string,
|
|
561
|
+
targetFile: string,
|
|
562
|
+
): string[] {
|
|
563
|
+
const filePath: string[] = [targetFile];
|
|
564
|
+
let cur = targetFile;
|
|
565
|
+
while (cur !== sourceFile) {
|
|
566
|
+
cur = parentMap.get(cur)!;
|
|
567
|
+
filePath.push(cur);
|
|
568
|
+
}
|
|
569
|
+
filePath.reverse();
|
|
570
|
+
return filePath;
|
|
571
|
+
}
|
|
572
|
+
|
|
477
573
|
/** BFS over file adjacency graph to find shortest path. */
|
|
478
574
|
function bfsFilePath(
|
|
479
575
|
neighborStmt: ReturnType<BetterSqlite3Database['prepare']>,
|
|
@@ -483,11 +579,13 @@ function bfsFilePath(
|
|
|
483
579
|
maxDepth: number,
|
|
484
580
|
noTests: boolean,
|
|
485
581
|
): { found: boolean; path: string[]; alternateCount: number } {
|
|
486
|
-
const
|
|
487
|
-
|
|
582
|
+
const state: FileBfsState = {
|
|
583
|
+
visited: new Set([sourceFile]),
|
|
584
|
+
parentMap: new Map<string, string>(),
|
|
585
|
+
found: false,
|
|
586
|
+
alternateCount: 0,
|
|
587
|
+
};
|
|
488
588
|
let queue = [sourceFile];
|
|
489
|
-
let found = false;
|
|
490
|
-
let alternateCount = 0;
|
|
491
589
|
|
|
492
590
|
for (let depth = 1; depth <= maxDepth; depth++) {
|
|
493
591
|
const nextQueue: string[] = [];
|
|
@@ -496,38 +594,21 @@ function bfsFilePath(
|
|
|
496
594
|
neighbor_file: string;
|
|
497
595
|
}>;
|
|
498
596
|
for (const n of neighbors) {
|
|
499
|
-
|
|
500
|
-
if (n.neighbor_file === targetFile) {
|
|
501
|
-
if (!found) {
|
|
502
|
-
found = true;
|
|
503
|
-
parentMap.set(n.neighbor_file, currentFile);
|
|
504
|
-
}
|
|
505
|
-
alternateCount++;
|
|
506
|
-
continue;
|
|
507
|
-
}
|
|
508
|
-
if (!visited.has(n.neighbor_file)) {
|
|
509
|
-
visited.add(n.neighbor_file);
|
|
510
|
-
parentMap.set(n.neighbor_file, currentFile);
|
|
511
|
-
nextQueue.push(n.neighbor_file);
|
|
512
|
-
}
|
|
597
|
+
visitFileNeighbor(n.neighbor_file, currentFile, targetFile, state, nextQueue, noTests);
|
|
513
598
|
}
|
|
514
599
|
}
|
|
515
|
-
if (found) break;
|
|
600
|
+
if (state.found) break;
|
|
516
601
|
queue = nextQueue;
|
|
517
602
|
if (queue.length === 0) break;
|
|
518
603
|
}
|
|
519
604
|
|
|
520
|
-
if (!found) return { found: false, path: [], alternateCount: 0 };
|
|
605
|
+
if (!state.found) return { found: false, path: [], alternateCount: 0 };
|
|
521
606
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
filePath.push(cur);
|
|
528
|
-
}
|
|
529
|
-
filePath.reverse();
|
|
530
|
-
return { found: true, path: filePath, alternateCount: Math.max(0, alternateCount - 1) };
|
|
607
|
+
return {
|
|
608
|
+
found: true,
|
|
609
|
+
path: reconstructFilePath(state.parentMap, sourceFile, targetFile),
|
|
610
|
+
alternateCount: Math.max(0, state.alternateCount - 1),
|
|
611
|
+
};
|
|
531
612
|
}
|
|
532
613
|
|
|
533
614
|
/**
|
|
@@ -83,6 +83,63 @@ function expandImplementors(
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/** Record a caller node at depth `d`, adding to frontier and levels. */
|
|
87
|
+
function recordCaller(
|
|
88
|
+
caller: RelatedNodeRow,
|
|
89
|
+
parentId: number,
|
|
90
|
+
depth: number,
|
|
91
|
+
visited: Set<number>,
|
|
92
|
+
nextFrontier: number[],
|
|
93
|
+
levels: BfsLevels,
|
|
94
|
+
noTests: boolean,
|
|
95
|
+
onVisit?: BfsOnVisit,
|
|
96
|
+
): void {
|
|
97
|
+
if (visited.has(caller.id) || (noTests && isTestFile(caller.file))) return;
|
|
98
|
+
visited.add(caller.id);
|
|
99
|
+
nextFrontier.push(caller.id);
|
|
100
|
+
if (!levels[depth]) levels[depth] = [];
|
|
101
|
+
levels[depth]!.push(toSymbolRef(caller));
|
|
102
|
+
if (onVisit) onVisit(caller, parentId, depth);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Process all callers of one frontier node, recording new nodes and expanding implementors. */
|
|
106
|
+
function processFrontierNode(
|
|
107
|
+
repo: InstanceType<typeof Repository>,
|
|
108
|
+
fid: number,
|
|
109
|
+
depth: number,
|
|
110
|
+
visited: Set<number>,
|
|
111
|
+
nextFrontier: number[],
|
|
112
|
+
levels: BfsLevels,
|
|
113
|
+
noTests: boolean,
|
|
114
|
+
resolveImplementors: boolean,
|
|
115
|
+
onVisit?: BfsOnVisit,
|
|
116
|
+
): void {
|
|
117
|
+
const callers = repo.findDistinctCallers(fid) as RelatedNodeRow[];
|
|
118
|
+
for (const c of callers) {
|
|
119
|
+
recordCaller(c, fid, depth, visited, nextFrontier, levels, noTests, onVisit);
|
|
120
|
+
if (resolveImplementors && INTERFACE_LIKE_KINDS.has(c.kind)) {
|
|
121
|
+
expandImplementors(repo, c.id, depth + 1, visited, nextFrontier, levels, noTests, onVisit);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Seed BFS with implementors of the start node when it is an interface/trait. */
|
|
127
|
+
function seedInterfaceImplementors(
|
|
128
|
+
repo: InstanceType<typeof Repository>,
|
|
129
|
+
startId: number,
|
|
130
|
+
visited: Set<number>,
|
|
131
|
+
levels: BfsLevels,
|
|
132
|
+
noTests: boolean,
|
|
133
|
+
onVisit?: BfsOnVisit,
|
|
134
|
+
): number[] {
|
|
135
|
+
const implNextFrontier: number[] = [];
|
|
136
|
+
const startNode = repo.findNodeById(startId) as NodeRow | undefined;
|
|
137
|
+
if (startNode && INTERFACE_LIKE_KINDS.has(startNode.kind)) {
|
|
138
|
+
expandImplementors(repo, startId, 1, visited, implNextFrontier, levels, noTests, onVisit);
|
|
139
|
+
}
|
|
140
|
+
return implNextFrontier;
|
|
141
|
+
}
|
|
142
|
+
|
|
86
143
|
export function bfsTransitiveCallers(
|
|
87
144
|
dbOrRepo: BetterSqlite3Database | InstanceType<typeof Repository>,
|
|
88
145
|
startId: number,
|
|
@@ -105,13 +162,9 @@ export function bfsTransitiveCallers(
|
|
|
105
162
|
let frontier = [startId];
|
|
106
163
|
|
|
107
164
|
// Seed: if start node is an interface/trait, include its implementors at depth 1
|
|
108
|
-
const implNextFrontier
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (startNode && INTERFACE_LIKE_KINDS.has(startNode.kind)) {
|
|
112
|
-
expandImplementors(repo, startId, 1, visited, implNextFrontier, levels, noTests, onVisit);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
165
|
+
const implNextFrontier = resolveImplementors
|
|
166
|
+
? seedInterfaceImplementors(repo, startId, visited, levels, noTests, onVisit)
|
|
167
|
+
: [];
|
|
115
168
|
|
|
116
169
|
for (let d = 1; d <= maxDepth; d++) {
|
|
117
170
|
if (d === 1 && implNextFrontier.length > 0) {
|
|
@@ -119,19 +172,17 @@ export function bfsTransitiveCallers(
|
|
|
119
172
|
}
|
|
120
173
|
const nextFrontier: number[] = [];
|
|
121
174
|
for (const fid of frontier) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
}
|
|
175
|
+
processFrontierNode(
|
|
176
|
+
repo,
|
|
177
|
+
fid,
|
|
178
|
+
d,
|
|
179
|
+
visited,
|
|
180
|
+
nextFrontier,
|
|
181
|
+
levels,
|
|
182
|
+
noTests,
|
|
183
|
+
resolveImplementors,
|
|
184
|
+
onVisit,
|
|
185
|
+
);
|
|
135
186
|
}
|
|
136
187
|
frontier = nextFrontier;
|
|
137
188
|
if (frontier.length === 0) break;
|
|
@@ -140,6 +191,53 @@ export function bfsTransitiveCallers(
|
|
|
140
191
|
return { totalDependents: visited.size - 1, levels };
|
|
141
192
|
}
|
|
142
193
|
|
|
194
|
+
/** BFS over import dependents, returning visited node IDs and depth-per-id map. */
|
|
195
|
+
function bfsImportDependents(
|
|
196
|
+
repo: InstanceType<typeof Repository>,
|
|
197
|
+
seedNodes: NodeRow[],
|
|
198
|
+
noTests: boolean,
|
|
199
|
+
): { visited: Set<number>; levels: Map<number, number> } {
|
|
200
|
+
const visited = new Set<number>();
|
|
201
|
+
const queue: number[] = [];
|
|
202
|
+
const levels = new Map<number, number>();
|
|
203
|
+
|
|
204
|
+
for (const fn of seedNodes) {
|
|
205
|
+
visited.add(fn.id);
|
|
206
|
+
queue.push(fn.id);
|
|
207
|
+
levels.set(fn.id, 0);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
while (queue.length > 0) {
|
|
211
|
+
const current = queue.shift()!;
|
|
212
|
+
const level = levels.get(current)!;
|
|
213
|
+
const dependents = repo.findImportDependents(current) as RelatedNodeRow[];
|
|
214
|
+
for (const dep of dependents) {
|
|
215
|
+
if (visited.has(dep.id)) continue;
|
|
216
|
+
if (noTests && isTestFile(dep.file)) continue;
|
|
217
|
+
visited.add(dep.id);
|
|
218
|
+
queue.push(dep.id);
|
|
219
|
+
levels.set(dep.id, level + 1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return { visited, levels };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Group visited dependents by depth (excluding seed depth 0). */
|
|
227
|
+
function groupDependentsByLevel(
|
|
228
|
+
repo: InstanceType<typeof Repository>,
|
|
229
|
+
levels: Map<number, number>,
|
|
230
|
+
): Record<number, Array<{ file: string }>> {
|
|
231
|
+
const byLevel: Record<number, Array<{ file: string }>> = {};
|
|
232
|
+
for (const [id, level] of levels) {
|
|
233
|
+
if (level === 0) continue;
|
|
234
|
+
if (!byLevel[level]) byLevel[level] = [];
|
|
235
|
+
const node = repo.findNodeById(id) as NodeRow | undefined;
|
|
236
|
+
if (node) byLevel[level].push({ file: node.file });
|
|
237
|
+
}
|
|
238
|
+
return byLevel;
|
|
239
|
+
}
|
|
240
|
+
|
|
143
241
|
export function impactAnalysisData(
|
|
144
242
|
file: string,
|
|
145
243
|
customDbPath: string,
|
|
@@ -152,36 +250,8 @@ export function impactAnalysisData(
|
|
|
152
250
|
return { file, sources: [], levels: {}, totalDependents: 0 };
|
|
153
251
|
}
|
|
154
252
|
|
|
155
|
-
const visited =
|
|
156
|
-
const
|
|
157
|
-
const levels = new Map<number, number>();
|
|
158
|
-
|
|
159
|
-
for (const fn of fileNodes) {
|
|
160
|
-
visited.add(fn.id);
|
|
161
|
-
queue.push(fn.id);
|
|
162
|
-
levels.set(fn.id, 0);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
while (queue.length > 0) {
|
|
166
|
-
const current = queue.shift()!;
|
|
167
|
-
const level = levels.get(current)!;
|
|
168
|
-
const dependents = repo.findImportDependents(current) as RelatedNodeRow[];
|
|
169
|
-
for (const dep of dependents) {
|
|
170
|
-
if (!visited.has(dep.id) && (!noTests || !isTestFile(dep.file))) {
|
|
171
|
-
visited.add(dep.id);
|
|
172
|
-
queue.push(dep.id);
|
|
173
|
-
levels.set(dep.id, level + 1);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const byLevel: Record<number, Array<{ file: string }>> = {};
|
|
179
|
-
for (const [id, level] of levels) {
|
|
180
|
-
if (level === 0) continue;
|
|
181
|
-
if (!byLevel[level]) byLevel[level] = [];
|
|
182
|
-
const node = repo.findNodeById(id) as NodeRow | undefined;
|
|
183
|
-
if (node) byLevel[level].push({ file: node.file });
|
|
184
|
-
}
|
|
253
|
+
const { visited, levels } = bfsImportDependents(repo, fileNodes, noTests);
|
|
254
|
+
const byLevel = groupDependentsByLevel(repo, levels);
|
|
185
255
|
|
|
186
256
|
return {
|
|
187
257
|
file,
|