@optave/codegraph 3.4.1 → 3.6.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 +50 -28
- 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/rules/javascript.d.ts.map +1 -1
- package/dist/ast-analysis/rules/javascript.js +1 -0
- package/dist/ast-analysis/rules/javascript.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 +116 -35
- 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/better-sqlite3.d.ts +3 -0
- package/dist/db/better-sqlite3.d.ts.map +1 -0
- package/dist/db/better-sqlite3.js +19 -0
- package/dist/db/better-sqlite3.js.map +1 -0
- package/dist/db/connection.d.ts +25 -4
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +125 -23
- package/dist/db/connection.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- 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 +40 -32
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/query-builder.d.ts +5 -5
- package/dist/db/query-builder.d.ts.map +1 -1
- package/dist/db/query-builder.js +20 -4
- package/dist/db/query-builder.js.map +1 -1
- package/dist/db/repository/index.d.ts +1 -0
- package/dist/db/repository/index.d.ts.map +1 -1
- package/dist/db/repository/index.js +1 -0
- package/dist/db/repository/index.js.map +1 -1
- package/dist/db/repository/native-repository.d.ts +58 -0
- package/dist/db/repository/native-repository.d.ts.map +1 -0
- package/dist/db/repository/native-repository.js +261 -0
- package/dist/db/repository/native-repository.js.map +1 -0
- package/dist/db/repository/nodes.d.ts +4 -4
- package/dist/db/repository/nodes.d.ts.map +1 -1
- package/dist/db/repository/nodes.js +6 -6
- package/dist/db/repository/nodes.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/context.d.ts +2 -1
- package/dist/domain/graph/builder/context.d.ts.map +1 -1
- package/dist/domain/graph/builder/context.js +1 -0
- package/dist/domain/graph/builder/context.js.map +1 -1
- 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 +95 -7
- 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 +101 -57
- 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 +33 -3
- package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.js +70 -6
- 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 +36 -14
- 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 +130 -88
- 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 +124 -16
- 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 +165 -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/go.d.ts.map +1 -1
- package/dist/extractors/go.js +126 -130
- package/dist/extractors/go.js.map +1 -1
- 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 +6 -0
- package/dist/extractors/index.d.ts.map +1 -1
- package/dist/extractors/index.js +6 -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 +359 -330
- 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/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 -82
- 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/features/ast.d.ts +16 -1
- package/dist/features/ast.d.ts.map +1 -1
- package/dist/features/ast.js +45 -23
- 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 +50 -4
- 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 +118 -62
- 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/snapshot.d.ts.map +1 -1
- package/dist/features/snapshot.js +2 -1
- package/dist/features/snapshot.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 +47 -45
- 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 +406 -3
- 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-kotlin.wasm +0 -0
- package/grammars/tree-sitter-scala.wasm +0 -0
- package/grammars/tree-sitter-swift.wasm +0 -0
- package/package.json +67 -11
- package/src/ast-analysis/engine.ts +147 -138
- package/src/ast-analysis/rules/javascript.ts +1 -0
- package/src/ast-analysis/visitors/ast-store-visitor.ts +116 -34
- package/src/ast-analysis/visitors/complexity-visitor.ts +11 -11
- package/src/db/better-sqlite3.ts +20 -0
- package/src/db/connection.ts +148 -26
- package/src/db/index.ts +4 -1
- package/src/db/migrations.ts +38 -32
- package/src/db/query-builder.ts +30 -5
- package/src/db/repository/index.ts +1 -0
- package/src/db/repository/native-repository.ts +361 -0
- package/src/db/repository/nodes.ts +7 -3
- 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/context.ts +2 -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 +98 -6
- package/src/domain/graph/builder/stages/build-edges.ts +116 -83
- package/src/domain/graph/builder/stages/build-structure.ts +46 -8
- package/src/domain/graph/builder/stages/collect-files.ts +83 -6
- package/src/domain/graph/builder/stages/detect-changes.ts +44 -21
- package/src/domain/graph/builder/stages/finalize.ts +172 -109
- package/src/domain/graph/builder/stages/insert-nodes.ts +147 -17
- 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 +169 -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/go.ts +152 -134
- package/src/extractors/hcl.ts +6 -6
- package/src/extractors/helpers.ts +93 -1
- package/src/extractors/index.ts +6 -0
- package/src/extractors/java.ts +43 -48
- package/src/extractors/javascript.ts +382 -317
- package/src/extractors/kotlin.ts +293 -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 -84
- package/src/extractors/scala.ts +285 -0
- package/src/extractors/swift.ts +293 -0
- package/src/features/ast.ts +74 -24
- package/src/features/audit.ts +24 -20
- package/src/features/branch-compare.ts +54 -5
- package/src/features/cfg.ts +158 -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/snapshot.ts +2 -1
- 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 +48 -47
- 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 +458 -3
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
findImportSources,
|
|
6
6
|
findImportTargets,
|
|
7
7
|
findNodesByFile,
|
|
8
|
-
openReadonlyOrFail,
|
|
9
8
|
} from '../../db/index.js';
|
|
10
9
|
import { cachedStmt } from '../../db/repository/cached-stmt.js';
|
|
11
10
|
import { isTestFile } from '../../infrastructure/test-filter.js';
|
|
@@ -19,6 +18,7 @@ import type {
|
|
|
19
18
|
RelatedNodeRow,
|
|
20
19
|
StmtCache,
|
|
21
20
|
} from '../../types.js';
|
|
21
|
+
import { withReadonlyDb } from './query-helpers.js';
|
|
22
22
|
import { findMatchingNodes } from './symbol-lookup.js';
|
|
23
23
|
|
|
24
24
|
type UpstreamRow = { id: number; name: string; kind: string; file: string; line: number };
|
|
@@ -32,8 +32,7 @@ export function fileDepsData(
|
|
|
32
32
|
customDbPath: string,
|
|
33
33
|
opts: { noTests?: boolean; limit?: number; offset?: number } = {},
|
|
34
34
|
) {
|
|
35
|
-
|
|
36
|
-
try {
|
|
35
|
+
return withReadonlyDb(customDbPath, (db) => {
|
|
37
36
|
const noTests = opts.noTests || false;
|
|
38
37
|
const fileNodes = findFileNodes(db, `%${file}%`) as NodeRow[];
|
|
39
38
|
if (fileNodes.length === 0) {
|
|
@@ -59,9 +58,7 @@ export function fileDepsData(
|
|
|
59
58
|
|
|
60
59
|
const base = { file, results };
|
|
61
60
|
return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
|
|
62
|
-
}
|
|
63
|
-
db.close();
|
|
64
|
-
}
|
|
61
|
+
});
|
|
65
62
|
}
|
|
66
63
|
|
|
67
64
|
/**
|
|
@@ -140,8 +137,7 @@ export function fnDepsData(
|
|
|
140
137
|
offset?: number;
|
|
141
138
|
} = {},
|
|
142
139
|
) {
|
|
143
|
-
|
|
144
|
-
try {
|
|
140
|
+
return withReadonlyDb(customDbPath, (db) => {
|
|
145
141
|
const depth = opts.depth || 3;
|
|
146
142
|
const noTests = opts.noTests || false;
|
|
147
143
|
const hc = new Map();
|
|
@@ -194,9 +190,7 @@ export function fnDepsData(
|
|
|
194
190
|
|
|
195
191
|
const base = { name, results };
|
|
196
192
|
return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
|
|
197
|
-
}
|
|
198
|
-
db.close();
|
|
199
|
-
}
|
|
193
|
+
});
|
|
200
194
|
}
|
|
201
195
|
|
|
202
196
|
/**
|
|
@@ -384,8 +378,7 @@ export function pathData(
|
|
|
384
378
|
kind?: string;
|
|
385
379
|
} = {},
|
|
386
380
|
) {
|
|
387
|
-
|
|
388
|
-
try {
|
|
381
|
+
return withReadonlyDb(customDbPath, (db) => {
|
|
389
382
|
const noTests = opts.noTests || false;
|
|
390
383
|
const maxDepth = opts.maxDepth || 10;
|
|
391
384
|
const edgeKinds = opts.edgeKinds || ['calls'];
|
|
@@ -477,13 +470,67 @@ export function pathData(
|
|
|
477
470
|
reverse,
|
|
478
471
|
maxDepth,
|
|
479
472
|
};
|
|
480
|
-
}
|
|
481
|
-
db.close();
|
|
482
|
-
}
|
|
473
|
+
});
|
|
483
474
|
}
|
|
484
475
|
|
|
485
476
|
// ── File-level shortest path ────────────────────────────────────────────
|
|
486
477
|
|
|
478
|
+
/** BFS over file adjacency graph to find shortest path. */
|
|
479
|
+
function bfsFilePath(
|
|
480
|
+
neighborStmt: ReturnType<BetterSqlite3Database['prepare']>,
|
|
481
|
+
sourceFile: string,
|
|
482
|
+
targetFile: string,
|
|
483
|
+
edgeKinds: string[],
|
|
484
|
+
maxDepth: number,
|
|
485
|
+
noTests: boolean,
|
|
486
|
+
): { found: boolean; path: string[]; alternateCount: number } {
|
|
487
|
+
const visited = new Set([sourceFile]);
|
|
488
|
+
const parentMap = new Map<string, string>();
|
|
489
|
+
let queue = [sourceFile];
|
|
490
|
+
let found = false;
|
|
491
|
+
let alternateCount = 0;
|
|
492
|
+
|
|
493
|
+
for (let depth = 1; depth <= maxDepth; depth++) {
|
|
494
|
+
const nextQueue: string[] = [];
|
|
495
|
+
for (const currentFile of queue) {
|
|
496
|
+
const neighbors = neighborStmt.all(currentFile, ...edgeKinds) as Array<{
|
|
497
|
+
neighbor_file: string;
|
|
498
|
+
}>;
|
|
499
|
+
for (const n of neighbors) {
|
|
500
|
+
if (noTests && isTestFile(n.neighbor_file)) continue;
|
|
501
|
+
if (n.neighbor_file === targetFile) {
|
|
502
|
+
if (!found) {
|
|
503
|
+
found = true;
|
|
504
|
+
parentMap.set(n.neighbor_file, currentFile);
|
|
505
|
+
}
|
|
506
|
+
alternateCount++;
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
if (!visited.has(n.neighbor_file)) {
|
|
510
|
+
visited.add(n.neighbor_file);
|
|
511
|
+
parentMap.set(n.neighbor_file, currentFile);
|
|
512
|
+
nextQueue.push(n.neighbor_file);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (found) break;
|
|
517
|
+
queue = nextQueue;
|
|
518
|
+
if (queue.length === 0) break;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (!found) return { found: false, path: [], alternateCount: 0 };
|
|
522
|
+
|
|
523
|
+
// Reconstruct path
|
|
524
|
+
const filePath: string[] = [targetFile];
|
|
525
|
+
let cur = targetFile;
|
|
526
|
+
while (cur !== sourceFile) {
|
|
527
|
+
cur = parentMap.get(cur)!;
|
|
528
|
+
filePath.push(cur);
|
|
529
|
+
}
|
|
530
|
+
filePath.reverse();
|
|
531
|
+
return { found: true, path: filePath, alternateCount: Math.max(0, alternateCount - 1) };
|
|
532
|
+
}
|
|
533
|
+
|
|
487
534
|
/**
|
|
488
535
|
* BFS at the file level: find shortest import/edge path between two files.
|
|
489
536
|
* Adjacency: file A → file B if any symbol in A has an edge to any symbol in B.
|
|
@@ -499,8 +546,7 @@ export function filePathData(
|
|
|
499
546
|
reverse?: boolean;
|
|
500
547
|
} = {},
|
|
501
548
|
) {
|
|
502
|
-
|
|
503
|
-
try {
|
|
549
|
+
return withReadonlyDb(customDbPath, (db) => {
|
|
504
550
|
const noTests = opts.noTests || false;
|
|
505
551
|
const maxDepth = opts.maxDepth || 10;
|
|
506
552
|
const edgeKinds = opts.edgeKinds || ['imports', 'imports-type'];
|
|
@@ -569,42 +615,17 @@ export function filePathData(
|
|
|
569
615
|
WHERE n_src.file = ? AND e.kind IN (${kindPlaceholders}) AND n_tgt.file != n_src.file`;
|
|
570
616
|
const neighborStmt = db.prepare(neighborQuery);
|
|
571
617
|
|
|
572
|
-
// BFS
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
for (const currentFile of queue) {
|
|
582
|
-
const neighbors = neighborStmt.all(currentFile, ...edgeKinds) as Array<{
|
|
583
|
-
neighbor_file: string;
|
|
584
|
-
}>;
|
|
585
|
-
for (const n of neighbors) {
|
|
586
|
-
if (noTests && isTestFile(n.neighbor_file)) continue;
|
|
587
|
-
if (n.neighbor_file === targetFile) {
|
|
588
|
-
if (!found) {
|
|
589
|
-
found = true;
|
|
590
|
-
parentMap.set(n.neighbor_file, currentFile);
|
|
591
|
-
}
|
|
592
|
-
alternateCount++;
|
|
593
|
-
continue;
|
|
594
|
-
}
|
|
595
|
-
if (!visited.has(n.neighbor_file)) {
|
|
596
|
-
visited.add(n.neighbor_file);
|
|
597
|
-
parentMap.set(n.neighbor_file, currentFile);
|
|
598
|
-
nextQueue.push(n.neighbor_file);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
if (found) break;
|
|
603
|
-
queue = nextQueue;
|
|
604
|
-
if (queue.length === 0) break;
|
|
605
|
-
}
|
|
618
|
+
// BFS to find shortest file path
|
|
619
|
+
const bfsResult = bfsFilePath(
|
|
620
|
+
neighborStmt,
|
|
621
|
+
sourceFile,
|
|
622
|
+
targetFile,
|
|
623
|
+
edgeKinds,
|
|
624
|
+
maxDepth,
|
|
625
|
+
noTests,
|
|
626
|
+
);
|
|
606
627
|
|
|
607
|
-
if (!found) {
|
|
628
|
+
if (!bfsResult.found) {
|
|
608
629
|
return {
|
|
609
630
|
from,
|
|
610
631
|
to,
|
|
@@ -620,29 +641,18 @@ export function filePathData(
|
|
|
620
641
|
};
|
|
621
642
|
}
|
|
622
643
|
|
|
623
|
-
// Reconstruct path
|
|
624
|
-
const filePath: string[] = [targetFile];
|
|
625
|
-
let cur = targetFile;
|
|
626
|
-
while (cur !== sourceFile) {
|
|
627
|
-
cur = parentMap.get(cur)!;
|
|
628
|
-
filePath.push(cur);
|
|
629
|
-
}
|
|
630
|
-
filePath.reverse();
|
|
631
|
-
|
|
632
644
|
return {
|
|
633
645
|
from,
|
|
634
646
|
to,
|
|
635
647
|
fromCandidates,
|
|
636
648
|
toCandidates,
|
|
637
649
|
found: true,
|
|
638
|
-
hops:
|
|
639
|
-
path:
|
|
640
|
-
alternateCount:
|
|
650
|
+
hops: bfsResult.path.length - 1,
|
|
651
|
+
path: bfsResult.path,
|
|
652
|
+
alternateCount: bfsResult.alternateCount,
|
|
641
653
|
edgeKinds,
|
|
642
654
|
reverse,
|
|
643
655
|
maxDepth,
|
|
644
656
|
};
|
|
645
|
-
}
|
|
646
|
-
db.close();
|
|
647
|
-
}
|
|
657
|
+
});
|
|
648
658
|
}
|
|
@@ -4,10 +4,8 @@ import {
|
|
|
4
4
|
findDbPath,
|
|
5
5
|
findFileNodes,
|
|
6
6
|
findNodesByFile,
|
|
7
|
-
openReadonlyOrFail,
|
|
8
7
|
} from '../../db/index.js';
|
|
9
8
|
import { cachedStmt } from '../../db/repository/cached-stmt.js';
|
|
10
|
-
import { loadConfig } from '../../infrastructure/config.js';
|
|
11
9
|
import { debug } from '../../infrastructure/logger.js';
|
|
12
10
|
import { isTestFile } from '../../infrastructure/test-filter.js';
|
|
13
11
|
import {
|
|
@@ -17,6 +15,7 @@ import {
|
|
|
17
15
|
} from '../../shared/file-utils.js';
|
|
18
16
|
import { paginateResult } from '../../shared/paginate.js';
|
|
19
17
|
import type { BetterSqlite3Database, NodeRow, StmtCache } from '../../types.js';
|
|
18
|
+
import { resolveAnalysisOpts, withReadonlyDb } from './query-helpers.js';
|
|
20
19
|
|
|
21
20
|
/** Cache the schema probe for the `exported` column per db handle. */
|
|
22
21
|
const _hasExportedColCache: WeakMap<BetterSqlite3Database, boolean> = new WeakMap();
|
|
@@ -37,12 +36,8 @@ export function exportsData(
|
|
|
37
36
|
config?: any;
|
|
38
37
|
} = {},
|
|
39
38
|
) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const noTests = opts.noTests || false;
|
|
43
|
-
|
|
44
|
-
const config = opts.config || loadConfig();
|
|
45
|
-
const displayOpts = config.display || {};
|
|
39
|
+
return withReadonlyDb(customDbPath, (db) => {
|
|
40
|
+
const { noTests, displayOpts } = resolveAnalysisOpts(opts);
|
|
46
41
|
|
|
47
42
|
const dbFilePath = findDbPath(customDbPath);
|
|
48
43
|
const repoRoot = path.resolve(path.dirname(dbFilePath), '..');
|
|
@@ -101,9 +96,39 @@ export function exportsData(
|
|
|
101
96
|
}
|
|
102
97
|
}
|
|
103
98
|
return paginated;
|
|
104
|
-
}
|
|
105
|
-
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Collect symbols re-exported through barrel files. */
|
|
103
|
+
function collectReexportedSymbols(
|
|
104
|
+
db: BetterSqlite3Database,
|
|
105
|
+
fileNodeId: number,
|
|
106
|
+
reexportsToStmt: ReturnType<BetterSqlite3Database['prepare']>,
|
|
107
|
+
exportedNodesStmt: ReturnType<BetterSqlite3Database['prepare']> | null,
|
|
108
|
+
hasExportedCol: boolean,
|
|
109
|
+
getFileLines: (file: string) => string[] | null,
|
|
110
|
+
buildSymbolResult: (s: NodeRow, fileLines: string[] | null) => any,
|
|
111
|
+
) {
|
|
112
|
+
const reexportTargets = reexportsToStmt.all(fileNodeId) as Array<{ file: string }>;
|
|
113
|
+
const reexportedSymbols: Array<ReturnType<typeof buildSymbolResult> & { originFile: string }> =
|
|
114
|
+
[];
|
|
115
|
+
for (const reexTarget of reexportTargets) {
|
|
116
|
+
let targetExported: NodeRow[];
|
|
117
|
+
if (hasExportedCol) {
|
|
118
|
+
targetExported = exportedNodesStmt!.all(reexTarget.file) as NodeRow[];
|
|
119
|
+
} else {
|
|
120
|
+
const targetSymbols = findNodesByFile(db, reexTarget.file) as NodeRow[];
|
|
121
|
+
const exportedIds = findCrossFileCallTargets(db, reexTarget.file) as Set<number>;
|
|
122
|
+
targetExported = targetSymbols.filter((s) => exportedIds.has(s.id));
|
|
123
|
+
}
|
|
124
|
+
for (const s of targetExported) {
|
|
125
|
+
reexportedSymbols.push({
|
|
126
|
+
...buildSymbolResult(s, getFileLines(reexTarget.file)),
|
|
127
|
+
originFile: reexTarget.file,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
106
130
|
}
|
|
131
|
+
return reexportedSymbols;
|
|
107
132
|
}
|
|
108
133
|
|
|
109
134
|
function exportsFileImpl(
|
|
@@ -197,34 +222,20 @@ function exportsFileImpl(
|
|
|
197
222
|
|
|
198
223
|
const totalUnused = results.filter((r) => r.consumerCount === 0).length;
|
|
199
224
|
|
|
200
|
-
// Files that re-export this file (barrel -> this file)
|
|
201
225
|
const reexports = (reexportsFromStmt.all(fn.id) as Array<{ file: string }>).map((r) => ({
|
|
202
226
|
file: r.file,
|
|
203
227
|
}));
|
|
204
228
|
|
|
205
|
-
//
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// Fallback: same heuristic as direct exports — symbols called from other files
|
|
216
|
-
const targetSymbols = findNodesByFile(db, reexTarget.file) as NodeRow[];
|
|
217
|
-
const exportedIds = findCrossFileCallTargets(db, reexTarget.file) as Set<number>;
|
|
218
|
-
targetExported = targetSymbols.filter((s) => exportedIds.has(s.id));
|
|
219
|
-
}
|
|
220
|
-
for (const s of targetExported) {
|
|
221
|
-
const fileLines = getFileLines(reexTarget.file);
|
|
222
|
-
reexportedSymbols.push({
|
|
223
|
-
...buildSymbolResult(s, fileLines),
|
|
224
|
-
originFile: reexTarget.file,
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
|
-
}
|
|
229
|
+
// Gather symbols re-exported from target modules (barrel file support)
|
|
230
|
+
const reexportedSymbols = collectReexportedSymbols(
|
|
231
|
+
db,
|
|
232
|
+
fn.id,
|
|
233
|
+
reexportsToStmt,
|
|
234
|
+
exportedNodesStmt,
|
|
235
|
+
hasExportedCol,
|
|
236
|
+
getFileLines,
|
|
237
|
+
buildSymbolResult,
|
|
238
|
+
);
|
|
228
239
|
|
|
229
240
|
let filteredResults = results;
|
|
230
241
|
let filteredReexported = reexportedSymbols;
|
|
@@ -4,13 +4,12 @@ import {
|
|
|
4
4
|
findImplementors,
|
|
5
5
|
findImportDependents,
|
|
6
6
|
findNodeById,
|
|
7
|
-
openReadonlyOrFail,
|
|
8
7
|
} from '../../db/index.js';
|
|
9
|
-
import { loadConfig } from '../../infrastructure/config.js';
|
|
10
8
|
import { isTestFile } from '../../infrastructure/test-filter.js';
|
|
11
9
|
import { normalizeSymbol } from '../../shared/normalize.js';
|
|
12
10
|
import { paginateResult } from '../../shared/paginate.js';
|
|
13
11
|
import type { BetterSqlite3Database, NodeRow, RelatedNodeRow } from '../../types.js';
|
|
12
|
+
import { resolveAnalysisOpts, withReadonlyDb } from './query-helpers.js';
|
|
14
13
|
import { findMatchingNodes } from './symbol-lookup.js';
|
|
15
14
|
|
|
16
15
|
// --- Shared BFS: transitive callers ---
|
|
@@ -36,6 +35,62 @@ function hasImplementsEdges(db: BetterSqlite3Database): boolean {
|
|
|
36
35
|
* during traversal), its concrete implementors are also added to the frontier
|
|
37
36
|
* so that changes to an interface signature propagate to all implementors.
|
|
38
37
|
*/
|
|
38
|
+
type BfsLevel = Array<{
|
|
39
|
+
name: string;
|
|
40
|
+
kind: string;
|
|
41
|
+
file: string;
|
|
42
|
+
line: number;
|
|
43
|
+
viaImplements?: boolean;
|
|
44
|
+
}>;
|
|
45
|
+
type BfsLevels = Record<number, BfsLevel>;
|
|
46
|
+
type BfsOnVisit = (
|
|
47
|
+
caller: RelatedNodeRow & { viaImplements?: boolean },
|
|
48
|
+
parentId: number,
|
|
49
|
+
depth: number,
|
|
50
|
+
) => void;
|
|
51
|
+
|
|
52
|
+
/** Record an implementor node at the given depth, adding to frontier and levels. */
|
|
53
|
+
function recordImplementor(
|
|
54
|
+
impl: RelatedNodeRow,
|
|
55
|
+
parentId: number,
|
|
56
|
+
depth: number,
|
|
57
|
+
visited: Set<number>,
|
|
58
|
+
frontier: number[],
|
|
59
|
+
levels: BfsLevels,
|
|
60
|
+
noTests: boolean,
|
|
61
|
+
onVisit?: BfsOnVisit,
|
|
62
|
+
): void {
|
|
63
|
+
if (visited.has(impl.id) || (noTests && isTestFile(impl.file))) return;
|
|
64
|
+
visited.add(impl.id);
|
|
65
|
+
frontier.push(impl.id);
|
|
66
|
+
if (!levels[depth]) levels[depth] = [];
|
|
67
|
+
levels[depth].push({
|
|
68
|
+
name: impl.name,
|
|
69
|
+
kind: impl.kind,
|
|
70
|
+
file: impl.file,
|
|
71
|
+
line: impl.line,
|
|
72
|
+
viaImplements: true,
|
|
73
|
+
});
|
|
74
|
+
if (onVisit) onVisit({ ...impl, viaImplements: true }, parentId, depth);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Expand implementors for an interface/trait node into the BFS frontier. */
|
|
78
|
+
function expandImplementors(
|
|
79
|
+
db: BetterSqlite3Database,
|
|
80
|
+
nodeId: number,
|
|
81
|
+
depth: number,
|
|
82
|
+
visited: Set<number>,
|
|
83
|
+
frontier: number[],
|
|
84
|
+
levels: BfsLevels,
|
|
85
|
+
noTests: boolean,
|
|
86
|
+
onVisit?: BfsOnVisit,
|
|
87
|
+
): void {
|
|
88
|
+
const impls = findImplementors(db, nodeId) as RelatedNodeRow[];
|
|
89
|
+
for (const impl of impls) {
|
|
90
|
+
recordImplementor(impl, nodeId, depth, visited, frontier, levels, noTests, onVisit);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
39
94
|
export function bfsTransitiveCallers(
|
|
40
95
|
db: BetterSqlite3Database,
|
|
41
96
|
startId: number,
|
|
@@ -48,50 +103,24 @@ export function bfsTransitiveCallers(
|
|
|
48
103
|
noTests?: boolean;
|
|
49
104
|
maxDepth?: number;
|
|
50
105
|
includeImplementors?: boolean;
|
|
51
|
-
onVisit?:
|
|
52
|
-
caller: RelatedNodeRow & { viaImplements?: boolean },
|
|
53
|
-
parentId: number,
|
|
54
|
-
depth: number,
|
|
55
|
-
) => void;
|
|
106
|
+
onVisit?: BfsOnVisit;
|
|
56
107
|
} = {},
|
|
57
108
|
) {
|
|
58
|
-
// Skip all implementor lookups when the graph has no implements edges
|
|
59
109
|
const resolveImplementors = includeImplementors && hasImplementsEdges(db);
|
|
60
|
-
|
|
61
110
|
const visited = new Set([startId]);
|
|
62
|
-
const levels:
|
|
63
|
-
number,
|
|
64
|
-
Array<{ name: string; kind: string; file: string; line: number; viaImplements?: boolean }>
|
|
65
|
-
> = {};
|
|
111
|
+
const levels: BfsLevels = {};
|
|
66
112
|
let frontier = [startId];
|
|
67
113
|
|
|
68
|
-
// Seed: if start node is an interface/trait, include its implementors at depth 1
|
|
69
|
-
// Implementors go into a separate list so their callers appear at depth 2, not depth 1.
|
|
114
|
+
// Seed: if start node is an interface/trait, include its implementors at depth 1
|
|
70
115
|
const implNextFrontier: number[] = [];
|
|
71
116
|
if (resolveImplementors) {
|
|
72
117
|
const startNode = findNodeById(db, startId) as NodeRow | undefined;
|
|
73
118
|
if (startNode && INTERFACE_LIKE_KINDS.has(startNode.kind)) {
|
|
74
|
-
|
|
75
|
-
for (const impl of impls) {
|
|
76
|
-
if (!visited.has(impl.id) && (!noTests || !isTestFile(impl.file))) {
|
|
77
|
-
visited.add(impl.id);
|
|
78
|
-
implNextFrontier.push(impl.id);
|
|
79
|
-
if (!levels[1]) levels[1] = [];
|
|
80
|
-
levels[1].push({
|
|
81
|
-
name: impl.name,
|
|
82
|
-
kind: impl.kind,
|
|
83
|
-
file: impl.file,
|
|
84
|
-
line: impl.line,
|
|
85
|
-
viaImplements: true,
|
|
86
|
-
});
|
|
87
|
-
if (onVisit) onVisit({ ...impl, viaImplements: true }, startId, 1);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
119
|
+
expandImplementors(db, startId, 1, visited, implNextFrontier, levels, noTests, onVisit);
|
|
90
120
|
}
|
|
91
121
|
}
|
|
92
122
|
|
|
93
123
|
for (let d = 1; d <= maxDepth; d++) {
|
|
94
|
-
// On the first wave, merge seeded implementors so their callers appear at d=2
|
|
95
124
|
if (d === 1 && implNextFrontier.length > 0) {
|
|
96
125
|
frontier = [...frontier, ...implNextFrontier];
|
|
97
126
|
}
|
|
@@ -106,27 +135,8 @@ export function bfsTransitiveCallers(
|
|
|
106
135
|
levels[d]!.push({ name: c.name, kind: c.kind, file: c.file, line: c.line });
|
|
107
136
|
if (onVisit) onVisit(c, fid, d);
|
|
108
137
|
}
|
|
109
|
-
|
|
110
|
-
// If a caller is an interface/trait, also pull in its implementors
|
|
111
|
-
// Implementors are one extra hop away, so record at d+1
|
|
112
138
|
if (resolveImplementors && INTERFACE_LIKE_KINDS.has(c.kind)) {
|
|
113
|
-
|
|
114
|
-
for (const impl of impls) {
|
|
115
|
-
if (!visited.has(impl.id) && (!noTests || !isTestFile(impl.file))) {
|
|
116
|
-
visited.add(impl.id);
|
|
117
|
-
nextFrontier.push(impl.id);
|
|
118
|
-
const implDepth = d + 1;
|
|
119
|
-
if (!levels[implDepth]) levels[implDepth] = [];
|
|
120
|
-
levels[implDepth].push({
|
|
121
|
-
name: impl.name,
|
|
122
|
-
kind: impl.kind,
|
|
123
|
-
file: impl.file,
|
|
124
|
-
line: impl.line,
|
|
125
|
-
viaImplements: true,
|
|
126
|
-
});
|
|
127
|
-
if (onVisit) onVisit({ ...impl, viaImplements: true }, c.id, implDepth);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
139
|
+
expandImplementors(db, c.id, d + 1, visited, nextFrontier, levels, noTests, onVisit);
|
|
130
140
|
}
|
|
131
141
|
}
|
|
132
142
|
}
|
|
@@ -142,8 +152,7 @@ export function impactAnalysisData(
|
|
|
142
152
|
customDbPath: string,
|
|
143
153
|
opts: { noTests?: boolean } = {},
|
|
144
154
|
) {
|
|
145
|
-
|
|
146
|
-
try {
|
|
155
|
+
return withReadonlyDb(customDbPath, (db) => {
|
|
147
156
|
const noTests = opts.noTests || false;
|
|
148
157
|
const fileNodes = findFileNodes(db, `%${file}%`) as NodeRow[];
|
|
149
158
|
if (fileNodes.length === 0) {
|
|
@@ -187,9 +196,7 @@ export function impactAnalysisData(
|
|
|
187
196
|
levels: byLevel,
|
|
188
197
|
totalDependents: visited.size - fileNodes.length,
|
|
189
198
|
};
|
|
190
|
-
}
|
|
191
|
-
db.close();
|
|
192
|
-
}
|
|
199
|
+
});
|
|
193
200
|
}
|
|
194
201
|
|
|
195
202
|
export function fnImpactData(
|
|
@@ -206,11 +213,9 @@ export function fnImpactData(
|
|
|
206
213
|
config?: any;
|
|
207
214
|
} = {},
|
|
208
215
|
) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const config = opts.config || loadConfig();
|
|
216
|
+
return withReadonlyDb(customDbPath, (db) => {
|
|
217
|
+
const { noTests, config } = resolveAnalysisOpts(opts);
|
|
212
218
|
const maxDepth = opts.depth || config.analysis?.fnImpactDepth || 5;
|
|
213
|
-
const noTests = opts.noTests || false;
|
|
214
219
|
const hc = new Map();
|
|
215
220
|
|
|
216
221
|
const nodes = findMatchingNodes(db, name, { noTests, file: opts.file, kind: opts.kind });
|
|
@@ -235,7 +240,5 @@ export function fnImpactData(
|
|
|
235
240
|
|
|
236
241
|
const base = { name, results };
|
|
237
242
|
return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
|
|
238
|
-
}
|
|
239
|
-
db.close();
|
|
240
|
-
}
|
|
243
|
+
});
|
|
241
244
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { openReadonlyOrFail, testFilterSQL } from '../../db/index.js';
|
|
2
|
+
import { openReadonlyOrFail, openReadonlyWithNative, testFilterSQL } from '../../db/index.js';
|
|
3
3
|
import { cachedStmt } from '../../db/repository/cached-stmt.js';
|
|
4
4
|
import { loadConfig } from '../../infrastructure/config.js';
|
|
5
5
|
import { debug } from '../../infrastructure/logger.js';
|
|
@@ -381,20 +381,115 @@ export function moduleMapData(customDbPath: string, limit = 20, opts: { noTests?
|
|
|
381
381
|
}
|
|
382
382
|
|
|
383
383
|
export function statsData(customDbPath: string, opts: { noTests?: boolean; config?: any } = {}) {
|
|
384
|
-
const db =
|
|
384
|
+
const { db, nativeDb, close } = openReadonlyWithNative(customDbPath);
|
|
385
385
|
try {
|
|
386
386
|
const noTests = opts.noTests || false;
|
|
387
387
|
const config = opts.config || loadConfig();
|
|
388
|
-
const testFilter = testFilterSQL('n.file', noTests);
|
|
389
388
|
|
|
389
|
+
// These always need JS (non-SQL logic)
|
|
390
|
+
const files = countFilesByLanguage(db, noTests);
|
|
391
|
+
const fileCycles = findCycles(db, { fileLevel: true, noTests });
|
|
392
|
+
const fnCycles = findCycles(db, { fileLevel: false, noTests });
|
|
393
|
+
|
|
394
|
+
// ── Native fast path: batch all SQL aggregations in one napi call ──
|
|
395
|
+
if (nativeDb?.getGraphStats) {
|
|
396
|
+
const s = nativeDb.getGraphStats(noTests);
|
|
397
|
+
const nodesByKind: Record<string, number> = {};
|
|
398
|
+
for (const k of s.nodesByKind) nodesByKind[k.kind] = k.count;
|
|
399
|
+
const edgesByKind: Record<string, number> = {};
|
|
400
|
+
for (const k of s.edgesByKind) edgesByKind[k.kind] = k.count;
|
|
401
|
+
const roles: Record<string, number> & { dead?: number } = {};
|
|
402
|
+
let deadTotal = 0;
|
|
403
|
+
for (const r of s.roleCounts) {
|
|
404
|
+
roles[r.role] = r.count;
|
|
405
|
+
if (r.role.startsWith(DEAD_ROLE_PREFIX)) deadTotal += r.count;
|
|
406
|
+
}
|
|
407
|
+
if (deadTotal > 0) roles.dead = deadTotal;
|
|
408
|
+
|
|
409
|
+
const callerCoverage =
|
|
410
|
+
s.quality.callableTotal > 0 ? s.quality.callableWithCallers / s.quality.callableTotal : 0;
|
|
411
|
+
const callConfidence =
|
|
412
|
+
s.quality.callEdges > 0 ? s.quality.highConfCallEdges / s.quality.callEdges : 0;
|
|
413
|
+
|
|
414
|
+
// False-positive analysis still uses JS (needs FALSE_POSITIVE_NAMES set)
|
|
415
|
+
const fpThreshold = config.analysis?.falsePositiveCallers ?? FALSE_POSITIVE_CALLER_THRESHOLD;
|
|
416
|
+
const fpRows = db
|
|
417
|
+
.prepare(`
|
|
418
|
+
SELECT n.name, n.file, n.line, COUNT(e.source_id) as caller_count
|
|
419
|
+
FROM nodes n
|
|
420
|
+
LEFT JOIN edges e ON n.id = e.target_id AND e.kind = 'calls'
|
|
421
|
+
WHERE n.kind IN ('function', 'method')
|
|
422
|
+
GROUP BY n.id
|
|
423
|
+
HAVING caller_count > ?
|
|
424
|
+
ORDER BY caller_count DESC
|
|
425
|
+
`)
|
|
426
|
+
.all(fpThreshold) as Array<{
|
|
427
|
+
name: string;
|
|
428
|
+
file: string;
|
|
429
|
+
line: number;
|
|
430
|
+
caller_count: number;
|
|
431
|
+
}>;
|
|
432
|
+
const falsePositiveWarnings = fpRows
|
|
433
|
+
.filter((r) =>
|
|
434
|
+
FALSE_POSITIVE_NAMES.has(r.name.includes('.') ? r.name.split('.').pop()! : r.name),
|
|
435
|
+
)
|
|
436
|
+
.map((r) => ({ name: r.name, file: r.file, line: r.line, callerCount: r.caller_count }));
|
|
437
|
+
let fpEdgeCount = 0;
|
|
438
|
+
for (const fp of falsePositiveWarnings) fpEdgeCount += fp.callerCount;
|
|
439
|
+
const falsePositiveRatio = s.quality.callEdges > 0 ? fpEdgeCount / s.quality.callEdges : 0;
|
|
440
|
+
const score = Math.round(
|
|
441
|
+
callerCoverage * 40 + callConfidence * 40 + (1 - falsePositiveRatio) * 20,
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
nodes: { total: s.totalNodes, byKind: nodesByKind },
|
|
446
|
+
edges: { total: s.totalEdges, byKind: edgesByKind },
|
|
447
|
+
files,
|
|
448
|
+
cycles: { fileLevel: fileCycles.length, functionLevel: fnCycles.length },
|
|
449
|
+
hotspots: s.hotspots.map((h) => ({ file: h.file, fanIn: h.fanIn, fanOut: h.fanOut })),
|
|
450
|
+
embeddings: s.embeddings
|
|
451
|
+
? {
|
|
452
|
+
count: s.embeddings.count,
|
|
453
|
+
model: s.embeddings.model,
|
|
454
|
+
dim: s.embeddings.dim,
|
|
455
|
+
builtAt: s.embeddings.builtAt,
|
|
456
|
+
}
|
|
457
|
+
: null,
|
|
458
|
+
quality: {
|
|
459
|
+
score,
|
|
460
|
+
callerCoverage: {
|
|
461
|
+
ratio: callerCoverage,
|
|
462
|
+
covered: s.quality.callableWithCallers,
|
|
463
|
+
total: s.quality.callableTotal,
|
|
464
|
+
},
|
|
465
|
+
callConfidence: {
|
|
466
|
+
ratio: callConfidence,
|
|
467
|
+
highConf: s.quality.highConfCallEdges,
|
|
468
|
+
total: s.quality.callEdges,
|
|
469
|
+
},
|
|
470
|
+
falsePositiveWarnings,
|
|
471
|
+
},
|
|
472
|
+
roles,
|
|
473
|
+
complexity: s.complexity
|
|
474
|
+
? {
|
|
475
|
+
analyzed: s.complexity.analyzed,
|
|
476
|
+
avgCognitive: s.complexity.avgCognitive,
|
|
477
|
+
avgCyclomatic: s.complexity.avgCyclomatic,
|
|
478
|
+
maxCognitive: s.complexity.maxCognitive,
|
|
479
|
+
maxCyclomatic: s.complexity.maxCyclomatic,
|
|
480
|
+
avgMI: s.complexity.avgMi,
|
|
481
|
+
minMI: s.complexity.minMi,
|
|
482
|
+
}
|
|
483
|
+
: null,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ── JS fallback ───────────────────────────────────────────────────
|
|
488
|
+
const testFilter = testFilterSQL('n.file', noTests);
|
|
390
489
|
const testFileIds = noTests ? buildTestFileIds(db) : null;
|
|
391
490
|
|
|
392
491
|
const { total: totalNodes, byKind: nodesByKind } = countNodesByKind(db, testFileIds);
|
|
393
492
|
const { total: totalEdges, byKind: edgesByKind } = countEdgesByKind(db, testFileIds);
|
|
394
|
-
const files = countFilesByLanguage(db, noTests);
|
|
395
|
-
|
|
396
|
-
const fileCycles = findCycles(db, { fileLevel: true, noTests });
|
|
397
|
-
const fnCycles = findCycles(db, { fileLevel: false, noTests });
|
|
398
493
|
|
|
399
494
|
const hotspots = findHotspots(db, noTests, 5);
|
|
400
495
|
const embeddings = getEmbeddingsInfo(db);
|
|
@@ -415,6 +510,6 @@ export function statsData(customDbPath: string, opts: { noTests?: boolean; confi
|
|
|
415
510
|
complexity,
|
|
416
511
|
};
|
|
417
512
|
} finally {
|
|
418
|
-
|
|
513
|
+
close();
|
|
419
514
|
}
|
|
420
515
|
}
|