@optave/codegraph 3.5.0 → 3.7.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/README.md +47 -21
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +119 -127
- package/dist/ast-analysis/engine.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 +14 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.js +11 -13
- package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
- package/dist/db/connection.d.ts +12 -2
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +81 -53
- package/dist/db/connection.js.map +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +38 -32
- package/dist/db/migrations.js.map +1 -1
- package/dist/domain/analysis/context.d.ts.map +1 -1
- package/dist/domain/analysis/context.js +51 -66
- package/dist/domain/analysis/context.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +62 -70
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/diff-impact.d.ts +9 -7
- package/dist/domain/analysis/diff-impact.d.ts.map +1 -1
- package/dist/domain/analysis/exports.d.ts.map +1 -1
- package/dist/domain/analysis/exports.js +29 -33
- package/dist/domain/analysis/exports.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts +15 -17
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +35 -65
- 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 +91 -6
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/analysis/query-helpers.d.ts +20 -0
- package/dist/domain/analysis/query-helpers.d.ts.map +1 -0
- package/dist/domain/analysis/query-helpers.js +27 -0
- package/dist/domain/analysis/query-helpers.js.map +1 -0
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +15 -9
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +3 -2
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +69 -3
- 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 +7 -51
- 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 +7 -5
- package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.js +2 -2
- package/dist/domain/graph/builder/stages/collect-files.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 +2 -2
- 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 +124 -105
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +28 -15
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +3 -2
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/resolve.d.ts +0 -4
- package/dist/domain/graph/resolve.d.ts.map +1 -1
- package/dist/domain/graph/resolve.js +32 -48
- package/dist/domain/graph/resolve.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +12 -12
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +1 -1
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +206 -101
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/search/cli-formatter.d.ts.map +1 -1
- package/dist/domain/search/search/cli-formatter.js +88 -83
- package/dist/domain/search/search/cli-formatter.js.map +1 -1
- package/dist/extractors/bash.d.ts +6 -0
- package/dist/extractors/bash.d.ts.map +1 -0
- package/dist/extractors/bash.js +91 -0
- package/dist/extractors/bash.js.map +1 -0
- package/dist/extractors/c.d.ts +6 -0
- package/dist/extractors/c.d.ts.map +1 -0
- package/dist/extractors/c.js +204 -0
- package/dist/extractors/c.js.map +1 -0
- package/dist/extractors/cpp.d.ts +6 -0
- package/dist/extractors/cpp.d.ts.map +1 -0
- package/dist/extractors/cpp.js +283 -0
- package/dist/extractors/cpp.js.map +1 -0
- package/dist/extractors/csharp.d.ts.map +1 -1
- package/dist/extractors/csharp.js +42 -54
- package/dist/extractors/csharp.js.map +1 -1
- package/dist/extractors/dart.d.ts +6 -0
- package/dist/extractors/dart.d.ts.map +1 -0
- package/dist/extractors/dart.js +277 -0
- package/dist/extractors/dart.js.map +1 -0
- package/dist/extractors/elixir.d.ts +9 -0
- package/dist/extractors/elixir.d.ts.map +1 -0
- package/dist/extractors/elixir.js +223 -0
- package/dist/extractors/elixir.js.map +1 -0
- package/dist/extractors/go.d.ts.map +1 -1
- package/dist/extractors/go.js +126 -130
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/haskell.d.ts +8 -0
- package/dist/extractors/haskell.d.ts.map +1 -0
- package/dist/extractors/haskell.js +217 -0
- package/dist/extractors/haskell.js.map +1 -0
- package/dist/extractors/hcl.js +6 -6
- package/dist/extractors/hcl.js.map +1 -1
- package/dist/extractors/helpers.d.ts +32 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +74 -0
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/index.d.ts +12 -0
- package/dist/extractors/index.d.ts.map +1 -1
- package/dist/extractors/index.js +12 -0
- package/dist/extractors/index.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +32 -47
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.d.ts.map +1 -1
- package/dist/extractors/javascript.js +306 -292
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/kotlin.d.ts +6 -0
- package/dist/extractors/kotlin.d.ts.map +1 -0
- package/dist/extractors/kotlin.js +275 -0
- package/dist/extractors/kotlin.js.map +1 -0
- package/dist/extractors/lua.d.ts +6 -0
- package/dist/extractors/lua.d.ts.map +1 -0
- package/dist/extractors/lua.js +162 -0
- package/dist/extractors/lua.js.map +1 -0
- package/dist/extractors/ocaml.d.ts +6 -0
- package/dist/extractors/ocaml.d.ts.map +1 -0
- package/dist/extractors/ocaml.js +236 -0
- package/dist/extractors/ocaml.js.map +1 -0
- package/dist/extractors/php.d.ts.map +1 -1
- package/dist/extractors/php.js +39 -44
- package/dist/extractors/php.js.map +1 -1
- package/dist/extractors/python.d.ts.map +1 -1
- package/dist/extractors/python.js +75 -93
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/ruby.js +6 -13
- package/dist/extractors/ruby.js.map +1 -1
- package/dist/extractors/rust.d.ts.map +1 -1
- package/dist/extractors/rust.js +58 -83
- package/dist/extractors/rust.js.map +1 -1
- package/dist/extractors/scala.d.ts +6 -0
- package/dist/extractors/scala.d.ts.map +1 -0
- package/dist/extractors/scala.js +269 -0
- package/dist/extractors/scala.js.map +1 -0
- package/dist/extractors/swift.d.ts +6 -0
- package/dist/extractors/swift.d.ts.map +1 -0
- package/dist/extractors/swift.js +275 -0
- package/dist/extractors/swift.js.map +1 -0
- package/dist/extractors/zig.d.ts +9 -0
- package/dist/extractors/zig.d.ts.map +1 -0
- package/dist/extractors/zig.js +276 -0
- package/dist/extractors/zig.js.map +1 -0
- package/dist/features/ast.d.ts +2 -0
- package/dist/features/ast.d.ts.map +1 -1
- package/dist/features/ast.js +9 -24
- package/dist/features/ast.js.map +1 -1
- package/dist/features/audit.d.ts.map +1 -1
- package/dist/features/audit.js +17 -21
- package/dist/features/audit.js.map +1 -1
- package/dist/features/branch-compare.d.ts.map +1 -1
- package/dist/features/branch-compare.js +47 -3
- package/dist/features/branch-compare.js.map +1 -1
- package/dist/features/cfg.d.ts +7 -1
- package/dist/features/cfg.d.ts.map +1 -1
- package/dist/features/cfg.js +72 -61
- package/dist/features/cfg.js.map +1 -1
- package/dist/features/check.d.ts.map +1 -1
- package/dist/features/check.js +79 -62
- package/dist/features/check.js.map +1 -1
- package/dist/features/complexity-query.d.ts.map +1 -1
- package/dist/features/complexity-query.js +142 -137
- package/dist/features/complexity-query.js.map +1 -1
- package/dist/features/complexity.d.ts +7 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +62 -1
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts +7 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +356 -188
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/graph-enrichment.d.ts.map +1 -1
- package/dist/features/graph-enrichment.js +117 -104
- package/dist/features/graph-enrichment.js.map +1 -1
- package/dist/features/sequence.d.ts.map +1 -1
- package/dist/features/sequence.js +25 -4
- package/dist/features/sequence.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +29 -4
- package/dist/features/structure-query.js.map +1 -1
- package/dist/features/structure.d.ts.map +1 -1
- package/dist/features/structure.js +35 -15
- package/dist/features/structure.js.map +1 -1
- package/dist/graph/algorithms/leiden/adapter.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/adapter.js +88 -73
- package/dist/graph/algorithms/leiden/adapter.js.map +1 -1
- package/dist/graph/algorithms/leiden/index.js +43 -28
- package/dist/graph/algorithms/leiden/index.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +90 -104
- package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
- package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/partition.js +89 -106
- package/dist/graph/algorithms/leiden/partition.js.map +1 -1
- package/dist/graph/model.d.ts +2 -0
- package/dist/graph/model.d.ts.map +1 -1
- package/dist/graph/model.js +20 -8
- package/dist/graph/model.js.map +1 -1
- package/dist/infrastructure/config.d.ts +0 -8
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +73 -62
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/registry.d.ts +0 -8
- package/dist/infrastructure/registry.d.ts.map +1 -1
- package/dist/infrastructure/registry.js +12 -14
- package/dist/infrastructure/registry.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +45 -36
- package/dist/mcp/server.js.map +1 -1
- package/dist/presentation/audit.d.ts.map +1 -1
- package/dist/presentation/audit.js +61 -57
- package/dist/presentation/audit.js.map +1 -1
- package/dist/presentation/branch-compare.d.ts.map +1 -1
- package/dist/presentation/branch-compare.js +56 -38
- package/dist/presentation/branch-compare.js.map +1 -1
- package/dist/presentation/check.d.ts.map +1 -1
- package/dist/presentation/check.js +30 -32
- package/dist/presentation/check.js.map +1 -1
- package/dist/presentation/colors.d.ts.map +1 -1
- package/dist/presentation/colors.js +2 -0
- package/dist/presentation/colors.js.map +1 -1
- package/dist/presentation/complexity.d.ts.map +1 -1
- package/dist/presentation/complexity.js +25 -19
- package/dist/presentation/complexity.js.map +1 -1
- package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
- package/dist/presentation/queries-cli/exports.js +15 -15
- package/dist/presentation/queries-cli/exports.js.map +1 -1
- package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
- package/dist/presentation/queries-cli/impact.js +29 -19
- package/dist/presentation/queries-cli/impact.js.map +1 -1
- package/dist/types.d.ts +182 -7
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-bash.wasm +0 -0
- package/grammars/tree-sitter-c.wasm +0 -0
- package/grammars/tree-sitter-cpp.wasm +0 -0
- package/grammars/tree-sitter-dart.wasm +0 -0
- package/grammars/tree-sitter-elixir.wasm +0 -0
- package/grammars/tree-sitter-haskell.wasm +0 -0
- package/grammars/tree-sitter-kotlin.wasm +0 -0
- package/grammars/tree-sitter-lua.wasm +0 -0
- package/grammars/tree-sitter-ocaml.wasm +0 -0
- package/grammars/tree-sitter-scala.wasm +0 -0
- package/grammars/tree-sitter-swift.wasm +0 -0
- package/grammars/tree-sitter-zig.wasm +0 -0
- package/package.json +19 -7
- package/src/ast-analysis/engine.ts +147 -138
- package/src/ast-analysis/visitors/ast-store-visitor.ts +15 -2
- package/src/ast-analysis/visitors/complexity-visitor.ts +11 -11
- package/src/db/connection.ts +90 -59
- package/src/db/index.ts +1 -0
- package/src/db/migrations.ts +36 -32
- package/src/domain/analysis/context.ts +73 -75
- package/src/domain/analysis/dependencies.ts +78 -68
- package/src/domain/analysis/exports.ts +45 -34
- package/src/domain/analysis/fn-impact.ts +67 -64
- package/src/domain/analysis/module-map.ts +103 -8
- package/src/domain/analysis/query-helpers.ts +35 -0
- package/src/domain/graph/builder/helpers.ts +12 -6
- package/src/domain/graph/builder/incremental.ts +3 -2
- package/src/domain/graph/builder/pipeline.ts +71 -3
- package/src/domain/graph/builder/stages/build-edges.ts +10 -75
- package/src/domain/graph/builder/stages/build-structure.ts +9 -7
- package/src/domain/graph/builder/stages/collect-files.ts +2 -2
- package/src/domain/graph/builder/stages/detect-changes.ts +7 -2
- package/src/domain/graph/builder/stages/finalize.ts +159 -125
- package/src/domain/graph/builder/stages/insert-nodes.ts +32 -21
- package/src/domain/graph/builder/stages/resolve-imports.ts +3 -2
- package/src/domain/graph/resolve.ts +34 -46
- package/src/domain/graph/watcher.ts +12 -14
- package/src/domain/parser.ts +222 -97
- package/src/domain/search/search/cli-formatter.ts +121 -94
- package/src/extractors/bash.ts +97 -0
- package/src/extractors/c.ts +212 -0
- package/src/extractors/cpp.ts +298 -0
- package/src/extractors/csharp.ts +53 -56
- package/src/extractors/dart.ts +304 -0
- package/src/extractors/elixir.ts +251 -0
- package/src/extractors/go.ts +152 -134
- package/src/extractors/haskell.ts +235 -0
- package/src/extractors/hcl.ts +6 -6
- package/src/extractors/helpers.ts +93 -1
- package/src/extractors/index.ts +12 -0
- package/src/extractors/java.ts +43 -48
- package/src/extractors/javascript.ts +328 -281
- package/src/extractors/kotlin.ts +293 -0
- package/src/extractors/lua.ts +169 -0
- package/src/extractors/ocaml.ts +259 -0
- package/src/extractors/php.ts +46 -40
- package/src/extractors/python.ts +81 -104
- package/src/extractors/ruby.ts +6 -13
- package/src/extractors/rust.ts +65 -85
- package/src/extractors/scala.ts +285 -0
- package/src/extractors/swift.ts +293 -0
- package/src/extractors/zig.ts +294 -0
- package/src/features/ast.ts +10 -25
- package/src/features/audit.ts +24 -20
- package/src/features/branch-compare.ts +51 -4
- package/src/features/cfg.ts +113 -65
- package/src/features/check.ts +90 -74
- package/src/features/complexity-query.ts +181 -163
- package/src/features/complexity.ts +64 -1
- package/src/features/dataflow.ts +462 -217
- package/src/features/graph-enrichment.ts +161 -117
- package/src/features/sequence.ts +27 -4
- package/src/features/structure-query.ts +43 -4
- package/src/features/structure.ts +50 -22
- package/src/graph/algorithms/leiden/adapter.ts +126 -71
- package/src/graph/algorithms/leiden/index.ts +67 -28
- package/src/graph/algorithms/leiden/optimiser.ts +114 -105
- package/src/graph/algorithms/leiden/partition.ts +131 -98
- package/src/graph/model.ts +19 -7
- package/src/infrastructure/config.ts +60 -58
- package/src/infrastructure/registry.ts +17 -14
- package/src/mcp/server.ts +46 -37
- package/src/presentation/audit.ts +72 -67
- package/src/presentation/branch-compare.ts +54 -50
- package/src/presentation/check.ts +34 -34
- package/src/presentation/colors.ts +2 -0
- package/src/presentation/complexity.ts +39 -33
- package/src/presentation/queries-cli/exports.ts +17 -17
- package/src/presentation/queries-cli/impact.ts +30 -22
- package/src/types.ts +195 -7
package/src/features/dataflow.ts
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from '../ast-analysis/shared.js';
|
|
20
20
|
import { walkWithVisitors } from '../ast-analysis/visitor.js';
|
|
21
21
|
import { createDataflowVisitor } from '../ast-analysis/visitors/dataflow-visitor.js';
|
|
22
|
-
import { hasDataflowTable, openReadonlyOrFail } from '../db/index.js';
|
|
22
|
+
import { hasDataflowTable, openReadonlyOrFail, openReadonlyWithNative } from '../db/index.js';
|
|
23
23
|
import { ALL_SYMBOL_KINDS, normalizeSymbol } from '../domain/queries.js';
|
|
24
24
|
import { debug, info } from '../infrastructure/logger.js';
|
|
25
25
|
import { isTestFile } from '../infrastructure/test-filter.js';
|
|
@@ -241,9 +241,121 @@ export async function buildDataflowEdges(
|
|
|
241
241
|
db: BetterSqlite3Database,
|
|
242
242
|
fileSymbols: Map<string, FileSymbolsDataflow>,
|
|
243
243
|
rootDir: string,
|
|
244
|
-
|
|
244
|
+
engineOpts?: {
|
|
245
|
+
nativeDb?: { bulkInsertDataflow?(edges: Array<Record<string, unknown>>): number };
|
|
246
|
+
suspendJsDb?: () => void;
|
|
247
|
+
resumeJsDb?: () => void;
|
|
248
|
+
},
|
|
245
249
|
): Promise<void> {
|
|
246
250
|
const extToLang = buildExtToLangMap();
|
|
251
|
+
|
|
252
|
+
// ── Native bulk-insert fast path ──────────────────────────────────────
|
|
253
|
+
const nativeDb = engineOpts?.nativeDb;
|
|
254
|
+
if (nativeDb?.bulkInsertDataflow) {
|
|
255
|
+
let needsJsFallback = false;
|
|
256
|
+
const nativeEdges: Array<Record<string, unknown>> = [];
|
|
257
|
+
|
|
258
|
+
const getNodeByNameAndFile = db.prepare<{
|
|
259
|
+
id: number;
|
|
260
|
+
name: string;
|
|
261
|
+
kind: string;
|
|
262
|
+
file: string;
|
|
263
|
+
line: number;
|
|
264
|
+
}>(
|
|
265
|
+
`SELECT id, name, kind, file, line FROM nodes
|
|
266
|
+
WHERE name = ? AND file = ? AND kind IN ('function', 'method')`,
|
|
267
|
+
);
|
|
268
|
+
const getNodeByName = db.prepare<{
|
|
269
|
+
id: number;
|
|
270
|
+
name: string;
|
|
271
|
+
kind: string;
|
|
272
|
+
file: string;
|
|
273
|
+
line: number;
|
|
274
|
+
}>(
|
|
275
|
+
`SELECT id, name, kind, file, line FROM nodes
|
|
276
|
+
WHERE name = ? AND kind IN ('function', 'method')
|
|
277
|
+
ORDER BY file, line LIMIT 10`,
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
for (const [relPath, symbols] of fileSymbols) {
|
|
281
|
+
const ext = path.extname(relPath).toLowerCase();
|
|
282
|
+
if (!DATAFLOW_EXTENSIONS.has(ext)) continue;
|
|
283
|
+
if (!symbols.dataflow) {
|
|
284
|
+
needsJsFallback = true;
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const resolveNode = (funcName: string): { id: number } | null => {
|
|
289
|
+
const local = getNodeByNameAndFile.all(funcName, relPath);
|
|
290
|
+
if (local.length > 0) return local[0]!;
|
|
291
|
+
const global = getNodeByName.all(funcName);
|
|
292
|
+
return global.length > 0 ? global[0]! : null;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const data = symbols.dataflow;
|
|
296
|
+
for (const flow of data.argFlows as ArgFlow[]) {
|
|
297
|
+
const sourceNode = resolveNode(flow.callerFunc);
|
|
298
|
+
const targetNode = resolveNode(flow.calleeName);
|
|
299
|
+
if (sourceNode && targetNode) {
|
|
300
|
+
nativeEdges.push({
|
|
301
|
+
sourceId: sourceNode.id,
|
|
302
|
+
targetId: targetNode.id,
|
|
303
|
+
kind: 'flows_to',
|
|
304
|
+
paramIndex: flow.argIndex,
|
|
305
|
+
expression: flow.expression,
|
|
306
|
+
line: flow.line,
|
|
307
|
+
confidence: flow.confidence,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
for (const assignment of data.assignments as Assignment[]) {
|
|
312
|
+
const producerNode = resolveNode(assignment.sourceCallName);
|
|
313
|
+
const consumerNode = resolveNode(assignment.callerFunc);
|
|
314
|
+
if (producerNode && consumerNode) {
|
|
315
|
+
nativeEdges.push({
|
|
316
|
+
sourceId: producerNode.id,
|
|
317
|
+
targetId: consumerNode.id,
|
|
318
|
+
kind: 'returns',
|
|
319
|
+
paramIndex: null,
|
|
320
|
+
expression: assignment.expression,
|
|
321
|
+
line: assignment.line,
|
|
322
|
+
confidence: 1.0,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
for (const mut of data.mutations as Mutation[]) {
|
|
327
|
+
const mutatorNode = resolveNode(mut.funcName);
|
|
328
|
+
if (mutatorNode && mut.binding?.type === 'param') {
|
|
329
|
+
nativeEdges.push({
|
|
330
|
+
sourceId: mutatorNode.id,
|
|
331
|
+
targetId: mutatorNode.id,
|
|
332
|
+
kind: 'mutates',
|
|
333
|
+
paramIndex: null,
|
|
334
|
+
expression: mut.mutatingExpr,
|
|
335
|
+
line: mut.line,
|
|
336
|
+
confidence: 1.0,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (!needsJsFallback) {
|
|
343
|
+
if (nativeEdges.length > 0) {
|
|
344
|
+
let inserted: number;
|
|
345
|
+
try {
|
|
346
|
+
engineOpts?.suspendJsDb?.();
|
|
347
|
+
inserted = nativeDb.bulkInsertDataflow(nativeEdges);
|
|
348
|
+
} finally {
|
|
349
|
+
engineOpts?.resumeJsDb?.();
|
|
350
|
+
}
|
|
351
|
+
info(`Dataflow (native bulk): ${inserted} edges inserted`);
|
|
352
|
+
}
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
debug('Dataflow: some files lack pre-computed data — falling back to JS');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ── JS fallback path ─────────────────────────────────────────────────
|
|
247
359
|
const { parsers, getParserFn } = await initDataflowParsers(fileSymbols);
|
|
248
360
|
|
|
249
361
|
const insert = db.prepare(
|
|
@@ -303,12 +415,137 @@ export async function buildDataflowEdges(
|
|
|
303
415
|
|
|
304
416
|
// findNodes imported from ./shared/find-nodes.js
|
|
305
417
|
|
|
418
|
+
interface DataflowStmts {
|
|
419
|
+
flowsToOut: ReturnType<BetterSqlite3Database['prepare']>;
|
|
420
|
+
flowsToIn: ReturnType<BetterSqlite3Database['prepare']>;
|
|
421
|
+
returnsOut: ReturnType<BetterSqlite3Database['prepare']>;
|
|
422
|
+
returnsIn: ReturnType<BetterSqlite3Database['prepare']>;
|
|
423
|
+
mutatesOut: ReturnType<BetterSqlite3Database['prepare']>;
|
|
424
|
+
mutatesIn: ReturnType<BetterSqlite3Database['prepare']>;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function prepareDataflowStmts(db: BetterSqlite3Database): DataflowStmts {
|
|
428
|
+
return {
|
|
429
|
+
flowsToOut: db.prepare(
|
|
430
|
+
`SELECT d.*, n.name AS target_name, n.kind AS target_kind, n.file AS target_file, n.line AS target_line
|
|
431
|
+
FROM dataflow d JOIN nodes n ON d.target_id = n.id
|
|
432
|
+
WHERE d.source_id = ? AND d.kind = 'flows_to'`,
|
|
433
|
+
),
|
|
434
|
+
flowsToIn: db.prepare(
|
|
435
|
+
`SELECT d.*, n.name AS source_name, n.kind AS source_kind, n.file AS source_file, n.line AS source_line
|
|
436
|
+
FROM dataflow d JOIN nodes n ON d.source_id = n.id
|
|
437
|
+
WHERE d.target_id = ? AND d.kind = 'flows_to'`,
|
|
438
|
+
),
|
|
439
|
+
returnsOut: db.prepare(
|
|
440
|
+
`SELECT d.*, n.name AS target_name, n.kind AS target_kind, n.file AS target_file, n.line AS target_line
|
|
441
|
+
FROM dataflow d JOIN nodes n ON d.target_id = n.id
|
|
442
|
+
WHERE d.source_id = ? AND d.kind = 'returns'`,
|
|
443
|
+
),
|
|
444
|
+
returnsIn: db.prepare(
|
|
445
|
+
`SELECT d.*, n.name AS source_name, n.kind AS source_kind, n.file AS source_file, n.line AS source_line
|
|
446
|
+
FROM dataflow d JOIN nodes n ON d.source_id = n.id
|
|
447
|
+
WHERE d.target_id = ? AND d.kind = 'returns'`,
|
|
448
|
+
),
|
|
449
|
+
mutatesOut: db.prepare(
|
|
450
|
+
`SELECT d.*, n.name AS target_name, n.kind AS target_kind, n.file AS target_file, n.line AS target_line
|
|
451
|
+
FROM dataflow d JOIN nodes n ON d.target_id = n.id
|
|
452
|
+
WHERE d.source_id = ? AND d.kind = 'mutates'`,
|
|
453
|
+
),
|
|
454
|
+
mutatesIn: db.prepare(
|
|
455
|
+
`SELECT d.*, n.name AS source_name, n.kind AS source_kind, n.file AS source_file, n.line AS source_line
|
|
456
|
+
FROM dataflow d JOIN nodes n ON d.source_id = n.id
|
|
457
|
+
WHERE d.target_id = ? AND d.kind = 'mutates'`,
|
|
458
|
+
),
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function buildNodeDataflowResult(
|
|
463
|
+
node: NodeRow,
|
|
464
|
+
stmts: DataflowStmts,
|
|
465
|
+
db: BetterSqlite3Database,
|
|
466
|
+
hc: Map<string, string | null>,
|
|
467
|
+
noTests: boolean,
|
|
468
|
+
): Record<string, unknown> {
|
|
469
|
+
const sym = normalizeSymbol(node, db, hc);
|
|
470
|
+
|
|
471
|
+
const flowsTo = stmts.flowsToOut.all(node.id).map((r: any) => ({
|
|
472
|
+
target: r.target_name,
|
|
473
|
+
kind: r.target_kind,
|
|
474
|
+
file: r.target_file,
|
|
475
|
+
line: r.line,
|
|
476
|
+
paramIndex: r.param_index,
|
|
477
|
+
expression: r.expression,
|
|
478
|
+
confidence: r.confidence,
|
|
479
|
+
}));
|
|
480
|
+
|
|
481
|
+
const flowsFrom = stmts.flowsToIn.all(node.id).map((r: any) => ({
|
|
482
|
+
source: r.source_name,
|
|
483
|
+
kind: r.source_kind,
|
|
484
|
+
file: r.source_file,
|
|
485
|
+
line: r.line,
|
|
486
|
+
paramIndex: r.param_index,
|
|
487
|
+
expression: r.expression,
|
|
488
|
+
confidence: r.confidence,
|
|
489
|
+
}));
|
|
490
|
+
|
|
491
|
+
const returnConsumers = stmts.returnsOut.all(node.id).map((r: any) => ({
|
|
492
|
+
consumer: r.target_name,
|
|
493
|
+
kind: r.target_kind,
|
|
494
|
+
file: r.target_file,
|
|
495
|
+
line: r.line,
|
|
496
|
+
expression: r.expression,
|
|
497
|
+
}));
|
|
498
|
+
|
|
499
|
+
const returnedBy = stmts.returnsIn.all(node.id).map((r: any) => ({
|
|
500
|
+
producer: r.source_name,
|
|
501
|
+
kind: r.source_kind,
|
|
502
|
+
file: r.source_file,
|
|
503
|
+
line: r.line,
|
|
504
|
+
expression: r.expression,
|
|
505
|
+
}));
|
|
506
|
+
|
|
507
|
+
const mutatesTargets = stmts.mutatesOut.all(node.id).map((r: any) => ({
|
|
508
|
+
target: r.target_name,
|
|
509
|
+
expression: r.expression,
|
|
510
|
+
line: r.line,
|
|
511
|
+
}));
|
|
512
|
+
|
|
513
|
+
const mutatedBy = stmts.mutatesIn.all(node.id).map((r: any) => ({
|
|
514
|
+
source: r.source_name,
|
|
515
|
+
expression: r.expression,
|
|
516
|
+
line: r.line,
|
|
517
|
+
}));
|
|
518
|
+
|
|
519
|
+
if (noTests) {
|
|
520
|
+
const filter = (arr: any[]) => arr.filter((r: any) => !isTestFile(r.file));
|
|
521
|
+
return {
|
|
522
|
+
...sym,
|
|
523
|
+
flowsTo: filter(flowsTo),
|
|
524
|
+
flowsFrom: filter(flowsFrom),
|
|
525
|
+
returns: returnConsumers.filter((r) => !isTestFile(r.file)),
|
|
526
|
+
returnedBy: returnedBy.filter((r) => !isTestFile(r.file)),
|
|
527
|
+
mutates: mutatesTargets,
|
|
528
|
+
mutatedBy,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return {
|
|
533
|
+
...sym,
|
|
534
|
+
flowsTo,
|
|
535
|
+
flowsFrom,
|
|
536
|
+
returns: returnConsumers,
|
|
537
|
+
returnedBy,
|
|
538
|
+
mutates: mutatesTargets,
|
|
539
|
+
mutatedBy,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
306
543
|
export function dataflowData(
|
|
307
544
|
name: string,
|
|
308
545
|
customDbPath?: string,
|
|
309
546
|
opts: { noTests?: boolean; file?: string; kind?: string; limit?: number; offset?: number } = {},
|
|
310
547
|
): Record<string, unknown> {
|
|
311
|
-
const db =
|
|
548
|
+
const { db, nativeDb, close } = openReadonlyWithNative(customDbPath);
|
|
312
549
|
try {
|
|
313
550
|
const noTests = opts.noTests || false;
|
|
314
551
|
|
|
@@ -331,120 +568,195 @@ export function dataflowData(
|
|
|
331
568
|
return { name, results: [] };
|
|
332
569
|
}
|
|
333
570
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const returnedBy = returnsIn.all(node.id).map((r: any) => ({
|
|
398
|
-
producer: r.source_name,
|
|
399
|
-
kind: r.source_kind,
|
|
400
|
-
file: r.source_file,
|
|
401
|
-
line: r.line,
|
|
402
|
-
expression: r.expression,
|
|
403
|
-
}));
|
|
404
|
-
|
|
405
|
-
const mutatesTargets = mutatesOut.all(node.id).map((r: any) => ({
|
|
406
|
-
target: r.target_name,
|
|
407
|
-
expression: r.expression,
|
|
408
|
-
line: r.line,
|
|
409
|
-
}));
|
|
410
|
-
|
|
411
|
-
const mutatedBy = mutatesIn.all(node.id).map((r: any) => ({
|
|
412
|
-
source: r.source_name,
|
|
413
|
-
expression: r.expression,
|
|
414
|
-
line: r.line,
|
|
415
|
-
}));
|
|
416
|
-
|
|
417
|
-
if (noTests) {
|
|
418
|
-
const filter = (arr: any[]) => arr.filter((r: any) => !isTestFile(r.file));
|
|
571
|
+
// ── Native fast path: 6 queries per node → 1 napi call per node ──
|
|
572
|
+
if (nativeDb?.getDataflowEdges) {
|
|
573
|
+
const hc = new Map<string, string | null>();
|
|
574
|
+
const results = nodes.map((node: NodeRow) => {
|
|
575
|
+
const sym = normalizeSymbol(node, db, hc);
|
|
576
|
+
const d = nativeDb.getDataflowEdges!(node.id);
|
|
577
|
+
|
|
578
|
+
const flowsTo = d.flowsToOut.map((r) => ({
|
|
579
|
+
target: r.name,
|
|
580
|
+
kind: r.kind,
|
|
581
|
+
file: r.file,
|
|
582
|
+
line: r.line,
|
|
583
|
+
paramIndex: r.paramIndex,
|
|
584
|
+
expression: r.expression,
|
|
585
|
+
confidence: r.confidence,
|
|
586
|
+
}));
|
|
587
|
+
const flowsFrom = d.flowsToIn.map((r) => ({
|
|
588
|
+
source: r.name,
|
|
589
|
+
kind: r.kind,
|
|
590
|
+
file: r.file,
|
|
591
|
+
line: r.line,
|
|
592
|
+
paramIndex: r.paramIndex,
|
|
593
|
+
expression: r.expression,
|
|
594
|
+
confidence: r.confidence,
|
|
595
|
+
}));
|
|
596
|
+
const returnConsumers = d.returnsOut.map((r) => ({
|
|
597
|
+
consumer: r.name,
|
|
598
|
+
kind: r.kind,
|
|
599
|
+
file: r.file,
|
|
600
|
+
line: r.line,
|
|
601
|
+
expression: r.expression,
|
|
602
|
+
}));
|
|
603
|
+
const returnedBy = d.returnsIn.map((r) => ({
|
|
604
|
+
producer: r.name,
|
|
605
|
+
kind: r.kind,
|
|
606
|
+
file: r.file,
|
|
607
|
+
line: r.line,
|
|
608
|
+
expression: r.expression,
|
|
609
|
+
}));
|
|
610
|
+
const mutatesTargets = d.mutatesOut.map((r) => ({
|
|
611
|
+
target: r.name,
|
|
612
|
+
expression: r.expression,
|
|
613
|
+
line: r.line,
|
|
614
|
+
}));
|
|
615
|
+
const mutatedBy = d.mutatesIn.map((r) => ({
|
|
616
|
+
source: r.name,
|
|
617
|
+
expression: r.expression,
|
|
618
|
+
line: r.line,
|
|
619
|
+
}));
|
|
620
|
+
|
|
621
|
+
if (noTests) {
|
|
622
|
+
const filter = (arr: any[]) => arr.filter((r: any) => !isTestFile(r.file));
|
|
623
|
+
return {
|
|
624
|
+
...sym,
|
|
625
|
+
flowsTo: filter(flowsTo),
|
|
626
|
+
flowsFrom: filter(flowsFrom),
|
|
627
|
+
returns: returnConsumers.filter((r) => !isTestFile(r.file)),
|
|
628
|
+
returnedBy: returnedBy.filter((r) => !isTestFile(r.file)),
|
|
629
|
+
mutates: mutatesTargets,
|
|
630
|
+
mutatedBy,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
419
633
|
return {
|
|
420
634
|
...sym,
|
|
421
|
-
flowsTo
|
|
422
|
-
flowsFrom
|
|
423
|
-
returns: returnConsumers
|
|
424
|
-
returnedBy
|
|
635
|
+
flowsTo,
|
|
636
|
+
flowsFrom,
|
|
637
|
+
returns: returnConsumers,
|
|
638
|
+
returnedBy,
|
|
425
639
|
mutates: mutatesTargets,
|
|
426
640
|
mutatedBy,
|
|
427
641
|
};
|
|
428
|
-
}
|
|
642
|
+
});
|
|
643
|
+
const base = { name, results };
|
|
644
|
+
return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
|
|
645
|
+
}
|
|
429
646
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
mutates: mutatesTargets,
|
|
437
|
-
mutatedBy,
|
|
438
|
-
};
|
|
439
|
-
});
|
|
647
|
+
// ── JS fallback ───────────────────────────────────────────────────
|
|
648
|
+
const stmts = prepareDataflowStmts(db);
|
|
649
|
+
const hc = new Map<string, string | null>();
|
|
650
|
+
const results = nodes.map((node: NodeRow) =>
|
|
651
|
+
buildNodeDataflowResult(node, stmts, db, hc, noTests),
|
|
652
|
+
);
|
|
440
653
|
|
|
441
654
|
const base = { name, results };
|
|
442
655
|
return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
|
|
443
656
|
} finally {
|
|
444
|
-
|
|
657
|
+
close();
|
|
445
658
|
}
|
|
446
659
|
}
|
|
447
660
|
|
|
661
|
+
interface BfsParentEntry {
|
|
662
|
+
parentId: number;
|
|
663
|
+
edgeKind: string;
|
|
664
|
+
expression: string;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/** BFS through dataflow edges to find a path from source to target. */
|
|
668
|
+
function bfsDataflowPath(
|
|
669
|
+
db: BetterSqlite3Database,
|
|
670
|
+
sourceId: number,
|
|
671
|
+
targetId: number,
|
|
672
|
+
maxDepth: number,
|
|
673
|
+
noTests: boolean,
|
|
674
|
+
): Map<number, BfsParentEntry> | null {
|
|
675
|
+
const neighborStmt = db.prepare(
|
|
676
|
+
`SELECT n.id, n.name, n.kind, n.file, n.line, d.kind AS edge_kind, d.expression
|
|
677
|
+
FROM dataflow d JOIN nodes n ON d.target_id = n.id
|
|
678
|
+
WHERE d.source_id = ? AND d.kind IN ('flows_to', 'returns')`,
|
|
679
|
+
);
|
|
680
|
+
|
|
681
|
+
const visited = new Set<number>([sourceId]);
|
|
682
|
+
const parent = new Map<number, BfsParentEntry>();
|
|
683
|
+
let queue = [sourceId];
|
|
684
|
+
let found = false;
|
|
685
|
+
|
|
686
|
+
for (let depth = 1; depth <= maxDepth; depth++) {
|
|
687
|
+
const nextQueue: number[] = [];
|
|
688
|
+
for (const currentId of queue) {
|
|
689
|
+
const neighbors = neighborStmt.all(currentId) as Array<{
|
|
690
|
+
id: number;
|
|
691
|
+
file: string;
|
|
692
|
+
edge_kind: string;
|
|
693
|
+
expression: string;
|
|
694
|
+
}>;
|
|
695
|
+
for (const n of neighbors) {
|
|
696
|
+
if (noTests && isTestFile(n.file)) continue;
|
|
697
|
+
if (n.id === targetId) {
|
|
698
|
+
if (!found) {
|
|
699
|
+
found = true;
|
|
700
|
+
parent.set(n.id, {
|
|
701
|
+
parentId: currentId,
|
|
702
|
+
edgeKind: n.edge_kind,
|
|
703
|
+
expression: n.expression,
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
if (!visited.has(n.id)) {
|
|
709
|
+
visited.add(n.id);
|
|
710
|
+
parent.set(n.id, {
|
|
711
|
+
parentId: currentId,
|
|
712
|
+
edgeKind: n.edge_kind,
|
|
713
|
+
expression: n.expression,
|
|
714
|
+
});
|
|
715
|
+
nextQueue.push(n.id);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (found) break;
|
|
720
|
+
queue = nextQueue;
|
|
721
|
+
if (queue.length === 0) break;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return found ? parent : null;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/** Reconstruct a path from BFS parent map. */
|
|
728
|
+
function reconstructDataflowPath(
|
|
729
|
+
db: BetterSqlite3Database,
|
|
730
|
+
parent: Map<number, BfsParentEntry>,
|
|
731
|
+
sourceId: number,
|
|
732
|
+
targetId: number,
|
|
733
|
+
): Array<Record<string, unknown>> {
|
|
734
|
+
const nodeById = db.prepare('SELECT * FROM nodes WHERE id = ?');
|
|
735
|
+
const hc = new Map<string, string | null>();
|
|
736
|
+
const pathItems: Array<Record<string, unknown>> = [];
|
|
737
|
+
let cur: number | undefined = targetId;
|
|
738
|
+
while (cur !== undefined) {
|
|
739
|
+
const nodeRow = nodeById.get(cur) as NodeRow;
|
|
740
|
+
const parentInfo = parent.get(cur);
|
|
741
|
+
pathItems.unshift({
|
|
742
|
+
...normalizeSymbol(nodeRow, db, hc),
|
|
743
|
+
edgeKind: parentInfo?.edgeKind ?? null,
|
|
744
|
+
expression: parentInfo?.expression ?? null,
|
|
745
|
+
});
|
|
746
|
+
cur = parentInfo?.parentId;
|
|
747
|
+
if (cur === sourceId) {
|
|
748
|
+
const srcRow = nodeById.get(cur) as NodeRow;
|
|
749
|
+
pathItems.unshift({
|
|
750
|
+
...normalizeSymbol(srcRow, db, hc),
|
|
751
|
+
edgeKind: null,
|
|
752
|
+
expression: null,
|
|
753
|
+
});
|
|
754
|
+
break;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return pathItems;
|
|
758
|
+
}
|
|
759
|
+
|
|
448
760
|
export function dataflowPathData(
|
|
449
761
|
from: string,
|
|
450
762
|
to: string,
|
|
@@ -500,103 +812,54 @@ export function dataflowPathData(
|
|
|
500
812
|
if (sourceNode.id === targetNode.id) {
|
|
501
813
|
const hc = new Map<string, string | null>();
|
|
502
814
|
const sym = normalizeSymbol(sourceNode, db, hc);
|
|
503
|
-
return {
|
|
504
|
-
from,
|
|
505
|
-
to,
|
|
506
|
-
found: true,
|
|
507
|
-
hops: 0,
|
|
508
|
-
path: [{ ...sym, edgeKind: null }],
|
|
509
|
-
};
|
|
815
|
+
return { from, to, found: true, hops: 0, path: [{ ...sym, edgeKind: null }] };
|
|
510
816
|
}
|
|
511
817
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
`SELECT n.id, n.name, n.kind, n.file, n.line, d.kind AS edge_kind, d.expression
|
|
515
|
-
FROM dataflow d JOIN nodes n ON d.target_id = n.id
|
|
516
|
-
WHERE d.source_id = ? AND d.kind IN ('flows_to', 'returns')`,
|
|
517
|
-
);
|
|
518
|
-
|
|
519
|
-
const visited = new Set<number>([sourceNode.id]);
|
|
520
|
-
const parent = new Map<number, { parentId: number; edgeKind: string; expression: string }>();
|
|
521
|
-
let queue = [sourceNode.id];
|
|
522
|
-
let found = false;
|
|
523
|
-
|
|
524
|
-
for (let depth = 1; depth <= maxDepth; depth++) {
|
|
525
|
-
const nextQueue: number[] = [];
|
|
526
|
-
for (const currentId of queue) {
|
|
527
|
-
const neighbors = neighborStmt.all(currentId) as Array<{
|
|
528
|
-
id: number;
|
|
529
|
-
name: string;
|
|
530
|
-
kind: string;
|
|
531
|
-
file: string;
|
|
532
|
-
line: number;
|
|
533
|
-
edge_kind: string;
|
|
534
|
-
expression: string;
|
|
535
|
-
}>;
|
|
536
|
-
for (const n of neighbors) {
|
|
537
|
-
if (noTests && isTestFile(n.file)) continue;
|
|
538
|
-
if (n.id === targetNode.id) {
|
|
539
|
-
if (!found) {
|
|
540
|
-
found = true;
|
|
541
|
-
parent.set(n.id, {
|
|
542
|
-
parentId: currentId,
|
|
543
|
-
edgeKind: n.edge_kind,
|
|
544
|
-
expression: n.expression,
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
continue;
|
|
548
|
-
}
|
|
549
|
-
if (!visited.has(n.id)) {
|
|
550
|
-
visited.add(n.id);
|
|
551
|
-
parent.set(n.id, {
|
|
552
|
-
parentId: currentId,
|
|
553
|
-
edgeKind: n.edge_kind,
|
|
554
|
-
expression: n.expression,
|
|
555
|
-
});
|
|
556
|
-
nextQueue.push(n.id);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
if (found) break;
|
|
561
|
-
queue = nextQueue;
|
|
562
|
-
if (queue.length === 0) break;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
if (!found) {
|
|
818
|
+
const parent = bfsDataflowPath(db, sourceNode.id, targetNode.id, maxDepth, noTests);
|
|
819
|
+
if (!parent) {
|
|
566
820
|
return { from, to, found: false };
|
|
567
821
|
}
|
|
568
822
|
|
|
569
|
-
|
|
570
|
-
const nodeById = db.prepare('SELECT * FROM nodes WHERE id = ?');
|
|
571
|
-
const hc = new Map<string, string | null>();
|
|
572
|
-
const pathItems: Array<Record<string, unknown>> = [];
|
|
573
|
-
let cur: number | undefined = targetNode.id;
|
|
574
|
-
while (cur !== undefined) {
|
|
575
|
-
const nodeRow = nodeById.get(cur) as NodeRow;
|
|
576
|
-
const parentInfo = parent.get(cur);
|
|
577
|
-
pathItems.unshift({
|
|
578
|
-
...normalizeSymbol(nodeRow, db, hc),
|
|
579
|
-
edgeKind: parentInfo?.edgeKind ?? null,
|
|
580
|
-
expression: parentInfo?.expression ?? null,
|
|
581
|
-
});
|
|
582
|
-
cur = parentInfo?.parentId;
|
|
583
|
-
if (cur === sourceNode.id) {
|
|
584
|
-
const srcRow = nodeById.get(cur) as NodeRow;
|
|
585
|
-
pathItems.unshift({
|
|
586
|
-
...normalizeSymbol(srcRow, db, hc),
|
|
587
|
-
edgeKind: null,
|
|
588
|
-
expression: null,
|
|
589
|
-
});
|
|
590
|
-
break;
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
|
|
823
|
+
const pathItems = reconstructDataflowPath(db, parent, sourceNode.id, targetNode.id);
|
|
594
824
|
return { from, to, found: true, hops: pathItems.length - 1, path: pathItems };
|
|
595
825
|
} finally {
|
|
596
826
|
db.close();
|
|
597
827
|
}
|
|
598
828
|
}
|
|
599
829
|
|
|
830
|
+
/** BFS forward through return-value consumers to build impact levels. */
|
|
831
|
+
function bfsReturnConsumers(
|
|
832
|
+
node: NodeRow,
|
|
833
|
+
consumersStmt: ReturnType<BetterSqlite3Database['prepare']>,
|
|
834
|
+
db: BetterSqlite3Database,
|
|
835
|
+
hc: Map<string, string | null>,
|
|
836
|
+
maxDepth: number,
|
|
837
|
+
noTests: boolean,
|
|
838
|
+
): { levels: Record<number, unknown[]>; totalAffected: number } {
|
|
839
|
+
const visited = new Set<number>([node.id]);
|
|
840
|
+
const levels: Record<number, unknown[]> = {};
|
|
841
|
+
let frontier = [node.id];
|
|
842
|
+
|
|
843
|
+
for (let d = 1; d <= maxDepth; d++) {
|
|
844
|
+
const nextFrontier: number[] = [];
|
|
845
|
+
for (const fid of frontier) {
|
|
846
|
+
const consumers = consumersStmt.all(fid) as NodeRow[];
|
|
847
|
+
for (const c of consumers) {
|
|
848
|
+
if (!visited.has(c.id) && (!noTests || !isTestFile(c.file))) {
|
|
849
|
+
visited.add(c.id);
|
|
850
|
+
nextFrontier.push(c.id);
|
|
851
|
+
if (!levels[d]) levels[d] = [];
|
|
852
|
+
levels[d]!.push(normalizeSymbol(c, db, hc));
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
frontier = nextFrontier;
|
|
857
|
+
if (frontier.length === 0) break;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
return { levels, totalAffected: visited.size - 1 };
|
|
861
|
+
}
|
|
862
|
+
|
|
600
863
|
export function dataflowImpactData(
|
|
601
864
|
name: string,
|
|
602
865
|
customDbPath?: string,
|
|
@@ -633,7 +896,6 @@ export function dataflowImpactData(
|
|
|
633
896
|
return { name, results: [] };
|
|
634
897
|
}
|
|
635
898
|
|
|
636
|
-
// Forward BFS: who consumes this function's return value (directly or transitively)?
|
|
637
899
|
const consumersStmt = db.prepare(
|
|
638
900
|
`SELECT DISTINCT n.*
|
|
639
901
|
FROM dataflow d JOIN nodes n ON d.target_id = n.id
|
|
@@ -643,32 +905,15 @@ export function dataflowImpactData(
|
|
|
643
905
|
const hc = new Map<string, string | null>();
|
|
644
906
|
const results = nodes.map((node: NodeRow) => {
|
|
645
907
|
const sym = normalizeSymbol(node, db, hc);
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
if (!visited.has(c.id) && (!noTests || !isTestFile(c.file))) {
|
|
656
|
-
visited.add(c.id);
|
|
657
|
-
nextFrontier.push(c.id);
|
|
658
|
-
if (!levels[d]) levels[d] = [];
|
|
659
|
-
levels[d]!.push(normalizeSymbol(c, db, hc));
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
frontier = nextFrontier;
|
|
664
|
-
if (frontier.length === 0) break;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
return {
|
|
668
|
-
...sym,
|
|
669
|
-
levels,
|
|
670
|
-
totalAffected: visited.size - 1,
|
|
671
|
-
};
|
|
908
|
+
const { levels, totalAffected } = bfsReturnConsumers(
|
|
909
|
+
node,
|
|
910
|
+
consumersStmt,
|
|
911
|
+
db,
|
|
912
|
+
hc,
|
|
913
|
+
maxDepth,
|
|
914
|
+
noTests,
|
|
915
|
+
);
|
|
916
|
+
return { ...sym, levels, totalAffected };
|
|
672
917
|
});
|
|
673
918
|
|
|
674
919
|
const base = { name, results };
|