@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
package/src/features/dataflow.ts
CHANGED
|
@@ -24,7 +24,7 @@ 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';
|
|
26
26
|
import { paginateResult } from '../shared/paginate.js';
|
|
27
|
-
import type { BetterSqlite3Database, NodeRow, TreeSitterNode } from '../types.js';
|
|
27
|
+
import type { BetterSqlite3Database, NativeDatabase, NodeRow, TreeSitterNode } from '../types.js';
|
|
28
28
|
import { findNodes } from './shared/find-nodes.js';
|
|
29
29
|
|
|
30
30
|
// Re-export for backward compatibility
|
|
@@ -237,6 +237,86 @@ function insertDataflowEdges(
|
|
|
237
237
|
|
|
238
238
|
// ── buildDataflowEdges ──────────────────────────────────────────────────────
|
|
239
239
|
|
|
240
|
+
function prepareNodeResolvers(db: BetterSqlite3Database): {
|
|
241
|
+
getNodeByNameAndFile: ReturnType<BetterSqlite3Database['prepare']>;
|
|
242
|
+
getNodeByName: ReturnType<BetterSqlite3Database['prepare']>;
|
|
243
|
+
} {
|
|
244
|
+
return {
|
|
245
|
+
getNodeByNameAndFile: db.prepare(
|
|
246
|
+
`SELECT id, name, kind, file, line FROM nodes
|
|
247
|
+
WHERE name = ? AND file = ? AND kind IN ('function', 'method')`,
|
|
248
|
+
),
|
|
249
|
+
getNodeByName: db.prepare(
|
|
250
|
+
`SELECT id, name, kind, file, line FROM nodes
|
|
251
|
+
WHERE name = ? AND kind IN ('function', 'method')
|
|
252
|
+
ORDER BY file, line LIMIT 10`,
|
|
253
|
+
),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function makeNodeResolver(
|
|
258
|
+
stmts: ReturnType<typeof prepareNodeResolvers>,
|
|
259
|
+
relPath: string,
|
|
260
|
+
): (funcName: string) => { id: number } | null {
|
|
261
|
+
return (funcName: string): { id: number } | null => {
|
|
262
|
+
const local = stmts.getNodeByNameAndFile.all(funcName, relPath) as { id: number }[];
|
|
263
|
+
if (local.length > 0) return local[0]!;
|
|
264
|
+
const global = stmts.getNodeByName.all(funcName) as { id: number }[];
|
|
265
|
+
return global.length > 0 ? global[0]! : null;
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function collectNativeEdges(
|
|
270
|
+
data: DataflowResult,
|
|
271
|
+
resolveNode: (name: string) => { id: number } | null,
|
|
272
|
+
edges: Array<Record<string, unknown>>,
|
|
273
|
+
): void {
|
|
274
|
+
for (const flow of data.argFlows as ArgFlow[]) {
|
|
275
|
+
const sourceNode = resolveNode(flow.callerFunc);
|
|
276
|
+
const targetNode = resolveNode(flow.calleeName);
|
|
277
|
+
if (sourceNode && targetNode) {
|
|
278
|
+
edges.push({
|
|
279
|
+
sourceId: sourceNode.id,
|
|
280
|
+
targetId: targetNode.id,
|
|
281
|
+
kind: 'flows_to',
|
|
282
|
+
paramIndex: flow.argIndex,
|
|
283
|
+
expression: flow.expression,
|
|
284
|
+
line: flow.line,
|
|
285
|
+
confidence: flow.confidence,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
for (const assignment of data.assignments as Assignment[]) {
|
|
290
|
+
const producerNode = resolveNode(assignment.sourceCallName);
|
|
291
|
+
const consumerNode = resolveNode(assignment.callerFunc);
|
|
292
|
+
if (producerNode && consumerNode) {
|
|
293
|
+
edges.push({
|
|
294
|
+
sourceId: producerNode.id,
|
|
295
|
+
targetId: consumerNode.id,
|
|
296
|
+
kind: 'returns',
|
|
297
|
+
paramIndex: undefined,
|
|
298
|
+
expression: assignment.expression,
|
|
299
|
+
line: assignment.line,
|
|
300
|
+
confidence: 1.0,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
for (const mut of data.mutations as Mutation[]) {
|
|
305
|
+
const mutatorNode = resolveNode(mut.funcName);
|
|
306
|
+
if (mutatorNode && mut.binding?.type === 'param') {
|
|
307
|
+
edges.push({
|
|
308
|
+
sourceId: mutatorNode.id,
|
|
309
|
+
targetId: mutatorNode.id,
|
|
310
|
+
kind: 'mutates',
|
|
311
|
+
paramIndex: undefined,
|
|
312
|
+
expression: mut.mutatingExpr,
|
|
313
|
+
line: mut.line,
|
|
314
|
+
confidence: 1.0,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
240
320
|
export async function buildDataflowEdges(
|
|
241
321
|
db: BetterSqlite3Database,
|
|
242
322
|
fileSymbols: Map<string, FileSymbolsDataflow>,
|
|
@@ -254,28 +334,7 @@ export async function buildDataflowEdges(
|
|
|
254
334
|
if (nativeDb?.bulkInsertDataflow) {
|
|
255
335
|
let needsJsFallback = false;
|
|
256
336
|
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
|
-
);
|
|
337
|
+
const stmts = prepareNodeResolvers(db);
|
|
279
338
|
|
|
280
339
|
for (const [relPath, symbols] of fileSymbols) {
|
|
281
340
|
const ext = path.extname(relPath).toLowerCase();
|
|
@@ -285,58 +344,7 @@ export async function buildDataflowEdges(
|
|
|
285
344
|
break;
|
|
286
345
|
}
|
|
287
346
|
|
|
288
|
-
|
|
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
|
-
}
|
|
347
|
+
collectNativeEdges(symbols.dataflow, makeNodeResolver(stmts, relPath), nativeEdges);
|
|
340
348
|
}
|
|
341
349
|
|
|
342
350
|
if (!needsJsFallback) {
|
|
@@ -363,29 +371,7 @@ export async function buildDataflowEdges(
|
|
|
363
371
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
364
372
|
);
|
|
365
373
|
|
|
366
|
-
const
|
|
367
|
-
id: number;
|
|
368
|
-
name: string;
|
|
369
|
-
kind: string;
|
|
370
|
-
file: string;
|
|
371
|
-
line: number;
|
|
372
|
-
}>(
|
|
373
|
-
`SELECT id, name, kind, file, line FROM nodes
|
|
374
|
-
WHERE name = ? AND file = ? AND kind IN ('function', 'method')`,
|
|
375
|
-
);
|
|
376
|
-
|
|
377
|
-
const getNodeByName = db.prepare<{
|
|
378
|
-
id: number;
|
|
379
|
-
name: string;
|
|
380
|
-
kind: string;
|
|
381
|
-
file: string;
|
|
382
|
-
line: number;
|
|
383
|
-
}>(
|
|
384
|
-
`SELECT id, name, kind, file, line FROM nodes
|
|
385
|
-
WHERE name = ? AND kind IN ('function', 'method')
|
|
386
|
-
ORDER BY file, line LIMIT 10`,
|
|
387
|
-
);
|
|
388
|
-
|
|
374
|
+
const stmts = prepareNodeResolvers(db);
|
|
389
375
|
let totalEdges = 0;
|
|
390
376
|
|
|
391
377
|
const tx = db.transaction(() => {
|
|
@@ -396,14 +382,7 @@ export async function buildDataflowEdges(
|
|
|
396
382
|
const data = getDataflowForFile(symbols, relPath, rootDir, extToLang, parsers, getParserFn);
|
|
397
383
|
if (!data) continue;
|
|
398
384
|
|
|
399
|
-
|
|
400
|
-
const local = getNodeByNameAndFile.all(funcName, relPath);
|
|
401
|
-
if (local.length > 0) return local[0]!;
|
|
402
|
-
const global = getNodeByName.all(funcName);
|
|
403
|
-
return global.length > 0 ? global[0]! : null;
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
totalEdges += insertDataflowEdges(insert, data, resolveNode);
|
|
385
|
+
totalEdges += insertDataflowEdges(insert, data, makeNodeResolver(stmts, relPath));
|
|
407
386
|
}
|
|
408
387
|
});
|
|
409
388
|
|
|
@@ -540,6 +519,83 @@ function buildNodeDataflowResult(
|
|
|
540
519
|
};
|
|
541
520
|
}
|
|
542
521
|
|
|
522
|
+
function buildNativeDataflowResult(
|
|
523
|
+
node: NodeRow,
|
|
524
|
+
nativeDb: NativeDatabase,
|
|
525
|
+
db: BetterSqlite3Database,
|
|
526
|
+
hc: Map<string, string | null>,
|
|
527
|
+
noTests: boolean,
|
|
528
|
+
): Record<string, unknown> {
|
|
529
|
+
const sym = normalizeSymbol(node, db, hc);
|
|
530
|
+
const d = nativeDb.getDataflowEdges!(node.id);
|
|
531
|
+
|
|
532
|
+
const flowsTo = d.flowsToOut.map((r: any) => ({
|
|
533
|
+
target: r.name,
|
|
534
|
+
kind: r.kind,
|
|
535
|
+
file: r.file,
|
|
536
|
+
line: r.line,
|
|
537
|
+
paramIndex: r.paramIndex,
|
|
538
|
+
expression: r.expression,
|
|
539
|
+
confidence: r.confidence,
|
|
540
|
+
}));
|
|
541
|
+
const flowsFrom = d.flowsToIn.map((r: any) => ({
|
|
542
|
+
source: r.name,
|
|
543
|
+
kind: r.kind,
|
|
544
|
+
file: r.file,
|
|
545
|
+
line: r.line,
|
|
546
|
+
paramIndex: r.paramIndex,
|
|
547
|
+
expression: r.expression,
|
|
548
|
+
confidence: r.confidence,
|
|
549
|
+
}));
|
|
550
|
+
const returnConsumers = d.returnsOut.map((r: any) => ({
|
|
551
|
+
consumer: r.name,
|
|
552
|
+
kind: r.kind,
|
|
553
|
+
file: r.file,
|
|
554
|
+
line: r.line,
|
|
555
|
+
expression: r.expression,
|
|
556
|
+
}));
|
|
557
|
+
const returnedBy = d.returnsIn.map((r: any) => ({
|
|
558
|
+
producer: r.name,
|
|
559
|
+
kind: r.kind,
|
|
560
|
+
file: r.file,
|
|
561
|
+
line: r.line,
|
|
562
|
+
expression: r.expression,
|
|
563
|
+
}));
|
|
564
|
+
const mutatesTargets = d.mutatesOut.map((r: any) => ({
|
|
565
|
+
target: r.name,
|
|
566
|
+
expression: r.expression,
|
|
567
|
+
line: r.line,
|
|
568
|
+
}));
|
|
569
|
+
const mutatedBy = d.mutatesIn.map((r: any) => ({
|
|
570
|
+
source: r.name,
|
|
571
|
+
expression: r.expression,
|
|
572
|
+
line: r.line,
|
|
573
|
+
}));
|
|
574
|
+
|
|
575
|
+
if (noTests) {
|
|
576
|
+
const filter = (arr: any[]) => arr.filter((r: any) => !isTestFile(r.file));
|
|
577
|
+
return {
|
|
578
|
+
...sym,
|
|
579
|
+
flowsTo: filter(flowsTo),
|
|
580
|
+
flowsFrom: filter(flowsFrom),
|
|
581
|
+
returns: returnConsumers.filter((r: any) => !isTestFile(r.file)),
|
|
582
|
+
returnedBy: returnedBy.filter((r: any) => !isTestFile(r.file)),
|
|
583
|
+
mutates: mutatesTargets,
|
|
584
|
+
mutatedBy,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return {
|
|
589
|
+
...sym,
|
|
590
|
+
flowsTo,
|
|
591
|
+
flowsFrom,
|
|
592
|
+
returns: returnConsumers,
|
|
593
|
+
returnedBy,
|
|
594
|
+
mutates: mutatesTargets,
|
|
595
|
+
mutatedBy,
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
543
599
|
export function dataflowData(
|
|
544
600
|
name: string,
|
|
545
601
|
customDbPath?: string,
|
|
@@ -571,75 +627,9 @@ export function dataflowData(
|
|
|
571
627
|
// ── Native fast path: 6 queries per node → 1 napi call per node ──
|
|
572
628
|
if (nativeDb?.getDataflowEdges) {
|
|
573
629
|
const hc = new Map<string, string | null>();
|
|
574
|
-
const results = nodes.map((node: NodeRow) =>
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
}
|
|
633
|
-
return {
|
|
634
|
-
...sym,
|
|
635
|
-
flowsTo,
|
|
636
|
-
flowsFrom,
|
|
637
|
-
returns: returnConsumers,
|
|
638
|
-
returnedBy,
|
|
639
|
-
mutates: mutatesTargets,
|
|
640
|
-
mutatedBy,
|
|
641
|
-
};
|
|
642
|
-
});
|
|
630
|
+
const results = nodes.map((node: NodeRow) =>
|
|
631
|
+
buildNativeDataflowResult(node, nativeDb, db, hc, noTests),
|
|
632
|
+
);
|
|
643
633
|
const base = { name, results };
|
|
644
634
|
return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
|
|
645
635
|
}
|
package/src/features/flow.ts
CHANGED
|
@@ -96,6 +96,128 @@ interface NodeInfo {
|
|
|
96
96
|
type?: string;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/** Resolve the entry node by direct match or framework-prefix matching. */
|
|
100
|
+
function resolveEntryNode(
|
|
101
|
+
db: ReturnType<typeof openReadonlyOrFail>,
|
|
102
|
+
name: string,
|
|
103
|
+
flowOpts: { noTests?: boolean; file?: string; kinds?: string[] },
|
|
104
|
+
): {
|
|
105
|
+
id: number;
|
|
106
|
+
name: string;
|
|
107
|
+
kind: string;
|
|
108
|
+
file: string;
|
|
109
|
+
line: number;
|
|
110
|
+
role?: string | null;
|
|
111
|
+
} | null {
|
|
112
|
+
// Phase 1: Direct LIKE match on full name
|
|
113
|
+
let matchNode = findMatchingNodes(db, name, flowOpts)[0] ?? null;
|
|
114
|
+
|
|
115
|
+
// Phase 2: Prefix-stripped matching — try adding framework prefixes
|
|
116
|
+
if (!matchNode) {
|
|
117
|
+
for (const prefix of FRAMEWORK_ENTRY_PREFIXES) {
|
|
118
|
+
matchNode = findMatchingNodes(db, `${prefix}${name}`, flowOpts)[0] ?? null;
|
|
119
|
+
if (matchNode) break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return matchNode;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interface BfsState {
|
|
127
|
+
visited: Set<number>;
|
|
128
|
+
steps: Array<{ depth: number; nodes: NodeInfo[] }>;
|
|
129
|
+
cycles: Array<{ from: string; to: string; depth: number }>;
|
|
130
|
+
nodeDepths: Map<number, number>;
|
|
131
|
+
idToNode: Map<number, NodeInfo>;
|
|
132
|
+
truncated: boolean;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Forward BFS through callees, collecting steps, cycles, and node depth info. */
|
|
136
|
+
function bfsCallees(
|
|
137
|
+
db: ReturnType<typeof openReadonlyOrFail>,
|
|
138
|
+
entryId: number,
|
|
139
|
+
entryInfo: NodeInfo,
|
|
140
|
+
maxDepth: number,
|
|
141
|
+
noTests: boolean,
|
|
142
|
+
): BfsState {
|
|
143
|
+
const visited = new Set<number>([entryId]);
|
|
144
|
+
let frontier = [entryId];
|
|
145
|
+
const steps: Array<{ depth: number; nodes: NodeInfo[] }> = [];
|
|
146
|
+
const cycles: Array<{ from: string; to: string; depth: number }> = [];
|
|
147
|
+
const nodeDepths = new Map<number, number>();
|
|
148
|
+
const idToNode = new Map<number, NodeInfo>();
|
|
149
|
+
idToNode.set(entryId, entryInfo);
|
|
150
|
+
let truncated = false;
|
|
151
|
+
|
|
152
|
+
const calleesStmt = db.prepare<CalleeRow>(
|
|
153
|
+
`SELECT DISTINCT n.id, n.name, n.kind, n.file, n.line, n.role
|
|
154
|
+
FROM edges e JOIN nodes n ON e.target_id = n.id
|
|
155
|
+
WHERE e.source_id = ? AND e.kind = 'calls'`,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
for (let d = 1; d <= maxDepth; d++) {
|
|
159
|
+
const nextFrontier: number[] = [];
|
|
160
|
+
const levelNodes: NodeInfo[] = [];
|
|
161
|
+
|
|
162
|
+
for (const fid of frontier) {
|
|
163
|
+
const callees = calleesStmt.all(fid);
|
|
164
|
+
|
|
165
|
+
for (const c of callees) {
|
|
166
|
+
if (noTests && isTestFile(c.file)) continue;
|
|
167
|
+
|
|
168
|
+
if (visited.has(c.id)) {
|
|
169
|
+
const fromNode = idToNode.get(fid);
|
|
170
|
+
if (fromNode) {
|
|
171
|
+
cycles.push({ from: fromNode.name, to: c.name, depth: d });
|
|
172
|
+
}
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
visited.add(c.id);
|
|
177
|
+
nextFrontier.push(c.id);
|
|
178
|
+
const nodeInfo: NodeInfo = { name: c.name, kind: c.kind, file: c.file, line: c.line };
|
|
179
|
+
levelNodes.push(nodeInfo);
|
|
180
|
+
nodeDepths.set(c.id, d);
|
|
181
|
+
idToNode.set(c.id, nodeInfo);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (levelNodes.length > 0) {
|
|
186
|
+
steps.push({ depth: d, nodes: levelNodes });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
frontier = nextFrontier;
|
|
190
|
+
if (frontier.length === 0) break;
|
|
191
|
+
if (d === maxDepth && frontier.length > 0) truncated = true;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { visited, steps, cycles, nodeDepths, idToNode, truncated };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Identify leaf nodes — visited nodes with no outgoing 'calls' edges. */
|
|
198
|
+
function findLeafNodes(
|
|
199
|
+
db: ReturnType<typeof openReadonlyOrFail>,
|
|
200
|
+
nodeDepths: Map<number, number>,
|
|
201
|
+
idToNode: Map<number, NodeInfo>,
|
|
202
|
+
): Array<NodeInfo & { depth: number }> {
|
|
203
|
+
const leaves: Array<NodeInfo & { depth: number }> = [];
|
|
204
|
+
const outgoingStmt = db.prepare<{ id: number }>(
|
|
205
|
+
`SELECT DISTINCT n.id
|
|
206
|
+
FROM edges e JOIN nodes n ON e.target_id = n.id
|
|
207
|
+
WHERE e.source_id = ? AND e.kind = 'calls'`,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
for (const [id, depth] of nodeDepths) {
|
|
211
|
+
const outgoing = outgoingStmt.all(id);
|
|
212
|
+
if (outgoing.length === 0) {
|
|
213
|
+
const node = idToNode.get(id);
|
|
214
|
+
if (node) leaves.push({ ...node, depth });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return leaves;
|
|
219
|
+
}
|
|
220
|
+
|
|
99
221
|
export function flowData(
|
|
100
222
|
name: string,
|
|
101
223
|
dbPath?: string,
|
|
@@ -117,17 +239,7 @@ export function flowData(
|
|
|
117
239
|
kinds: opts.kind ? [opts.kind] : (CORE_SYMBOL_KINDS as unknown as string[]),
|
|
118
240
|
};
|
|
119
241
|
|
|
120
|
-
|
|
121
|
-
// not just FUNCTION_KINDS, so flow can trace from interfaces/types/structs/etc.)
|
|
122
|
-
let matchNode = findMatchingNodes(db, name, flowOpts)[0] ?? null;
|
|
123
|
-
|
|
124
|
-
// Phase 2: Prefix-stripped matching — try adding framework prefixes
|
|
125
|
-
if (!matchNode) {
|
|
126
|
-
for (const prefix of FRAMEWORK_ENTRY_PREFIXES) {
|
|
127
|
-
matchNode = findMatchingNodes(db, `${prefix}${name}`, flowOpts)[0] ?? null;
|
|
128
|
-
if (matchNode) break;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
242
|
+
const matchNode = resolveEntryNode(db, name, flowOpts);
|
|
131
243
|
|
|
132
244
|
if (!matchNode) {
|
|
133
245
|
return {
|
|
@@ -151,92 +263,17 @@ export function flowData(
|
|
|
151
263
|
role: matchNode.role,
|
|
152
264
|
};
|
|
153
265
|
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
let frontier = [matchNode.id];
|
|
157
|
-
const steps: Array<{ depth: number; nodes: NodeInfo[] }> = [];
|
|
158
|
-
const cycles: Array<{ from: string; to: string; depth: number }> = [];
|
|
159
|
-
let truncated = false;
|
|
160
|
-
|
|
161
|
-
// Track which nodes are at each depth and their depth for leaf detection
|
|
162
|
-
const nodeDepths = new Map<number, number>();
|
|
163
|
-
const idToNode = new Map<number, NodeInfo>();
|
|
164
|
-
idToNode.set(matchNode.id, entry);
|
|
165
|
-
|
|
166
|
-
for (let d = 1; d <= maxDepth; d++) {
|
|
167
|
-
const nextFrontier: number[] = [];
|
|
168
|
-
const levelNodes: NodeInfo[] = [];
|
|
169
|
-
|
|
170
|
-
for (const fid of frontier) {
|
|
171
|
-
const callees = db
|
|
172
|
-
.prepare<CalleeRow>(
|
|
173
|
-
`SELECT DISTINCT n.id, n.name, n.kind, n.file, n.line, n.role
|
|
174
|
-
FROM edges e JOIN nodes n ON e.target_id = n.id
|
|
175
|
-
WHERE e.source_id = ? AND e.kind = 'calls'`,
|
|
176
|
-
)
|
|
177
|
-
.all(fid);
|
|
178
|
-
|
|
179
|
-
for (const c of callees) {
|
|
180
|
-
if (noTests && isTestFile(c.file)) continue;
|
|
181
|
-
|
|
182
|
-
if (visited.has(c.id)) {
|
|
183
|
-
// Cycle detected
|
|
184
|
-
const fromNode = idToNode.get(fid);
|
|
185
|
-
if (fromNode) {
|
|
186
|
-
cycles.push({ from: fromNode.name, to: c.name, depth: d });
|
|
187
|
-
}
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
visited.add(c.id);
|
|
192
|
-
nextFrontier.push(c.id);
|
|
193
|
-
const nodeInfo: NodeInfo = { name: c.name, kind: c.kind, file: c.file, line: c.line };
|
|
194
|
-
levelNodes.push(nodeInfo);
|
|
195
|
-
nodeDepths.set(c.id, d);
|
|
196
|
-
idToNode.set(c.id, nodeInfo);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (levelNodes.length > 0) {
|
|
201
|
-
steps.push({ depth: d, nodes: levelNodes });
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
frontier = nextFrontier;
|
|
205
|
-
if (frontier.length === 0) break;
|
|
206
|
-
|
|
207
|
-
if (d === maxDepth && frontier.length > 0) {
|
|
208
|
-
truncated = true;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Identify leaves: visited nodes that have no outgoing 'calls' edges to other visited nodes
|
|
213
|
-
// (or no outgoing calls at all)
|
|
214
|
-
const leaves: Array<NodeInfo & { depth: number }> = [];
|
|
215
|
-
for (const [id, depth] of nodeDepths) {
|
|
216
|
-
const outgoing = db
|
|
217
|
-
.prepare<{ id: number }>(
|
|
218
|
-
`SELECT DISTINCT n.id
|
|
219
|
-
FROM edges e JOIN nodes n ON e.target_id = n.id
|
|
220
|
-
WHERE e.source_id = ? AND e.kind = 'calls'`,
|
|
221
|
-
)
|
|
222
|
-
.all(id);
|
|
223
|
-
|
|
224
|
-
if (outgoing.length === 0) {
|
|
225
|
-
const node = idToNode.get(id);
|
|
226
|
-
if (node) {
|
|
227
|
-
leaves.push({ ...node, depth });
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
266
|
+
const bfs = bfsCallees(db, matchNode.id, entry, maxDepth, noTests);
|
|
267
|
+
const leaves = findLeafNodes(db, bfs.nodeDepths, bfs.idToNode);
|
|
231
268
|
|
|
232
269
|
const base = {
|
|
233
270
|
entry,
|
|
234
271
|
depth: maxDepth,
|
|
235
|
-
steps,
|
|
272
|
+
steps: bfs.steps,
|
|
236
273
|
leaves,
|
|
237
|
-
cycles,
|
|
238
|
-
totalReached: visited.size - 1,
|
|
239
|
-
truncated,
|
|
274
|
+
cycles: bfs.cycles,
|
|
275
|
+
totalReached: bfs.visited.size - 1,
|
|
276
|
+
truncated: bfs.truncated,
|
|
240
277
|
};
|
|
241
278
|
return paginateResult(base, 'steps', { limit: opts.limit, offset: opts.offset });
|
|
242
279
|
} finally {
|