@optave/codegraph 3.8.0 → 3.9.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 +13 -8
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +137 -86
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/metrics.d.ts +0 -3
- package/dist/ast-analysis/metrics.d.ts.map +1 -1
- package/dist/ast-analysis/metrics.js +30 -13
- package/dist/ast-analysis/metrics.js.map +1 -1
- package/dist/ast-analysis/shared.d.ts.map +1 -1
- package/dist/ast-analysis/shared.js +24 -19
- package/dist/ast-analysis/shared.js.map +1 -1
- package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
- package/dist/ast-analysis/visitor-utils.js +55 -39
- package/dist/ast-analysis/visitor-utils.js.map +1 -1
- package/dist/ast-analysis/visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitor.js +91 -70
- package/dist/ast-analysis/visitor.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 +54 -58
- 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 +81 -39
- package/dist/ast-analysis/visitors/complexity-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 +57 -38
- package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
- package/dist/cli/commands/branch-compare.d.ts.map +1 -1
- package/dist/cli/commands/branch-compare.js +4 -0
- package/dist/cli/commands/branch-compare.js.map +1 -1
- package/dist/cli/commands/diff-impact.d.ts.map +1 -1
- package/dist/cli/commands/diff-impact.js +2 -1
- package/dist/cli/commands/diff-impact.js.map +1 -1
- package/dist/cli/commands/info.d.ts.map +1 -1
- package/dist/cli/commands/info.js +3 -2
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/cli/commands/watch.d.ts.map +1 -1
- package/dist/cli/commands/watch.js +16 -2
- package/dist/cli/commands/watch.js.map +1 -1
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +29 -26
- package/dist/db/connection.js.map +1 -1
- package/dist/db/query-builder.d.ts.map +1 -1
- package/dist/db/query-builder.js +16 -5
- package/dist/db/query-builder.js.map +1 -1
- package/dist/db/repository/base.d.ts +16 -0
- package/dist/db/repository/base.d.ts.map +1 -1
- package/dist/db/repository/base.js +31 -0
- package/dist/db/repository/base.js.map +1 -1
- package/dist/db/repository/native-repository.d.ts +7 -1
- package/dist/db/repository/native-repository.d.ts.map +1 -1
- package/dist/db/repository/native-repository.js +100 -1
- package/dist/db/repository/native-repository.js.map +1 -1
- package/dist/db/repository/nodes.d.ts.map +1 -1
- package/dist/db/repository/nodes.js +8 -4
- package/dist/db/repository/nodes.js.map +1 -1
- package/dist/db/repository/sqlite-repository.d.ts +4 -0
- package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
- package/dist/db/repository/sqlite-repository.js +51 -0
- package/dist/db/repository/sqlite-repository.js.map +1 -1
- package/dist/domain/analysis/brief.d.ts.map +1 -1
- package/dist/domain/analysis/brief.js +13 -17
- package/dist/domain/analysis/brief.js.map +1 -1
- package/dist/domain/analysis/context.d.ts.map +1 -1
- package/dist/domain/analysis/context.js +14 -11
- 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 +64 -59
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts +2 -7
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +33 -31
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/implementations.d.ts.map +1 -1
- package/dist/domain/analysis/implementations.js +11 -19
- package/dist/domain/analysis/implementations.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +55 -76
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/analysis/query-helpers.d.ts +7 -0
- package/dist/domain/analysis/query-helpers.d.ts.map +1 -1
- package/dist/domain/analysis/query-helpers.js +15 -1
- package/dist/domain/analysis/query-helpers.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +352 -107
- 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 +49 -18
- package/dist/domain/graph/builder/stages/build-edges.js.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.js +2 -2
- 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 +32 -21
- 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 +95 -84
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/cycles.d.ts +6 -0
- package/dist/domain/graph/cycles.d.ts.map +1 -1
- package/dist/domain/graph/cycles.js +114 -22
- package/dist/domain/graph/cycles.js.map +1 -1
- package/dist/domain/graph/resolve.js +1 -1
- package/dist/domain/graph/resolve.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts +2 -0
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +170 -75
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +3 -4
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +141 -89
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/generator.js +1 -1
- package/dist/domain/search/generator.js.map +1 -1
- package/dist/domain/search/models.d.ts +4 -3
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +23 -8
- 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 +29 -18
- package/dist/domain/search/search/hybrid.js.map +1 -1
- package/dist/extractors/go.js +36 -33
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +40 -29
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.js +58 -46
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.js +65 -54
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/kotlin.js +84 -78
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/python.js +29 -24
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/rust.js +41 -32
- package/dist/extractors/rust.js.map +1 -1
- package/dist/extractors/solidity.js +58 -67
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/extractors/swift.js +83 -81
- package/dist/extractors/swift.js.map +1 -1
- package/dist/extractors/zig.js +58 -60
- package/dist/extractors/zig.js.map +1 -1
- package/dist/features/ast.d.ts +16 -14
- package/dist/features/ast.d.ts.map +1 -1
- package/dist/features/ast.js +83 -81
- package/dist/features/ast.js.map +1 -1
- package/dist/features/audit.d.ts.map +1 -1
- package/dist/features/audit.js +8 -6
- package/dist/features/audit.js.map +1 -1
- package/dist/features/branch-compare.d.ts.map +1 -1
- package/dist/features/branch-compare.js +69 -72
- package/dist/features/branch-compare.js.map +1 -1
- package/dist/features/communities.d.ts.map +1 -1
- package/dist/features/communities.js +19 -7
- package/dist/features/communities.js.map +1 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +120 -125
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +136 -137
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/flow.d.ts.map +1 -1
- package/dist/features/flow.js +84 -79
- package/dist/features/flow.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +69 -65
- package/dist/features/structure-query.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +70 -55
- 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 +288 -266
- package/dist/graph/algorithms/leiden/partition.js.map +1 -1
- package/dist/graph/model.d.ts.map +1 -1
- package/dist/graph/model.js +5 -1
- package/dist/graph/model.js.map +1 -1
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +6 -4
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/suppress.d.ts +25 -0
- package/dist/infrastructure/suppress.d.ts.map +1 -0
- package/dist/infrastructure/suppress.js +43 -0
- package/dist/infrastructure/suppress.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +29 -24
- package/dist/mcp/server.js.map +1 -1
- package/dist/presentation/dataflow.d.ts.map +1 -1
- package/dist/presentation/dataflow.js +47 -38
- package/dist/presentation/dataflow.js.map +1 -1
- package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -1
- package/dist/presentation/diff-impact-mermaid.js +60 -51
- package/dist/presentation/diff-impact-mermaid.js.map +1 -1
- package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
- package/dist/presentation/queries-cli/exports.js +20 -14
- 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 +15 -13
- package/dist/presentation/queries-cli/impact.js.map +1 -1
- package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
- package/dist/presentation/queries-cli/inspect.js +101 -79
- package/dist/presentation/queries-cli/inspect.js.map +1 -1
- package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
- package/dist/presentation/queries-cli/overview.js +25 -16
- package/dist/presentation/queries-cli/overview.js.map +1 -1
- package/dist/presentation/queries-cli/path.js +26 -20
- package/dist/presentation/queries-cli/path.js.map +1 -1
- package/dist/presentation/result-formatter.d.ts +10 -0
- package/dist/presentation/result-formatter.d.ts.map +1 -1
- package/dist/presentation/result-formatter.js +16 -1
- package/dist/presentation/result-formatter.js.map +1 -1
- package/dist/presentation/viewer.d.ts.map +1 -1
- package/dist/presentation/viewer.js +18 -12
- package/dist/presentation/viewer.js.map +1 -1
- package/dist/shared/errors.d.ts +5 -0
- package/dist/shared/errors.d.ts.map +1 -1
- package/dist/shared/errors.js +5 -0
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/hierarchy.d.ts +8 -2
- package/dist/shared/hierarchy.d.ts.map +1 -1
- package/dist/shared/hierarchy.js +42 -1
- package/dist/shared/hierarchy.js.map +1 -1
- package/dist/shared/normalize.d.ts +6 -1
- package/dist/shared/normalize.d.ts.map +1 -1
- package/dist/shared/normalize.js +20 -12
- package/dist/shared/normalize.js.map +1 -1
- package/dist/shared/paginate.d.ts +0 -9
- package/dist/shared/paginate.d.ts.map +1 -1
- package/dist/shared/paginate.js +0 -15
- package/dist/shared/paginate.js.map +1 -1
- package/dist/types.d.ts +12 -5
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/package.json +9 -9
- package/src/ast-analysis/engine.ts +176 -104
- package/src/ast-analysis/metrics.ts +33 -11
- package/src/ast-analysis/shared.ts +33 -24
- package/src/ast-analysis/visitor-utils.ts +52 -32
- package/src/ast-analysis/visitor.ts +132 -71
- package/src/ast-analysis/visitors/ast-store-visitor.ts +53 -50
- package/src/ast-analysis/visitors/complexity-visitor.ts +89 -40
- package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
- package/src/cli/commands/branch-compare.ts +4 -0
- package/src/cli/commands/diff-impact.ts +2 -1
- package/src/cli/commands/info.ts +3 -2
- package/src/cli/commands/watch.ts +16 -2
- package/src/db/connection.ts +29 -28
- package/src/db/query-builder.ts +15 -3
- package/src/db/repository/base.ts +34 -0
- package/src/db/repository/native-repository.ts +104 -1
- package/src/db/repository/nodes.ts +13 -8
- package/src/db/repository/sqlite-repository.ts +55 -0
- package/src/domain/analysis/brief.ts +15 -25
- package/src/domain/analysis/context.ts +17 -10
- package/src/domain/analysis/dependencies.ts +77 -81
- package/src/domain/analysis/fn-impact.ts +36 -43
- package/src/domain/analysis/implementations.ts +11 -17
- package/src/domain/analysis/module-map.ts +58 -92
- package/src/domain/analysis/query-helpers.ts +18 -1
- package/src/domain/graph/builder/pipeline.ts +409 -99
- package/src/domain/graph/builder/stages/build-edges.ts +45 -19
- package/src/domain/graph/builder/stages/detect-changes.ts +2 -2
- package/src/domain/graph/builder/stages/finalize.ts +2 -2
- package/src/domain/graph/builder/stages/insert-nodes.ts +59 -34
- package/src/domain/graph/builder/stages/resolve-imports.ts +122 -100
- package/src/domain/graph/cycles.ts +110 -23
- package/src/domain/graph/resolve.ts +1 -1
- package/src/domain/graph/watcher.ts +202 -96
- package/src/domain/parser.ts +143 -89
- package/src/domain/search/generator.ts +1 -1
- package/src/domain/search/models.ts +26 -7
- package/src/domain/search/search/hybrid.ts +69 -51
- package/src/extractors/go.ts +43 -33
- package/src/extractors/helpers.ts +37 -23
- package/src/extractors/java.ts +66 -47
- package/src/extractors/javascript.ts +66 -54
- package/src/extractors/kotlin.ts +84 -77
- package/src/extractors/python.ts +31 -25
- package/src/extractors/rust.ts +37 -29
- package/src/extractors/solidity.ts +57 -61
- package/src/extractors/swift.ts +81 -80
- package/src/extractors/zig.ts +58 -61
- package/src/features/ast.ts +130 -110
- package/src/features/audit.ts +8 -6
- package/src/features/branch-compare.ts +105 -79
- package/src/features/communities.ts +25 -10
- package/src/features/complexity.ts +171 -134
- package/src/features/dataflow.ts +165 -175
- package/src/features/flow.ts +129 -92
- package/src/features/structure-query.ts +79 -64
- package/src/graph/algorithms/leiden/optimiser.ts +99 -55
- package/src/graph/algorithms/leiden/partition.ts +359 -294
- package/src/graph/model.ts +6 -1
- package/src/infrastructure/config.ts +6 -4
- package/src/infrastructure/suppress.ts +47 -0
- package/src/mcp/server.ts +53 -37
- package/src/presentation/dataflow.ts +50 -44
- package/src/presentation/diff-impact-mermaid.ts +104 -62
- package/src/presentation/queries-cli/exports.ts +21 -13
- package/src/presentation/queries-cli/impact.ts +15 -13
- package/src/presentation/queries-cli/inspect.ts +100 -81
- package/src/presentation/queries-cli/overview.ts +26 -16
- package/src/presentation/queries-cli/path.ts +33 -25
- package/src/presentation/result-formatter.ts +19 -1
- package/src/presentation/viewer.ts +42 -14
- package/src/shared/errors.ts +6 -0
- package/src/shared/hierarchy.ts +50 -2
- package/src/shared/normalize.ts +31 -12
- package/src/shared/paginate.ts +0 -17
- package/src/types.ts +26 -5
|
@@ -154,6 +154,32 @@ export class SqliteRepository extends Repository {
|
|
|
154
154
|
return findCallers(this.#db, nodeId);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
findCallersBatch(nodeIds: number[]): Map<number, RelatedNodeRow[]> {
|
|
158
|
+
if (nodeIds.length === 0) return new Map();
|
|
159
|
+
const placeholders = nodeIds.map(() => '?').join(',');
|
|
160
|
+
const rows = this.#db
|
|
161
|
+
.prepare(
|
|
162
|
+
`SELECT e.target_id AS queried_id, n.id, n.name, n.kind, n.file, n.line, n.end_line
|
|
163
|
+
FROM edges e JOIN nodes n ON e.source_id = n.id
|
|
164
|
+
WHERE e.target_id IN (${placeholders}) AND e.kind = 'calls'`,
|
|
165
|
+
)
|
|
166
|
+
.all(...nodeIds) as Array<RelatedNodeRow & { queried_id: number }>;
|
|
167
|
+
const result = new Map<number, RelatedNodeRow[]>();
|
|
168
|
+
for (const row of rows) {
|
|
169
|
+
const qid = row.queried_id;
|
|
170
|
+
if (!result.has(qid)) result.set(qid, []);
|
|
171
|
+
result.get(qid)!.push({
|
|
172
|
+
id: row.id,
|
|
173
|
+
name: row.name,
|
|
174
|
+
kind: row.kind,
|
|
175
|
+
file: row.file,
|
|
176
|
+
line: row.line,
|
|
177
|
+
end_line: row.end_line,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
|
|
157
183
|
findDistinctCallers(nodeId: number): RelatedNodeRow[] {
|
|
158
184
|
return findDistinctCallers(this.#db, nodeId);
|
|
159
185
|
}
|
|
@@ -245,4 +271,33 @@ export class SqliteRepository extends Repository {
|
|
|
245
271
|
getComplexityForNode(nodeId: number): ComplexityMetrics | undefined {
|
|
246
272
|
return getComplexityForNode(this.#db, nodeId);
|
|
247
273
|
}
|
|
274
|
+
|
|
275
|
+
// ── Convenience queries ────────────────────────────────────────────
|
|
276
|
+
|
|
277
|
+
getFileHash(file: string): string | null {
|
|
278
|
+
const row = this.#db.prepare('SELECT hash FROM file_hashes WHERE file = ?').get(file) as
|
|
279
|
+
| { hash: string }
|
|
280
|
+
| undefined;
|
|
281
|
+
return row?.hash ?? null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
#implementsEdgesCache?: boolean;
|
|
285
|
+
hasImplementsEdges(): boolean {
|
|
286
|
+
if (this.#implementsEdgesCache !== undefined) return this.#implementsEdgesCache;
|
|
287
|
+
this.#implementsEdgesCache = !!this.#db
|
|
288
|
+
.prepare("SELECT 1 FROM edges WHERE kind = 'implements' LIMIT 1")
|
|
289
|
+
.get();
|
|
290
|
+
return this.#implementsEdgesCache;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
#coChangesTableCache?: boolean;
|
|
294
|
+
hasCoChangesTable(): boolean {
|
|
295
|
+
if (this.#coChangesTableCache !== undefined) return this.#coChangesTableCache;
|
|
296
|
+
try {
|
|
297
|
+
this.#coChangesTableCache = !!this.#db.prepare('SELECT 1 FROM co_changes LIMIT 1').get();
|
|
298
|
+
} catch {
|
|
299
|
+
this.#coChangesTableCache = false;
|
|
300
|
+
}
|
|
301
|
+
return this.#coChangesTableCache;
|
|
302
|
+
}
|
|
248
303
|
}
|
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
findDistinctCallers,
|
|
3
|
-
findFileNodes,
|
|
4
|
-
findImportDependents,
|
|
5
|
-
findImportSources,
|
|
6
|
-
findImportTargets,
|
|
7
|
-
findNodesByFile,
|
|
8
|
-
openReadonlyOrFail,
|
|
9
|
-
} from '../../db/index.js';
|
|
1
|
+
import type { Repository } from '../../db/index.js';
|
|
10
2
|
import { loadConfig } from '../../infrastructure/config.js';
|
|
11
3
|
import { isTestFile } from '../../infrastructure/test-filter.js';
|
|
12
|
-
import type {
|
|
4
|
+
import type { ImportEdgeRow, NodeRow, RelatedNodeRow } from '../../types.js';
|
|
5
|
+
import { withRepo } from './query-helpers.js';
|
|
13
6
|
|
|
14
7
|
/** Symbol kinds meaningful for a file brief — excludes parameters, properties, constants. */
|
|
15
8
|
const BRIEF_KINDS = new Set([
|
|
@@ -49,7 +42,7 @@ function computeRiskTier(
|
|
|
49
42
|
* Lightweight variant — only counts, does not collect details.
|
|
50
43
|
*/
|
|
51
44
|
function countTransitiveCallers(
|
|
52
|
-
|
|
45
|
+
repo: InstanceType<typeof Repository>,
|
|
53
46
|
startId: number,
|
|
54
47
|
noTests: boolean,
|
|
55
48
|
maxDepth = 5,
|
|
@@ -60,7 +53,7 @@ function countTransitiveCallers(
|
|
|
60
53
|
for (let d = 1; d <= maxDepth; d++) {
|
|
61
54
|
const nextFrontier: number[] = [];
|
|
62
55
|
for (const fid of frontier) {
|
|
63
|
-
const callers = findDistinctCallers(
|
|
56
|
+
const callers = repo.findDistinctCallers(fid) as RelatedNodeRow[];
|
|
64
57
|
for (const c of callers) {
|
|
65
58
|
if (!visited.has(c.id) && (!noTests || !isTestFile(c.file))) {
|
|
66
59
|
visited.add(c.id);
|
|
@@ -80,7 +73,7 @@ function countTransitiveCallers(
|
|
|
80
73
|
* Depth-bounded to match countTransitiveCallers and keep hook latency predictable.
|
|
81
74
|
*/
|
|
82
75
|
function countTransitiveImporters(
|
|
83
|
-
|
|
76
|
+
repo: InstanceType<typeof Repository>,
|
|
84
77
|
fileNodeIds: number[],
|
|
85
78
|
noTests: boolean,
|
|
86
79
|
maxDepth = 5,
|
|
@@ -91,7 +84,7 @@ function countTransitiveImporters(
|
|
|
91
84
|
for (let d = 1; d <= maxDepth; d++) {
|
|
92
85
|
const nextFrontier: number[] = [];
|
|
93
86
|
for (const current of frontier) {
|
|
94
|
-
const dependents = findImportDependents(
|
|
87
|
+
const dependents = repo.findImportDependents(current) as RelatedNodeRow[];
|
|
95
88
|
for (const dep of dependents) {
|
|
96
89
|
if (!visited.has(dep.id) && (!noTests || !isTestFile(dep.file))) {
|
|
97
90
|
visited.add(dep.id);
|
|
@@ -115,38 +108,37 @@ export function briefData(
|
|
|
115
108
|
customDbPath: string,
|
|
116
109
|
opts: { noTests?: boolean; config?: any } = {},
|
|
117
110
|
) {
|
|
118
|
-
|
|
119
|
-
try {
|
|
111
|
+
return withRepo(customDbPath, (repo) => {
|
|
120
112
|
const noTests = opts.noTests || false;
|
|
121
113
|
const config = opts.config || loadConfig();
|
|
122
114
|
const callerDepth = config.analysis?.briefCallerDepth ?? 5;
|
|
123
115
|
const importerDepth = config.analysis?.briefImporterDepth ?? 5;
|
|
124
116
|
const highRiskCallers = config.analysis?.briefHighRiskCallers ?? 10;
|
|
125
117
|
const mediumRiskCallers = config.analysis?.briefMediumRiskCallers ?? 3;
|
|
126
|
-
const fileNodes = findFileNodes(
|
|
118
|
+
const fileNodes = repo.findFileNodes(`%${file}%`) as NodeRow[];
|
|
127
119
|
if (fileNodes.length === 0) {
|
|
128
120
|
return { file, results: [] };
|
|
129
121
|
}
|
|
130
122
|
|
|
131
123
|
const results = fileNodes.map((fn) => {
|
|
132
124
|
// Direct importers
|
|
133
|
-
let importedBy = findImportSources(
|
|
125
|
+
let importedBy = repo.findImportSources(fn.id) as ImportEdgeRow[];
|
|
134
126
|
if (noTests) importedBy = importedBy.filter((i) => !isTestFile(i.file));
|
|
135
127
|
const directImporters = [...new Set(importedBy.map((i) => i.file))];
|
|
136
128
|
|
|
137
129
|
// Transitive importer count
|
|
138
|
-
const totalImporterCount = countTransitiveImporters(
|
|
130
|
+
const totalImporterCount = countTransitiveImporters(repo, [fn.id], noTests, importerDepth);
|
|
139
131
|
|
|
140
132
|
// Direct imports
|
|
141
|
-
let importsTo = findImportTargets(
|
|
133
|
+
let importsTo = repo.findImportTargets(fn.id) as ImportEdgeRow[];
|
|
142
134
|
if (noTests) importsTo = importsTo.filter((i) => !isTestFile(i.file));
|
|
143
135
|
|
|
144
136
|
// Symbol definitions with roles and caller counts
|
|
145
|
-
const defs = (findNodesByFile(
|
|
137
|
+
const defs = (repo.findNodesByFile(fn.file) as NodeRow[]).filter((d) =>
|
|
146
138
|
BRIEF_KINDS.has(d.kind),
|
|
147
139
|
);
|
|
148
140
|
const symbols = defs.map((d) => {
|
|
149
|
-
const callerCount = countTransitiveCallers(
|
|
141
|
+
const callerCount = countTransitiveCallers(repo, d.id, noTests, callerDepth);
|
|
150
142
|
return {
|
|
151
143
|
name: d.name,
|
|
152
144
|
kind: d.kind,
|
|
@@ -169,7 +161,5 @@ export function briefData(
|
|
|
169
161
|
});
|
|
170
162
|
|
|
171
163
|
return { file, results };
|
|
172
|
-
}
|
|
173
|
-
db.close();
|
|
174
|
-
}
|
|
164
|
+
});
|
|
175
165
|
}
|
|
@@ -264,6 +264,22 @@ function getNodeChildrenSafe(db: BetterSqlite3Database, nodeId: number) {
|
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
+
function buildIntraFileDataFlow(
|
|
268
|
+
db: BetterSqlite3Database,
|
|
269
|
+
file: string,
|
|
270
|
+
): Array<{ caller: string; callees: string[] }> {
|
|
271
|
+
const intraEdges = findIntraFileCallEdges(db, file) as IntraFileCallEdge[];
|
|
272
|
+
const dataFlowMap = new Map<string, string[]>();
|
|
273
|
+
for (const edge of intraEdges) {
|
|
274
|
+
if (!dataFlowMap.has(edge.caller_name)) dataFlowMap.set(edge.caller_name, []);
|
|
275
|
+
dataFlowMap.get(edge.caller_name)!.push(edge.callee_name);
|
|
276
|
+
}
|
|
277
|
+
return [...dataFlowMap.entries()].map(([caller, callees]) => ({
|
|
278
|
+
caller,
|
|
279
|
+
callees,
|
|
280
|
+
}));
|
|
281
|
+
}
|
|
282
|
+
|
|
267
283
|
function explainFileImpl(
|
|
268
284
|
db: BetterSqlite3Database,
|
|
269
285
|
target: string,
|
|
@@ -299,16 +315,7 @@ function explainFileImpl(
|
|
|
299
315
|
file: r.file,
|
|
300
316
|
}));
|
|
301
317
|
|
|
302
|
-
const
|
|
303
|
-
const dataFlowMap = new Map<string, string[]>();
|
|
304
|
-
for (const edge of intraEdges) {
|
|
305
|
-
if (!dataFlowMap.has(edge.caller_name)) dataFlowMap.set(edge.caller_name, []);
|
|
306
|
-
dataFlowMap.get(edge.caller_name)!.push(edge.callee_name);
|
|
307
|
-
}
|
|
308
|
-
const dataFlow = [...dataFlowMap.entries()].map(([caller, callees]) => ({
|
|
309
|
-
caller,
|
|
310
|
-
callees,
|
|
311
|
-
}));
|
|
318
|
+
const dataFlow = buildIntraFileDataFlow(db, fn.file);
|
|
312
319
|
|
|
313
320
|
const metric = getLineCountForNode(db, fn.id) as { line_count: number } | undefined;
|
|
314
321
|
let lineCount: number | null = metric?.line_count || null;
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
findCallees,
|
|
3
|
-
findCallers,
|
|
4
|
-
findFileNodes,
|
|
5
|
-
findImportSources,
|
|
6
|
-
findImportTargets,
|
|
7
|
-
findNodesByFile,
|
|
8
|
-
} from '../../db/index.js';
|
|
1
|
+
import { findFileNodes, type Repository } from '../../db/index.js';
|
|
9
2
|
import { cachedStmt } from '../../db/repository/cached-stmt.js';
|
|
10
3
|
import { isTestFile } from '../../infrastructure/test-filter.js';
|
|
11
4
|
import { resolveMethodViaHierarchy } from '../../shared/hierarchy.js';
|
|
@@ -18,13 +11,11 @@ import type {
|
|
|
18
11
|
RelatedNodeRow,
|
|
19
12
|
StmtCache,
|
|
20
13
|
} from '../../types.js';
|
|
21
|
-
import { withReadonlyDb } from './query-helpers.js';
|
|
14
|
+
import { withReadonlyDb, withRepo } from './query-helpers.js';
|
|
22
15
|
import { findMatchingNodes } from './symbol-lookup.js';
|
|
23
16
|
|
|
24
|
-
type UpstreamRow = { id: number; name: string; kind: string; file: string; line: number };
|
|
25
17
|
type NodeByIdRow = { name: string; kind: string; file: string; line: number };
|
|
26
18
|
|
|
27
|
-
const _upstreamStmtCache: StmtCache<UpstreamRow> = new WeakMap();
|
|
28
19
|
const _nodeByIdStmtCache: StmtCache<NodeByIdRow> = new WeakMap();
|
|
29
20
|
|
|
30
21
|
export function fileDepsData(
|
|
@@ -32,21 +23,21 @@ export function fileDepsData(
|
|
|
32
23
|
customDbPath: string,
|
|
33
24
|
opts: { noTests?: boolean; limit?: number; offset?: number } = {},
|
|
34
25
|
) {
|
|
35
|
-
return
|
|
26
|
+
return withRepo(customDbPath, (repo) => {
|
|
36
27
|
const noTests = opts.noTests || false;
|
|
37
|
-
const fileNodes = findFileNodes(
|
|
28
|
+
const fileNodes = repo.findFileNodes(`%${file}%`) as NodeRow[];
|
|
38
29
|
if (fileNodes.length === 0) {
|
|
39
30
|
return { file, results: [] };
|
|
40
31
|
}
|
|
41
32
|
|
|
42
33
|
const results = fileNodes.map((fn) => {
|
|
43
|
-
let importsTo = findImportTargets(
|
|
34
|
+
let importsTo = repo.findImportTargets(fn.id) as ImportEdgeRow[];
|
|
44
35
|
if (noTests) importsTo = importsTo.filter((i) => !isTestFile(i.file));
|
|
45
36
|
|
|
46
|
-
let importedBy = findImportSources(
|
|
37
|
+
let importedBy = repo.findImportSources(fn.id) as ImportEdgeRow[];
|
|
47
38
|
if (noTests) importedBy = importedBy.filter((i) => !isTestFile(i.file));
|
|
48
39
|
|
|
49
|
-
const defs = findNodesByFile(
|
|
40
|
+
const defs = repo.findNodesByFile(fn.file) as NodeRow[];
|
|
50
41
|
|
|
51
42
|
return {
|
|
52
43
|
file: fn.file,
|
|
@@ -64,9 +55,11 @@ export function fileDepsData(
|
|
|
64
55
|
/**
|
|
65
56
|
* BFS transitive caller traversal starting from `callers` of `nodeId`.
|
|
66
57
|
* Returns an object keyed by depth (2..depth) -> array of caller descriptors.
|
|
58
|
+
*
|
|
59
|
+
* Uses Repository.findCallers() so it works with both native and WASM engines.
|
|
67
60
|
*/
|
|
68
61
|
function buildTransitiveCallers(
|
|
69
|
-
|
|
62
|
+
repo: InstanceType<typeof Repository>,
|
|
70
63
|
callers: Array<{ id: number; name: string; kind: string; file: string; line: number }>,
|
|
71
64
|
nodeId: number,
|
|
72
65
|
depth: number,
|
|
@@ -81,31 +74,21 @@ function buildTransitiveCallers(
|
|
|
81
74
|
const visited = new Set([nodeId]);
|
|
82
75
|
let frontier = callers;
|
|
83
76
|
|
|
84
|
-
const upstreamStmt = cachedStmt(
|
|
85
|
-
_upstreamStmtCache,
|
|
86
|
-
db,
|
|
87
|
-
`
|
|
88
|
-
SELECT n.id, n.name, n.kind, n.file, n.line
|
|
89
|
-
FROM edges e JOIN nodes n ON e.source_id = n.id
|
|
90
|
-
WHERE e.target_id = ? AND e.kind = 'calls'
|
|
91
|
-
`,
|
|
92
|
-
);
|
|
93
|
-
|
|
94
77
|
for (let d = 2; d <= depth; d++) {
|
|
78
|
+
// Collect unvisited frontier IDs for a single batched query per depth
|
|
79
|
+
const unvisited = frontier.filter((f) => !visited.has(f.id));
|
|
80
|
+
for (const f of unvisited) visited.add(f.id);
|
|
81
|
+
if (unvisited.length === 0) break;
|
|
82
|
+
|
|
83
|
+
const batchCallers = repo.findCallersBatch(unvisited.map((f) => f.id));
|
|
95
84
|
const nextFrontier: typeof frontier = [];
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const upstream = upstreamStmt.all(f.id) as Array<{
|
|
100
|
-
id: number;
|
|
101
|
-
name: string;
|
|
102
|
-
kind: string;
|
|
103
|
-
file: string;
|
|
104
|
-
line: number;
|
|
105
|
-
}>;
|
|
85
|
+
const nextFrontierIds = new Set<number>();
|
|
86
|
+
for (const f of unvisited) {
|
|
87
|
+
const upstream = batchCallers.get(f.id) || [];
|
|
106
88
|
for (const u of upstream) {
|
|
107
89
|
if (noTests && isTestFile(u.file)) continue;
|
|
108
|
-
if (!visited.has(u.id)) {
|
|
90
|
+
if (!visited.has(u.id) && !nextFrontierIds.has(u.id)) {
|
|
91
|
+
nextFrontierIds.add(u.id);
|
|
109
92
|
nextFrontier.push(u);
|
|
110
93
|
}
|
|
111
94
|
}
|
|
@@ -119,12 +102,64 @@ function buildTransitiveCallers(
|
|
|
119
102
|
}));
|
|
120
103
|
}
|
|
121
104
|
frontier = nextFrontier;
|
|
122
|
-
if (frontier.length === 0) break;
|
|
123
105
|
}
|
|
124
106
|
|
|
125
107
|
return transitiveCallers;
|
|
126
108
|
}
|
|
127
109
|
|
|
110
|
+
function collectCallersWithHierarchy(
|
|
111
|
+
repo: InstanceType<typeof Repository>,
|
|
112
|
+
node: NodeRow,
|
|
113
|
+
noTests: boolean,
|
|
114
|
+
): Array<RelatedNodeRow & { viaHierarchy?: string }> {
|
|
115
|
+
let callers: Array<RelatedNodeRow & { viaHierarchy?: string }> = repo.findCallers(
|
|
116
|
+
node.id,
|
|
117
|
+
) as RelatedNodeRow[];
|
|
118
|
+
|
|
119
|
+
if (node.kind === 'method' && node.name.includes('.')) {
|
|
120
|
+
const methodName = node.name.split('.').pop()!;
|
|
121
|
+
const relatedMethods = resolveMethodViaHierarchy(repo, methodName);
|
|
122
|
+
for (const rm of relatedMethods) {
|
|
123
|
+
if (rm.id === node.id) continue;
|
|
124
|
+
const extraCallers = repo.findCallers(rm.id) as RelatedNodeRow[];
|
|
125
|
+
callers.push(...extraCallers.map((c) => ({ ...c, viaHierarchy: rm.name })));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (noTests) callers = callers.filter((c) => !isTestFile(c.file));
|
|
129
|
+
return callers;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function buildNodeDepsResult(
|
|
133
|
+
repo: InstanceType<typeof Repository>,
|
|
134
|
+
node: NodeRow,
|
|
135
|
+
hc: Map<string, string | null>,
|
|
136
|
+
depth: number,
|
|
137
|
+
noTests: boolean,
|
|
138
|
+
) {
|
|
139
|
+
const callees = repo.findCallees(node.id) as RelatedNodeRow[];
|
|
140
|
+
const filteredCallees = noTests ? callees.filter((c) => !isTestFile(c.file)) : callees;
|
|
141
|
+
const callers = collectCallersWithHierarchy(repo, node, noTests);
|
|
142
|
+
const transitiveCallers = buildTransitiveCallers(repo, callers, node.id, depth, noTests);
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
...normalizeSymbol(node, repo, hc),
|
|
146
|
+
callees: filteredCallees.map((c) => ({
|
|
147
|
+
name: c.name,
|
|
148
|
+
kind: c.kind,
|
|
149
|
+
file: c.file,
|
|
150
|
+
line: c.line,
|
|
151
|
+
})),
|
|
152
|
+
callers: callers.map((c) => ({
|
|
153
|
+
name: c.name,
|
|
154
|
+
kind: c.kind,
|
|
155
|
+
file: c.file,
|
|
156
|
+
line: c.line,
|
|
157
|
+
viaHierarchy: c.viaHierarchy || undefined,
|
|
158
|
+
})),
|
|
159
|
+
transitiveCallers,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
128
163
|
export function fnDepsData(
|
|
129
164
|
name: string,
|
|
130
165
|
customDbPath: string,
|
|
@@ -137,56 +172,17 @@ export function fnDepsData(
|
|
|
137
172
|
offset?: number;
|
|
138
173
|
} = {},
|
|
139
174
|
) {
|
|
140
|
-
return
|
|
175
|
+
return withRepo(customDbPath, (repo) => {
|
|
141
176
|
const depth = opts.depth || 3;
|
|
142
177
|
const noTests = opts.noTests || false;
|
|
143
178
|
const hc = new Map();
|
|
144
179
|
|
|
145
|
-
const nodes = findMatchingNodes(
|
|
180
|
+
const nodes = findMatchingNodes(repo, name, { noTests, file: opts.file, kind: opts.kind });
|
|
146
181
|
if (nodes.length === 0) {
|
|
147
182
|
return { name, results: [] };
|
|
148
183
|
}
|
|
149
184
|
|
|
150
|
-
const results = nodes.map((node) =>
|
|
151
|
-
const callees = findCallees(db, node.id) as RelatedNodeRow[];
|
|
152
|
-
const filteredCallees = noTests ? callees.filter((c) => !isTestFile(c.file)) : callees;
|
|
153
|
-
|
|
154
|
-
let callers: Array<RelatedNodeRow & { viaHierarchy?: string }> = findCallers(
|
|
155
|
-
db,
|
|
156
|
-
node.id,
|
|
157
|
-
) as RelatedNodeRow[];
|
|
158
|
-
|
|
159
|
-
if (node.kind === 'method' && node.name.includes('.')) {
|
|
160
|
-
const methodName = node.name.split('.').pop()!;
|
|
161
|
-
const relatedMethods = resolveMethodViaHierarchy(db, methodName);
|
|
162
|
-
for (const rm of relatedMethods) {
|
|
163
|
-
if (rm.id === node.id) continue;
|
|
164
|
-
const extraCallers = findCallers(db, rm.id) as RelatedNodeRow[];
|
|
165
|
-
callers.push(...extraCallers.map((c) => ({ ...c, viaHierarchy: rm.name })));
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
if (noTests) callers = callers.filter((c) => !isTestFile(c.file));
|
|
169
|
-
|
|
170
|
-
const transitiveCallers = buildTransitiveCallers(db, callers, node.id, depth, noTests);
|
|
171
|
-
|
|
172
|
-
return {
|
|
173
|
-
...normalizeSymbol(node, db, hc),
|
|
174
|
-
callees: filteredCallees.map((c) => ({
|
|
175
|
-
name: c.name,
|
|
176
|
-
kind: c.kind,
|
|
177
|
-
file: c.file,
|
|
178
|
-
line: c.line,
|
|
179
|
-
})),
|
|
180
|
-
callers: callers.map((c) => ({
|
|
181
|
-
name: c.name,
|
|
182
|
-
kind: c.kind,
|
|
183
|
-
file: c.file,
|
|
184
|
-
line: c.line,
|
|
185
|
-
viaHierarchy: c.viaHierarchy || undefined,
|
|
186
|
-
})),
|
|
187
|
-
transitiveCallers,
|
|
188
|
-
};
|
|
189
|
-
});
|
|
185
|
+
const results = nodes.map((node) => buildNodeDepsResult(repo, node, hc, depth, noTests));
|
|
190
186
|
|
|
191
187
|
const base = { name, results };
|
|
192
188
|
return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
|
|
@@ -1,40 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
findDistinctCallers,
|
|
3
|
-
findFileNodes,
|
|
4
|
-
findImplementors,
|
|
5
|
-
findImportDependents,
|
|
6
|
-
findNodeById,
|
|
7
|
-
} from '../../db/index.js';
|
|
1
|
+
import { Repository, SqliteRepository } from '../../db/index.js';
|
|
8
2
|
import { isTestFile } from '../../infrastructure/test-filter.js';
|
|
9
3
|
import { normalizeSymbol } from '../../shared/normalize.js';
|
|
10
4
|
import { paginateResult } from '../../shared/paginate.js';
|
|
11
5
|
import type { BetterSqlite3Database, NodeRow, RelatedNodeRow } from '../../types.js';
|
|
12
|
-
import { resolveAnalysisOpts,
|
|
6
|
+
import { resolveAnalysisOpts, withRepo } from './query-helpers.js';
|
|
13
7
|
import { findMatchingNodes } from './symbol-lookup.js';
|
|
14
8
|
|
|
9
|
+
/** Cache so repeated raw-db calls reuse the same SqliteRepository (preserves per-instance memoization). */
|
|
10
|
+
const repoCache = new WeakMap<BetterSqlite3Database, InstanceType<typeof SqliteRepository>>();
|
|
11
|
+
|
|
12
|
+
/** Coerce a raw db handle or Repository into a Repository instance. */
|
|
13
|
+
function toRepo(
|
|
14
|
+
dbOrRepo: BetterSqlite3Database | InstanceType<typeof Repository>,
|
|
15
|
+
): InstanceType<typeof Repository> {
|
|
16
|
+
if (dbOrRepo instanceof Repository) return dbOrRepo;
|
|
17
|
+
const db = dbOrRepo as BetterSqlite3Database;
|
|
18
|
+
let repo = repoCache.get(db);
|
|
19
|
+
if (!repo) {
|
|
20
|
+
repo = new SqliteRepository(db);
|
|
21
|
+
repoCache.set(db, repo);
|
|
22
|
+
}
|
|
23
|
+
return repo;
|
|
24
|
+
}
|
|
25
|
+
|
|
15
26
|
// --- Shared BFS: transitive callers ---
|
|
16
27
|
|
|
17
28
|
const INTERFACE_LIKE_KINDS = new Set(['interface', 'trait']);
|
|
18
29
|
|
|
19
|
-
/**
|
|
20
|
-
* Check whether the graph contains any 'implements' edges.
|
|
21
|
-
* Cached per db handle so the query runs at most once per connection.
|
|
22
|
-
*/
|
|
23
|
-
const _hasImplementsCache: WeakMap<BetterSqlite3Database, boolean> = new WeakMap();
|
|
24
|
-
function hasImplementsEdges(db: BetterSqlite3Database): boolean {
|
|
25
|
-
if (_hasImplementsCache.has(db)) return _hasImplementsCache.get(db)!;
|
|
26
|
-
const row = db.prepare("SELECT 1 FROM edges WHERE kind = 'implements' LIMIT 1").get();
|
|
27
|
-
const result = !!row;
|
|
28
|
-
_hasImplementsCache.set(db, result);
|
|
29
|
-
return result;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* BFS traversal to find transitive callers of a node.
|
|
34
|
-
* When an interface/trait node is encountered (either as the start node or
|
|
35
|
-
* during traversal), its concrete implementors are also added to the frontier
|
|
36
|
-
* so that changes to an interface signature propagate to all implementors.
|
|
37
|
-
*/
|
|
38
30
|
type BfsLevel = Array<{
|
|
39
31
|
name: string;
|
|
40
32
|
kind: string;
|
|
@@ -76,7 +68,7 @@ function recordImplementor(
|
|
|
76
68
|
|
|
77
69
|
/** Expand implementors for an interface/trait node into the BFS frontier. */
|
|
78
70
|
function expandImplementors(
|
|
79
|
-
|
|
71
|
+
repo: InstanceType<typeof Repository>,
|
|
80
72
|
nodeId: number,
|
|
81
73
|
depth: number,
|
|
82
74
|
visited: Set<number>,
|
|
@@ -85,14 +77,14 @@ function expandImplementors(
|
|
|
85
77
|
noTests: boolean,
|
|
86
78
|
onVisit?: BfsOnVisit,
|
|
87
79
|
): void {
|
|
88
|
-
const impls = findImplementors(
|
|
80
|
+
const impls = repo.findImplementors(nodeId) as RelatedNodeRow[];
|
|
89
81
|
for (const impl of impls) {
|
|
90
82
|
recordImplementor(impl, nodeId, depth, visited, frontier, levels, noTests, onVisit);
|
|
91
83
|
}
|
|
92
84
|
}
|
|
93
85
|
|
|
94
86
|
export function bfsTransitiveCallers(
|
|
95
|
-
|
|
87
|
+
dbOrRepo: BetterSqlite3Database | InstanceType<typeof Repository>,
|
|
96
88
|
startId: number,
|
|
97
89
|
{
|
|
98
90
|
noTests = false,
|
|
@@ -106,7 +98,8 @@ export function bfsTransitiveCallers(
|
|
|
106
98
|
onVisit?: BfsOnVisit;
|
|
107
99
|
} = {},
|
|
108
100
|
) {
|
|
109
|
-
const
|
|
101
|
+
const repo = toRepo(dbOrRepo);
|
|
102
|
+
const resolveImplementors = includeImplementors && repo.hasImplementsEdges();
|
|
110
103
|
const visited = new Set([startId]);
|
|
111
104
|
const levels: BfsLevels = {};
|
|
112
105
|
let frontier = [startId];
|
|
@@ -114,9 +107,9 @@ export function bfsTransitiveCallers(
|
|
|
114
107
|
// Seed: if start node is an interface/trait, include its implementors at depth 1
|
|
115
108
|
const implNextFrontier: number[] = [];
|
|
116
109
|
if (resolveImplementors) {
|
|
117
|
-
const startNode = findNodeById(
|
|
110
|
+
const startNode = repo.findNodeById(startId) as NodeRow | undefined;
|
|
118
111
|
if (startNode && INTERFACE_LIKE_KINDS.has(startNode.kind)) {
|
|
119
|
-
expandImplementors(
|
|
112
|
+
expandImplementors(repo, startId, 1, visited, implNextFrontier, levels, noTests, onVisit);
|
|
120
113
|
}
|
|
121
114
|
}
|
|
122
115
|
|
|
@@ -126,7 +119,7 @@ export function bfsTransitiveCallers(
|
|
|
126
119
|
}
|
|
127
120
|
const nextFrontier: number[] = [];
|
|
128
121
|
for (const fid of frontier) {
|
|
129
|
-
const callers = findDistinctCallers(
|
|
122
|
+
const callers = repo.findDistinctCallers(fid) as RelatedNodeRow[];
|
|
130
123
|
for (const c of callers) {
|
|
131
124
|
if (!visited.has(c.id) && (!noTests || !isTestFile(c.file))) {
|
|
132
125
|
visited.add(c.id);
|
|
@@ -136,7 +129,7 @@ export function bfsTransitiveCallers(
|
|
|
136
129
|
if (onVisit) onVisit(c, fid, d);
|
|
137
130
|
}
|
|
138
131
|
if (resolveImplementors && INTERFACE_LIKE_KINDS.has(c.kind)) {
|
|
139
|
-
expandImplementors(
|
|
132
|
+
expandImplementors(repo, c.id, d + 1, visited, nextFrontier, levels, noTests, onVisit);
|
|
140
133
|
}
|
|
141
134
|
}
|
|
142
135
|
}
|
|
@@ -152,9 +145,9 @@ export function impactAnalysisData(
|
|
|
152
145
|
customDbPath: string,
|
|
153
146
|
opts: { noTests?: boolean } = {},
|
|
154
147
|
) {
|
|
155
|
-
return
|
|
148
|
+
return withRepo(customDbPath, (repo) => {
|
|
156
149
|
const noTests = opts.noTests || false;
|
|
157
|
-
const fileNodes = findFileNodes(
|
|
150
|
+
const fileNodes = repo.findFileNodes(`%${file}%`) as NodeRow[];
|
|
158
151
|
if (fileNodes.length === 0) {
|
|
159
152
|
return { file, sources: [], levels: {}, totalDependents: 0 };
|
|
160
153
|
}
|
|
@@ -172,7 +165,7 @@ export function impactAnalysisData(
|
|
|
172
165
|
while (queue.length > 0) {
|
|
173
166
|
const current = queue.shift()!;
|
|
174
167
|
const level = levels.get(current)!;
|
|
175
|
-
const dependents = findImportDependents(
|
|
168
|
+
const dependents = repo.findImportDependents(current) as RelatedNodeRow[];
|
|
176
169
|
for (const dep of dependents) {
|
|
177
170
|
if (!visited.has(dep.id) && (!noTests || !isTestFile(dep.file))) {
|
|
178
171
|
visited.add(dep.id);
|
|
@@ -186,7 +179,7 @@ export function impactAnalysisData(
|
|
|
186
179
|
for (const [id, level] of levels) {
|
|
187
180
|
if (level === 0) continue;
|
|
188
181
|
if (!byLevel[level]) byLevel[level] = [];
|
|
189
|
-
const node = findNodeById(
|
|
182
|
+
const node = repo.findNodeById(id) as NodeRow | undefined;
|
|
190
183
|
if (node) byLevel[level].push({ file: node.file });
|
|
191
184
|
}
|
|
192
185
|
|
|
@@ -213,12 +206,12 @@ export function fnImpactData(
|
|
|
213
206
|
config?: any;
|
|
214
207
|
} = {},
|
|
215
208
|
) {
|
|
216
|
-
return
|
|
209
|
+
return withRepo(customDbPath, (repo) => {
|
|
217
210
|
const { noTests, config } = resolveAnalysisOpts(opts);
|
|
218
211
|
const maxDepth = opts.depth || config.analysis?.fnImpactDepth || 5;
|
|
219
212
|
const hc = new Map();
|
|
220
213
|
|
|
221
|
-
const nodes = findMatchingNodes(
|
|
214
|
+
const nodes = findMatchingNodes(repo, name, { noTests, file: opts.file, kind: opts.kind });
|
|
222
215
|
if (nodes.length === 0) {
|
|
223
216
|
return { name, results: [] };
|
|
224
217
|
}
|
|
@@ -226,13 +219,13 @@ export function fnImpactData(
|
|
|
226
219
|
const includeImplementors = opts.includeImplementors !== false;
|
|
227
220
|
|
|
228
221
|
const results = nodes.map((node) => {
|
|
229
|
-
const { levels, totalDependents } = bfsTransitiveCallers(
|
|
222
|
+
const { levels, totalDependents } = bfsTransitiveCallers(repo, node.id, {
|
|
230
223
|
noTests,
|
|
231
224
|
maxDepth,
|
|
232
225
|
includeImplementors,
|
|
233
226
|
});
|
|
234
227
|
return {
|
|
235
|
-
...normalizeSymbol(node,
|
|
228
|
+
...normalizeSymbol(node, repo, hc),
|
|
236
229
|
levels,
|
|
237
230
|
totalDependents,
|
|
238
231
|
};
|