@optave/codegraph 3.5.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -21
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +119 -127
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +14 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.js +11 -13
- package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
- package/dist/db/connection.d.ts +12 -2
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +81 -53
- package/dist/db/connection.js.map +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +38 -32
- package/dist/db/migrations.js.map +1 -1
- package/dist/domain/analysis/context.d.ts.map +1 -1
- package/dist/domain/analysis/context.js +51 -66
- package/dist/domain/analysis/context.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +62 -70
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/diff-impact.d.ts +9 -7
- package/dist/domain/analysis/diff-impact.d.ts.map +1 -1
- package/dist/domain/analysis/exports.d.ts.map +1 -1
- package/dist/domain/analysis/exports.js +29 -33
- package/dist/domain/analysis/exports.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts +15 -17
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +35 -65
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +91 -6
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/analysis/query-helpers.d.ts +20 -0
- package/dist/domain/analysis/query-helpers.d.ts.map +1 -0
- package/dist/domain/analysis/query-helpers.js +27 -0
- package/dist/domain/analysis/query-helpers.js.map +1 -0
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +15 -9
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +3 -2
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +69 -3
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +7 -51
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.js +7 -5
- package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.js +2 -2
- package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +124 -105
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +28 -15
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +3 -2
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/resolve.d.ts +0 -4
- package/dist/domain/graph/resolve.d.ts.map +1 -1
- package/dist/domain/graph/resolve.js +32 -48
- package/dist/domain/graph/resolve.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +12 -12
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +1 -1
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +206 -101
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/search/cli-formatter.d.ts.map +1 -1
- package/dist/domain/search/search/cli-formatter.js +88 -83
- package/dist/domain/search/search/cli-formatter.js.map +1 -1
- package/dist/extractors/bash.d.ts +6 -0
- package/dist/extractors/bash.d.ts.map +1 -0
- package/dist/extractors/bash.js +91 -0
- package/dist/extractors/bash.js.map +1 -0
- package/dist/extractors/c.d.ts +6 -0
- package/dist/extractors/c.d.ts.map +1 -0
- package/dist/extractors/c.js +204 -0
- package/dist/extractors/c.js.map +1 -0
- package/dist/extractors/cpp.d.ts +6 -0
- package/dist/extractors/cpp.d.ts.map +1 -0
- package/dist/extractors/cpp.js +283 -0
- package/dist/extractors/cpp.js.map +1 -0
- package/dist/extractors/csharp.d.ts.map +1 -1
- package/dist/extractors/csharp.js +42 -54
- package/dist/extractors/csharp.js.map +1 -1
- package/dist/extractors/dart.d.ts +6 -0
- package/dist/extractors/dart.d.ts.map +1 -0
- package/dist/extractors/dart.js +277 -0
- package/dist/extractors/dart.js.map +1 -0
- package/dist/extractors/elixir.d.ts +9 -0
- package/dist/extractors/elixir.d.ts.map +1 -0
- package/dist/extractors/elixir.js +223 -0
- package/dist/extractors/elixir.js.map +1 -0
- package/dist/extractors/go.d.ts.map +1 -1
- package/dist/extractors/go.js +126 -130
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/haskell.d.ts +8 -0
- package/dist/extractors/haskell.d.ts.map +1 -0
- package/dist/extractors/haskell.js +217 -0
- package/dist/extractors/haskell.js.map +1 -0
- package/dist/extractors/hcl.js +6 -6
- package/dist/extractors/hcl.js.map +1 -1
- package/dist/extractors/helpers.d.ts +32 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +74 -0
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/index.d.ts +12 -0
- package/dist/extractors/index.d.ts.map +1 -1
- package/dist/extractors/index.js +12 -0
- package/dist/extractors/index.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +32 -47
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.d.ts.map +1 -1
- package/dist/extractors/javascript.js +306 -292
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/kotlin.d.ts +6 -0
- package/dist/extractors/kotlin.d.ts.map +1 -0
- package/dist/extractors/kotlin.js +275 -0
- package/dist/extractors/kotlin.js.map +1 -0
- package/dist/extractors/lua.d.ts +6 -0
- package/dist/extractors/lua.d.ts.map +1 -0
- package/dist/extractors/lua.js +162 -0
- package/dist/extractors/lua.js.map +1 -0
- package/dist/extractors/ocaml.d.ts +6 -0
- package/dist/extractors/ocaml.d.ts.map +1 -0
- package/dist/extractors/ocaml.js +236 -0
- package/dist/extractors/ocaml.js.map +1 -0
- package/dist/extractors/php.d.ts.map +1 -1
- package/dist/extractors/php.js +39 -44
- package/dist/extractors/php.js.map +1 -1
- package/dist/extractors/python.d.ts.map +1 -1
- package/dist/extractors/python.js +75 -93
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/ruby.js +6 -13
- package/dist/extractors/ruby.js.map +1 -1
- package/dist/extractors/rust.d.ts.map +1 -1
- package/dist/extractors/rust.js +58 -83
- package/dist/extractors/rust.js.map +1 -1
- package/dist/extractors/scala.d.ts +6 -0
- package/dist/extractors/scala.d.ts.map +1 -0
- package/dist/extractors/scala.js +269 -0
- package/dist/extractors/scala.js.map +1 -0
- package/dist/extractors/swift.d.ts +6 -0
- package/dist/extractors/swift.d.ts.map +1 -0
- package/dist/extractors/swift.js +275 -0
- package/dist/extractors/swift.js.map +1 -0
- package/dist/extractors/zig.d.ts +9 -0
- package/dist/extractors/zig.d.ts.map +1 -0
- package/dist/extractors/zig.js +276 -0
- package/dist/extractors/zig.js.map +1 -0
- package/dist/features/ast.d.ts +2 -0
- package/dist/features/ast.d.ts.map +1 -1
- package/dist/features/ast.js +9 -24
- package/dist/features/ast.js.map +1 -1
- package/dist/features/audit.d.ts.map +1 -1
- package/dist/features/audit.js +17 -21
- package/dist/features/audit.js.map +1 -1
- package/dist/features/branch-compare.d.ts.map +1 -1
- package/dist/features/branch-compare.js +47 -3
- package/dist/features/branch-compare.js.map +1 -1
- package/dist/features/cfg.d.ts +7 -1
- package/dist/features/cfg.d.ts.map +1 -1
- package/dist/features/cfg.js +72 -61
- package/dist/features/cfg.js.map +1 -1
- package/dist/features/check.d.ts.map +1 -1
- package/dist/features/check.js +79 -62
- package/dist/features/check.js.map +1 -1
- package/dist/features/complexity-query.d.ts.map +1 -1
- package/dist/features/complexity-query.js +142 -137
- package/dist/features/complexity-query.js.map +1 -1
- package/dist/features/complexity.d.ts +7 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +62 -1
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts +7 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +356 -188
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/graph-enrichment.d.ts.map +1 -1
- package/dist/features/graph-enrichment.js +117 -104
- package/dist/features/graph-enrichment.js.map +1 -1
- package/dist/features/sequence.d.ts.map +1 -1
- package/dist/features/sequence.js +25 -4
- package/dist/features/sequence.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +29 -4
- package/dist/features/structure-query.js.map +1 -1
- package/dist/features/structure.d.ts.map +1 -1
- package/dist/features/structure.js +35 -15
- package/dist/features/structure.js.map +1 -1
- package/dist/graph/algorithms/leiden/adapter.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/adapter.js +88 -73
- package/dist/graph/algorithms/leiden/adapter.js.map +1 -1
- package/dist/graph/algorithms/leiden/index.js +43 -28
- package/dist/graph/algorithms/leiden/index.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +90 -104
- package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
- package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/partition.js +89 -106
- package/dist/graph/algorithms/leiden/partition.js.map +1 -1
- package/dist/graph/model.d.ts +2 -0
- package/dist/graph/model.d.ts.map +1 -1
- package/dist/graph/model.js +20 -8
- package/dist/graph/model.js.map +1 -1
- package/dist/infrastructure/config.d.ts +0 -8
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +73 -62
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/registry.d.ts +0 -8
- package/dist/infrastructure/registry.d.ts.map +1 -1
- package/dist/infrastructure/registry.js +12 -14
- package/dist/infrastructure/registry.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +45 -36
- package/dist/mcp/server.js.map +1 -1
- package/dist/presentation/audit.d.ts.map +1 -1
- package/dist/presentation/audit.js +61 -57
- package/dist/presentation/audit.js.map +1 -1
- package/dist/presentation/branch-compare.d.ts.map +1 -1
- package/dist/presentation/branch-compare.js +56 -38
- package/dist/presentation/branch-compare.js.map +1 -1
- package/dist/presentation/check.d.ts.map +1 -1
- package/dist/presentation/check.js +30 -32
- package/dist/presentation/check.js.map +1 -1
- package/dist/presentation/colors.d.ts.map +1 -1
- package/dist/presentation/colors.js +2 -0
- package/dist/presentation/colors.js.map +1 -1
- package/dist/presentation/complexity.d.ts.map +1 -1
- package/dist/presentation/complexity.js +25 -19
- package/dist/presentation/complexity.js.map +1 -1
- package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
- package/dist/presentation/queries-cli/exports.js +15 -15
- package/dist/presentation/queries-cli/exports.js.map +1 -1
- package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
- package/dist/presentation/queries-cli/impact.js +29 -19
- package/dist/presentation/queries-cli/impact.js.map +1 -1
- package/dist/types.d.ts +182 -7
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-bash.wasm +0 -0
- package/grammars/tree-sitter-c.wasm +0 -0
- package/grammars/tree-sitter-cpp.wasm +0 -0
- package/grammars/tree-sitter-dart.wasm +0 -0
- package/grammars/tree-sitter-elixir.wasm +0 -0
- package/grammars/tree-sitter-haskell.wasm +0 -0
- package/grammars/tree-sitter-kotlin.wasm +0 -0
- package/grammars/tree-sitter-lua.wasm +0 -0
- package/grammars/tree-sitter-ocaml.wasm +0 -0
- package/grammars/tree-sitter-scala.wasm +0 -0
- package/grammars/tree-sitter-swift.wasm +0 -0
- package/grammars/tree-sitter-zig.wasm +0 -0
- package/package.json +19 -7
- package/src/ast-analysis/engine.ts +147 -138
- package/src/ast-analysis/visitors/ast-store-visitor.ts +15 -2
- package/src/ast-analysis/visitors/complexity-visitor.ts +11 -11
- package/src/db/connection.ts +90 -59
- package/src/db/index.ts +1 -0
- package/src/db/migrations.ts +36 -32
- package/src/domain/analysis/context.ts +73 -75
- package/src/domain/analysis/dependencies.ts +78 -68
- package/src/domain/analysis/exports.ts +45 -34
- package/src/domain/analysis/fn-impact.ts +67 -64
- package/src/domain/analysis/module-map.ts +103 -8
- package/src/domain/analysis/query-helpers.ts +35 -0
- package/src/domain/graph/builder/helpers.ts +12 -6
- package/src/domain/graph/builder/incremental.ts +3 -2
- package/src/domain/graph/builder/pipeline.ts +71 -3
- package/src/domain/graph/builder/stages/build-edges.ts +10 -75
- package/src/domain/graph/builder/stages/build-structure.ts +9 -7
- package/src/domain/graph/builder/stages/collect-files.ts +2 -2
- package/src/domain/graph/builder/stages/detect-changes.ts +7 -2
- package/src/domain/graph/builder/stages/finalize.ts +159 -125
- package/src/domain/graph/builder/stages/insert-nodes.ts +32 -21
- package/src/domain/graph/builder/stages/resolve-imports.ts +3 -2
- package/src/domain/graph/resolve.ts +34 -46
- package/src/domain/graph/watcher.ts +12 -14
- package/src/domain/parser.ts +222 -97
- package/src/domain/search/search/cli-formatter.ts +121 -94
- package/src/extractors/bash.ts +97 -0
- package/src/extractors/c.ts +212 -0
- package/src/extractors/cpp.ts +298 -0
- package/src/extractors/csharp.ts +53 -56
- package/src/extractors/dart.ts +304 -0
- package/src/extractors/elixir.ts +251 -0
- package/src/extractors/go.ts +152 -134
- package/src/extractors/haskell.ts +235 -0
- package/src/extractors/hcl.ts +6 -6
- package/src/extractors/helpers.ts +93 -1
- package/src/extractors/index.ts +12 -0
- package/src/extractors/java.ts +43 -48
- package/src/extractors/javascript.ts +328 -281
- package/src/extractors/kotlin.ts +293 -0
- package/src/extractors/lua.ts +169 -0
- package/src/extractors/ocaml.ts +259 -0
- package/src/extractors/php.ts +46 -40
- package/src/extractors/python.ts +81 -104
- package/src/extractors/ruby.ts +6 -13
- package/src/extractors/rust.ts +65 -85
- package/src/extractors/scala.ts +285 -0
- package/src/extractors/swift.ts +293 -0
- package/src/extractors/zig.ts +294 -0
- package/src/features/ast.ts +10 -25
- package/src/features/audit.ts +24 -20
- package/src/features/branch-compare.ts +51 -4
- package/src/features/cfg.ts +113 -65
- package/src/features/check.ts +90 -74
- package/src/features/complexity-query.ts +181 -163
- package/src/features/complexity.ts +64 -1
- package/src/features/dataflow.ts +462 -217
- package/src/features/graph-enrichment.ts +161 -117
- package/src/features/sequence.ts +27 -4
- package/src/features/structure-query.ts +43 -4
- package/src/features/structure.ts +50 -22
- package/src/graph/algorithms/leiden/adapter.ts +126 -71
- package/src/graph/algorithms/leiden/index.ts +67 -28
- package/src/graph/algorithms/leiden/optimiser.ts +114 -105
- package/src/graph/algorithms/leiden/partition.ts +131 -98
- package/src/graph/model.ts +19 -7
- package/src/infrastructure/config.ts +60 -58
- package/src/infrastructure/registry.ts +17 -14
- package/src/mcp/server.ts +46 -37
- package/src/presentation/audit.ts +72 -67
- package/src/presentation/branch-compare.ts +54 -50
- package/src/presentation/check.ts +34 -34
- package/src/presentation/colors.ts +2 -0
- package/src/presentation/complexity.ts +39 -33
- package/src/presentation/queries-cli/exports.ts +17 -17
- package/src/presentation/queries-cli/impact.ts +30 -22
- package/src/types.ts +195 -7
|
@@ -83,50 +83,18 @@ interface FunctionEdgeRow {
|
|
|
83
83
|
edge_kind: string;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
`
|
|
95
|
-
SELECT n1.id AS source_id, n1.name AS source_name, n1.kind AS source_kind,
|
|
96
|
-
n1.file AS source_file, n1.line AS source_line, n1.role AS source_role,
|
|
97
|
-
n2.id AS target_id, n2.name AS target_name, n2.kind AS target_kind,
|
|
98
|
-
n2.file AS target_file, n2.line AS target_line, n2.role AS target_role,
|
|
99
|
-
e.kind AS edge_kind
|
|
100
|
-
FROM edges e
|
|
101
|
-
JOIN nodes n1 ON e.source_id = n1.id
|
|
102
|
-
JOIN nodes n2 ON e.target_id = n2.id
|
|
103
|
-
WHERE n1.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module', 'constant')
|
|
104
|
-
AND n2.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module', 'constant')
|
|
105
|
-
AND e.kind = 'calls'
|
|
106
|
-
AND e.confidence >= ?
|
|
107
|
-
`,
|
|
108
|
-
)
|
|
109
|
-
.all(minConf);
|
|
110
|
-
if (noTests)
|
|
111
|
-
edges = edges.filter((e) => !isTestFile(e.source_file) && !isTestFile(e.target_file));
|
|
112
|
-
|
|
113
|
-
if (cfg.filter?.kinds) {
|
|
114
|
-
const kinds = new Set(cfg.filter.kinds);
|
|
115
|
-
edges = edges.filter((e) => kinds.has(e.source_kind) && kinds.has(e.target_kind));
|
|
116
|
-
}
|
|
117
|
-
if (cfg.filter?.files) {
|
|
118
|
-
const patterns = cfg.filter.files;
|
|
119
|
-
edges = edges.filter(
|
|
120
|
-
(e) =>
|
|
121
|
-
patterns.some((p) => e.source_file.includes(p)) &&
|
|
122
|
-
patterns.some((p) => e.target_file.includes(p)),
|
|
123
|
-
);
|
|
124
|
-
}
|
|
86
|
+
type NodeInfo = {
|
|
87
|
+
id: number;
|
|
88
|
+
name: string;
|
|
89
|
+
kind: string;
|
|
90
|
+
file: string;
|
|
91
|
+
line: number;
|
|
92
|
+
role: string | null;
|
|
93
|
+
};
|
|
125
94
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
>();
|
|
95
|
+
/** Build node map from edge rows, collecting unique source/target nodes. */
|
|
96
|
+
function buildNodeMapFromEdges(edges: FunctionEdgeRow[]): Map<number, NodeInfo> {
|
|
97
|
+
const nodeMap = new Map<number, NodeInfo>();
|
|
130
98
|
for (const e of edges) {
|
|
131
99
|
if (!nodeMap.has(e.source_id)) {
|
|
132
100
|
nodeMap.set(e.source_id, {
|
|
@@ -149,17 +117,13 @@ function prepareFunctionLevelData(
|
|
|
149
117
|
});
|
|
150
118
|
}
|
|
151
119
|
}
|
|
120
|
+
return nodeMap;
|
|
121
|
+
}
|
|
152
122
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
const nodeIds = new Set(nodeMap.keys());
|
|
159
|
-
edges = edges.filter((e) => nodeIds.has(e.source_id) && nodeIds.has(e.target_id));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Complexity data
|
|
123
|
+
/** Load complexity data from function_complexity table. */
|
|
124
|
+
function loadComplexityMap(
|
|
125
|
+
db: BetterSqlite3Database,
|
|
126
|
+
): Map<number, { cognitive: number; cyclomatic: number; maintainabilityIndex: number }> {
|
|
163
127
|
const complexityMap = new Map<
|
|
164
128
|
number,
|
|
165
129
|
{ cognitive: number; cyclomatic: number; maintainabilityIndex: number }
|
|
@@ -186,19 +150,17 @@ function prepareFunctionLevelData(
|
|
|
186
150
|
} catch {
|
|
187
151
|
// table may not exist in old DBs
|
|
188
152
|
}
|
|
153
|
+
return complexityMap;
|
|
154
|
+
}
|
|
189
155
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const tgt = String(e.target_id);
|
|
196
|
-
if (src !== tgt && !fnGraph.hasEdge(src, tgt)) fnGraph.addEdge(src, tgt);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Use DB-level fan-in/fan-out (counts ALL call edges, not just visible)
|
|
156
|
+
/** Load fan-in and fan-out maps from edges table. */
|
|
157
|
+
function loadFanMaps(db: BetterSqlite3Database): {
|
|
158
|
+
fanInMap: Map<number, number>;
|
|
159
|
+
fanOutMap: Map<number, number>;
|
|
160
|
+
} {
|
|
200
161
|
const fanInMap = new Map<number, number>();
|
|
201
162
|
const fanOutMap = new Map<number, number>();
|
|
163
|
+
|
|
202
164
|
const fanInRows = db
|
|
203
165
|
.prepare<{ node_id: number; fan_in: number }>(
|
|
204
166
|
"SELECT target_id AS node_id, COUNT(*) AS fan_in FROM edges WHERE kind = 'calls' GROUP BY target_id",
|
|
@@ -213,6 +175,138 @@ function prepareFunctionLevelData(
|
|
|
213
175
|
.all();
|
|
214
176
|
for (const r of fanOutRows) fanOutMap.set(r.node_id, r.fan_out);
|
|
215
177
|
|
|
178
|
+
return { fanInMap, fanOutMap };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Build an enriched VisNode from raw node info and computed maps. */
|
|
182
|
+
function buildEnrichedVisNode(
|
|
183
|
+
n: NodeInfo,
|
|
184
|
+
complexityMap: Map<
|
|
185
|
+
number,
|
|
186
|
+
{ cognitive: number; cyclomatic: number; maintainabilityIndex: number }
|
|
187
|
+
>,
|
|
188
|
+
fanInMap: Map<number, number>,
|
|
189
|
+
fanOutMap: Map<number, number>,
|
|
190
|
+
communityMap: Map<number, number>,
|
|
191
|
+
cfg: PlotConfig,
|
|
192
|
+
): VisNode {
|
|
193
|
+
const cx = complexityMap.get(n.id) || null;
|
|
194
|
+
const fanIn = fanInMap.get(n.id) || 0;
|
|
195
|
+
const fanOut = fanOutMap.get(n.id) || 0;
|
|
196
|
+
const community = communityMap.get(n.id) ?? null;
|
|
197
|
+
const directory = path.dirname(n.file);
|
|
198
|
+
const risk: string[] = [];
|
|
199
|
+
if (n.role?.startsWith('dead')) risk.push('dead-code');
|
|
200
|
+
if (fanIn >= (cfg.riskThresholds?.highBlastRadius ?? 10)) risk.push('high-blast-radius');
|
|
201
|
+
if (cx && cx.maintainabilityIndex < (cfg.riskThresholds?.lowMI ?? 40)) risk.push('low-mi');
|
|
202
|
+
|
|
203
|
+
const color: string =
|
|
204
|
+
cfg.colorBy === 'role' && n.role
|
|
205
|
+
? cfg.roleColors?.[n.role] ||
|
|
206
|
+
(DEFAULT_ROLE_COLORS as Record<string, string>)[n.role] ||
|
|
207
|
+
'#ccc'
|
|
208
|
+
: cfg.colorBy === 'community' && community !== null
|
|
209
|
+
? COMMUNITY_COLORS[community % COMMUNITY_COLORS.length] || '#ccc'
|
|
210
|
+
: cfg.nodeColors?.[n.kind] ||
|
|
211
|
+
(DEFAULT_NODE_COLORS as Record<string, string>)[n.kind] ||
|
|
212
|
+
'#ccc';
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
id: n.id,
|
|
216
|
+
label: n.name,
|
|
217
|
+
title: `${n.file}:${n.line} (${n.kind}${n.role ? `, ${n.role}` : ''})`,
|
|
218
|
+
color,
|
|
219
|
+
kind: n.kind,
|
|
220
|
+
role: n.role || '',
|
|
221
|
+
file: n.file,
|
|
222
|
+
line: n.line,
|
|
223
|
+
community,
|
|
224
|
+
cognitive: cx?.cognitive ?? null,
|
|
225
|
+
cyclomatic: cx?.cyclomatic ?? null,
|
|
226
|
+
maintainabilityIndex: cx?.maintainabilityIndex ?? null,
|
|
227
|
+
fanIn,
|
|
228
|
+
fanOut,
|
|
229
|
+
directory,
|
|
230
|
+
risk,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Select seed node IDs based on configured strategy. */
|
|
235
|
+
function selectSeedNodes(visNodes: VisNode[], cfg: PlotConfig): (number | string)[] {
|
|
236
|
+
if (cfg.seedStrategy === 'top-fanin') {
|
|
237
|
+
const sorted = [...visNodes].sort((a, b) => b.fanIn - a.fanIn);
|
|
238
|
+
return sorted.slice(0, cfg.seedCount || 30).map((n) => n.id);
|
|
239
|
+
}
|
|
240
|
+
if (cfg.seedStrategy === 'entry') {
|
|
241
|
+
return visNodes.filter((n) => n.role === 'entry').map((n) => n.id);
|
|
242
|
+
}
|
|
243
|
+
return visNodes.map((n) => n.id);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function prepareFunctionLevelData(
|
|
247
|
+
db: BetterSqlite3Database,
|
|
248
|
+
noTests: boolean,
|
|
249
|
+
minConf: number,
|
|
250
|
+
cfg: PlotConfig,
|
|
251
|
+
): GraphData {
|
|
252
|
+
let edges = db
|
|
253
|
+
.prepare<FunctionEdgeRow>(
|
|
254
|
+
`
|
|
255
|
+
SELECT n1.id AS source_id, n1.name AS source_name, n1.kind AS source_kind,
|
|
256
|
+
n1.file AS source_file, n1.line AS source_line, n1.role AS source_role,
|
|
257
|
+
n2.id AS target_id, n2.name AS target_name, n2.kind AS target_kind,
|
|
258
|
+
n2.file AS target_file, n2.line AS target_line, n2.role AS target_role,
|
|
259
|
+
e.kind AS edge_kind
|
|
260
|
+
FROM edges e
|
|
261
|
+
JOIN nodes n1 ON e.source_id = n1.id
|
|
262
|
+
JOIN nodes n2 ON e.target_id = n2.id
|
|
263
|
+
WHERE n1.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module', 'constant')
|
|
264
|
+
AND n2.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module', 'constant')
|
|
265
|
+
AND e.kind = 'calls'
|
|
266
|
+
AND e.confidence >= ?
|
|
267
|
+
`,
|
|
268
|
+
)
|
|
269
|
+
.all(minConf);
|
|
270
|
+
if (noTests)
|
|
271
|
+
edges = edges.filter((e) => !isTestFile(e.source_file) && !isTestFile(e.target_file));
|
|
272
|
+
|
|
273
|
+
if (cfg.filter?.kinds) {
|
|
274
|
+
const kinds = new Set(cfg.filter.kinds);
|
|
275
|
+
edges = edges.filter((e) => kinds.has(e.source_kind) && kinds.has(e.target_kind));
|
|
276
|
+
}
|
|
277
|
+
if (cfg.filter?.files) {
|
|
278
|
+
const patterns = cfg.filter.files;
|
|
279
|
+
edges = edges.filter(
|
|
280
|
+
(e) =>
|
|
281
|
+
patterns.some((p) => e.source_file.includes(p)) &&
|
|
282
|
+
patterns.some((p) => e.target_file.includes(p)),
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const nodeMap = buildNodeMapFromEdges(edges);
|
|
287
|
+
|
|
288
|
+
if (cfg.filter?.roles) {
|
|
289
|
+
const roles = new Set(cfg.filter.roles);
|
|
290
|
+
for (const [id, n] of nodeMap) {
|
|
291
|
+
if (n.role === null || !roles.has(n.role)) nodeMap.delete(id);
|
|
292
|
+
}
|
|
293
|
+
const nodeIds = new Set(nodeMap.keys());
|
|
294
|
+
edges = edges.filter((e) => nodeIds.has(e.source_id) && nodeIds.has(e.target_id));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const complexityMap = loadComplexityMap(db);
|
|
298
|
+
|
|
299
|
+
// Build CodeGraph for Louvain community detection
|
|
300
|
+
const fnGraph = new CodeGraph();
|
|
301
|
+
for (const [id] of nodeMap) fnGraph.addNode(String(id));
|
|
302
|
+
for (const e of edges) {
|
|
303
|
+
const src = String(e.source_id);
|
|
304
|
+
const tgt = String(e.target_id);
|
|
305
|
+
if (src !== tgt && !fnGraph.hasEdge(src, tgt)) fnGraph.addEdge(src, tgt);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const { fanInMap, fanOutMap } = loadFanMaps(db);
|
|
309
|
+
|
|
216
310
|
// Communities (Louvain) via graph subsystem
|
|
217
311
|
const communityMap = new Map<number, number>();
|
|
218
312
|
if (nodeMap.size > 0) {
|
|
@@ -224,48 +318,9 @@ function prepareFunctionLevelData(
|
|
|
224
318
|
}
|
|
225
319
|
}
|
|
226
320
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const fanIn = fanInMap.get(n.id) || 0;
|
|
231
|
-
const fanOut = fanOutMap.get(n.id) || 0;
|
|
232
|
-
const community = communityMap.get(n.id) ?? null;
|
|
233
|
-
const directory = path.dirname(n.file);
|
|
234
|
-
const risk: string[] = [];
|
|
235
|
-
if (n.role?.startsWith('dead')) risk.push('dead-code');
|
|
236
|
-
if (fanIn >= (cfg.riskThresholds?.highBlastRadius ?? 10)) risk.push('high-blast-radius');
|
|
237
|
-
if (cx && cx.maintainabilityIndex < (cfg.riskThresholds?.lowMI ?? 40)) risk.push('low-mi');
|
|
238
|
-
|
|
239
|
-
const color: string =
|
|
240
|
-
cfg.colorBy === 'role' && n.role
|
|
241
|
-
? cfg.roleColors?.[n.role] ||
|
|
242
|
-
(DEFAULT_ROLE_COLORS as Record<string, string>)[n.role] ||
|
|
243
|
-
'#ccc'
|
|
244
|
-
: cfg.colorBy === 'community' && community !== null
|
|
245
|
-
? COMMUNITY_COLORS[community % COMMUNITY_COLORS.length] || '#ccc'
|
|
246
|
-
: cfg.nodeColors?.[n.kind] ||
|
|
247
|
-
(DEFAULT_NODE_COLORS as Record<string, string>)[n.kind] ||
|
|
248
|
-
'#ccc';
|
|
249
|
-
|
|
250
|
-
return {
|
|
251
|
-
id: n.id,
|
|
252
|
-
label: n.name,
|
|
253
|
-
title: `${n.file}:${n.line} (${n.kind}${n.role ? `, ${n.role}` : ''})`,
|
|
254
|
-
color,
|
|
255
|
-
kind: n.kind,
|
|
256
|
-
role: n.role || '',
|
|
257
|
-
file: n.file,
|
|
258
|
-
line: n.line,
|
|
259
|
-
community,
|
|
260
|
-
cognitive: cx?.cognitive ?? null,
|
|
261
|
-
cyclomatic: cx?.cyclomatic ?? null,
|
|
262
|
-
maintainabilityIndex: cx?.maintainabilityIndex ?? null,
|
|
263
|
-
fanIn,
|
|
264
|
-
fanOut,
|
|
265
|
-
directory,
|
|
266
|
-
risk,
|
|
267
|
-
};
|
|
268
|
-
});
|
|
321
|
+
const visNodes: VisNode[] = [...nodeMap.values()].map((n) =>
|
|
322
|
+
buildEnrichedVisNode(n, complexityMap, fanInMap, fanOutMap, communityMap, cfg),
|
|
323
|
+
);
|
|
269
324
|
|
|
270
325
|
const visEdges: VisEdge[] = edges.map((e, i) => ({
|
|
271
326
|
id: `e${i}`,
|
|
@@ -273,18 +328,7 @@ function prepareFunctionLevelData(
|
|
|
273
328
|
to: e.target_id,
|
|
274
329
|
}));
|
|
275
330
|
|
|
276
|
-
|
|
277
|
-
let seedNodeIds: (number | string)[];
|
|
278
|
-
if (cfg.seedStrategy === 'top-fanin') {
|
|
279
|
-
const sorted = [...visNodes].sort((a, b) => b.fanIn - a.fanIn);
|
|
280
|
-
seedNodeIds = sorted.slice(0, cfg.seedCount || 30).map((n) => n.id);
|
|
281
|
-
} else if (cfg.seedStrategy === 'entry') {
|
|
282
|
-
seedNodeIds = visNodes.filter((n) => n.role === 'entry').map((n) => n.id);
|
|
283
|
-
} else {
|
|
284
|
-
seedNodeIds = visNodes.map((n) => n.id);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return { nodes: visNodes, edges: visEdges, seedNodeIds };
|
|
331
|
+
return { nodes: visNodes, edges: visEdges, seedNodeIds: selectSeedNodes(visNodes, cfg) };
|
|
288
332
|
}
|
|
289
333
|
|
|
290
334
|
interface FileLevelEdge {
|
package/src/features/sequence.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
1
2
|
import { openRepo, type Repository } from '../db/index.js';
|
|
2
3
|
import { SqliteRepository } from '../db/repository/sqlite-repository.js';
|
|
3
4
|
import { findMatchingNodes } from '../domain/queries.js';
|
|
4
5
|
import { loadConfig } from '../infrastructure/config.js';
|
|
5
6
|
import { isTestFile } from '../infrastructure/test-filter.js';
|
|
6
7
|
import { paginateResult } from '../shared/paginate.js';
|
|
7
|
-
import type { CodegraphConfig, NodeRowWithFanIn } from '../types.js';
|
|
8
|
+
import type { BetterSqlite3Database, CodegraphConfig, NodeRowWithFanIn } from '../types.js';
|
|
8
9
|
import { FRAMEWORK_ENTRY_PREFIXES } from './structure.js';
|
|
9
10
|
|
|
10
11
|
// ─── Alias generation ────────────────────────────────────────────────
|
|
@@ -150,12 +151,34 @@ function annotateDataflow(
|
|
|
150
151
|
repo: Repository,
|
|
151
152
|
messages: SequenceMessage[],
|
|
152
153
|
idToNode: Map<number, { id: number; name: string; file: string; kind: string; line: number }>,
|
|
154
|
+
dbPath?: string,
|
|
153
155
|
): void {
|
|
154
156
|
const hasTable = repo.hasDataflowTable();
|
|
157
|
+
if (!hasTable) return;
|
|
158
|
+
|
|
159
|
+
let db: BetterSqlite3Database;
|
|
160
|
+
let ownDb = false;
|
|
161
|
+
if (repo instanceof SqliteRepository) {
|
|
162
|
+
db = repo.db;
|
|
163
|
+
} else if (dbPath) {
|
|
164
|
+
db = new Database(dbPath, { readonly: true }) as unknown as BetterSqlite3Database;
|
|
165
|
+
ownDb = true;
|
|
166
|
+
} else {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
155
169
|
|
|
156
|
-
|
|
170
|
+
try {
|
|
171
|
+
_annotateDataflowImpl(db, messages, idToNode);
|
|
172
|
+
} finally {
|
|
173
|
+
if (ownDb) db.close();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
157
176
|
|
|
158
|
-
|
|
177
|
+
function _annotateDataflowImpl(
|
|
178
|
+
db: BetterSqlite3Database,
|
|
179
|
+
messages: SequenceMessage[],
|
|
180
|
+
idToNode: Map<number, { id: number; name: string; file: string; kind: string; line: number }>,
|
|
181
|
+
): void {
|
|
159
182
|
const nodeByNameFile = new Map<string, { id: number; name: string; file: string }>();
|
|
160
183
|
for (const n of idToNode.values()) {
|
|
161
184
|
nodeByNameFile.set(`${n.name}|${n.file}`, n);
|
|
@@ -308,7 +331,7 @@ export function sequenceData(
|
|
|
308
331
|
);
|
|
309
332
|
|
|
310
333
|
if (opts.dataflow && messages.length > 0) {
|
|
311
|
-
annotateDataflow(repo, messages, idToNode);
|
|
334
|
+
annotateDataflow(repo, messages, idToNode, dbPath);
|
|
312
335
|
}
|
|
313
336
|
|
|
314
337
|
messages.sort((a, b) => {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* role classification).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { openReadonlyOrFail, testFilterSQL } from '../db/index.js';
|
|
10
|
+
import { openReadonlyOrFail, openReadonlyWithNative, testFilterSQL } from '../db/index.js';
|
|
11
11
|
import { loadConfig } from '../infrastructure/config.js';
|
|
12
12
|
import { isTestFile } from '../infrastructure/test-filter.js';
|
|
13
13
|
import { normalizePath } from '../shared/constants.js';
|
|
@@ -221,7 +221,7 @@ export function hotspotsData(
|
|
|
221
221
|
limit: number;
|
|
222
222
|
hotspots: unknown[];
|
|
223
223
|
} {
|
|
224
|
-
const db =
|
|
224
|
+
const { db, nativeDb, close } = openReadonlyWithNative(customDbPath);
|
|
225
225
|
try {
|
|
226
226
|
const metric = opts.metric || 'fan-in';
|
|
227
227
|
const level = opts.level || 'file';
|
|
@@ -230,6 +230,46 @@ export function hotspotsData(
|
|
|
230
230
|
|
|
231
231
|
const kind = level === 'directory' ? 'directory' : 'file';
|
|
232
232
|
|
|
233
|
+
const mapRow = (r: {
|
|
234
|
+
name: string;
|
|
235
|
+
kind: string;
|
|
236
|
+
lineCount: number | null;
|
|
237
|
+
symbolCount: number | null;
|
|
238
|
+
importCount: number | null;
|
|
239
|
+
exportCount: number | null;
|
|
240
|
+
fanIn: number | null;
|
|
241
|
+
fanOut: number | null;
|
|
242
|
+
cohesion: number | null;
|
|
243
|
+
fileCount: number | null;
|
|
244
|
+
}) => ({
|
|
245
|
+
name: r.name,
|
|
246
|
+
kind: r.kind,
|
|
247
|
+
lineCount: r.lineCount,
|
|
248
|
+
symbolCount: r.symbolCount,
|
|
249
|
+
importCount: r.importCount,
|
|
250
|
+
exportCount: r.exportCount,
|
|
251
|
+
fanIn: r.fanIn,
|
|
252
|
+
fanOut: r.fanOut,
|
|
253
|
+
cohesion: r.cohesion,
|
|
254
|
+
fileCount: r.fileCount,
|
|
255
|
+
density:
|
|
256
|
+
(r.fileCount ?? 0) > 0
|
|
257
|
+
? (r.symbolCount || 0) / (r.fileCount ?? 1)
|
|
258
|
+
: (r.lineCount ?? 0) > 0
|
|
259
|
+
? (r.symbolCount || 0) / (r.lineCount ?? 1)
|
|
260
|
+
: 0,
|
|
261
|
+
coupling: (r.fanIn || 0) + (r.fanOut || 0),
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// ── Native fast path: single query instead of 4 eagerly prepared ──
|
|
265
|
+
if (nativeDb?.getHotspots) {
|
|
266
|
+
const rows = nativeDb.getHotspots(kind, metric, noTests, limit);
|
|
267
|
+
const hotspots = rows.map(mapRow);
|
|
268
|
+
const base = { metric, level, limit, hotspots };
|
|
269
|
+
return paginateResult(base, 'hotspots', { limit: opts.limit, offset: opts.offset });
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ── JS fallback ───────────────────────────────────────────────────
|
|
233
273
|
const testFilter = testFilterSQL('n.name', noTests && kind === 'file');
|
|
234
274
|
|
|
235
275
|
const HOTSPOT_QUERIES: Record<string, { all(...params: unknown[]): HotspotRow[] }> = {
|
|
@@ -256,7 +296,6 @@ export function hotspotsData(
|
|
|
256
296
|
};
|
|
257
297
|
|
|
258
298
|
const stmt = HOTSPOT_QUERIES[metric] ?? HOTSPOT_QUERIES['fan-in'];
|
|
259
|
-
// stmt is always defined: metric is a valid key or the fallback is a concrete property
|
|
260
299
|
const rows = stmt!.all(kind, limit);
|
|
261
300
|
|
|
262
301
|
const hotspots = rows.map((r) => ({
|
|
@@ -282,7 +321,7 @@ export function hotspotsData(
|
|
|
282
321
|
const base = { metric, level, limit, hotspots };
|
|
283
322
|
return paginateResult(base, 'hotspots', { limit: opts.limit, offset: opts.offset });
|
|
284
323
|
} finally {
|
|
285
|
-
|
|
324
|
+
close();
|
|
286
325
|
}
|
|
287
326
|
}
|
|
288
327
|
|
|
@@ -199,14 +199,11 @@ function computeFileMetrics(
|
|
|
199
199
|
})();
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
upsertMetric: SqliteStatement,
|
|
205
|
-
getNodeIdStmt: NodeIdStmt,
|
|
206
|
-
fileSymbols: Map<string, FileSymbolData>,
|
|
202
|
+
/** Map each directory to the files it transitively contains. */
|
|
203
|
+
function buildDirFilesMap(
|
|
207
204
|
allDirs: Set<string>,
|
|
208
|
-
|
|
209
|
-
):
|
|
205
|
+
fileSymbols: Map<string, FileSymbolData>,
|
|
206
|
+
): Map<string, string[]> {
|
|
210
207
|
const dirFiles = new Map<string, string[]>();
|
|
211
208
|
for (const dir of allDirs) {
|
|
212
209
|
dirFiles.set(dir, []);
|
|
@@ -220,7 +217,11 @@ function computeDirectoryMetrics(
|
|
|
220
217
|
d = normalizePath(path.dirname(d));
|
|
221
218
|
}
|
|
222
219
|
}
|
|
220
|
+
return dirFiles;
|
|
221
|
+
}
|
|
223
222
|
|
|
223
|
+
/** Build reverse map: file -> set of ancestor directories. */
|
|
224
|
+
function buildFileToAncestorDirs(dirFiles: Map<string, string[]>): Map<string, Set<string>> {
|
|
224
225
|
const fileToAncestorDirs = new Map<string, Set<string>>();
|
|
225
226
|
for (const [dir, files] of dirFiles) {
|
|
226
227
|
for (const f of files) {
|
|
@@ -228,7 +229,15 @@ function computeDirectoryMetrics(
|
|
|
228
229
|
fileToAncestorDirs.get(f)?.add(dir);
|
|
229
230
|
}
|
|
230
231
|
}
|
|
232
|
+
return fileToAncestorDirs;
|
|
233
|
+
}
|
|
231
234
|
|
|
235
|
+
/** Count intra-directory, fan-in, and fan-out edges per directory. */
|
|
236
|
+
function countDirectoryEdges(
|
|
237
|
+
allDirs: Set<string>,
|
|
238
|
+
importEdges: ImportEdge[],
|
|
239
|
+
fileToAncestorDirs: Map<string, Set<string>>,
|
|
240
|
+
): Map<string, { intra: number; fanIn: number; fanOut: number }> {
|
|
232
241
|
const dirEdgeCounts = new Map<string, { intra: number; fanIn: number; fanOut: number }>();
|
|
233
242
|
for (const dir of allDirs) {
|
|
234
243
|
dirEdgeCounts.set(dir, { intra: 0, fanIn: 0, fanOut: 0 });
|
|
@@ -258,6 +267,39 @@ function computeDirectoryMetrics(
|
|
|
258
267
|
}
|
|
259
268
|
}
|
|
260
269
|
}
|
|
270
|
+
return dirEdgeCounts;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Count unique symbols in a list of files. */
|
|
274
|
+
function countSymbolsInFiles(files: string[], fileSymbols: Map<string, FileSymbolData>): number {
|
|
275
|
+
let symbolCount = 0;
|
|
276
|
+
for (const f of files) {
|
|
277
|
+
const sym = fileSymbols.get(f);
|
|
278
|
+
if (sym) {
|
|
279
|
+
const seen = new Set<string>();
|
|
280
|
+
for (const d of sym.definitions) {
|
|
281
|
+
const key = `${d.name}|${d.kind}|${d.line}`;
|
|
282
|
+
if (!seen.has(key)) {
|
|
283
|
+
seen.add(key);
|
|
284
|
+
symbolCount++;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return symbolCount;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function computeDirectoryMetrics(
|
|
293
|
+
db: BetterSqlite3Database,
|
|
294
|
+
upsertMetric: SqliteStatement,
|
|
295
|
+
getNodeIdStmt: NodeIdStmt,
|
|
296
|
+
fileSymbols: Map<string, FileSymbolData>,
|
|
297
|
+
allDirs: Set<string>,
|
|
298
|
+
importEdges: ImportEdge[],
|
|
299
|
+
): void {
|
|
300
|
+
const dirFiles = buildDirFilesMap(allDirs, fileSymbols);
|
|
301
|
+
const fileToAncestorDirs = buildFileToAncestorDirs(dirFiles);
|
|
302
|
+
const dirEdgeCounts = countDirectoryEdges(allDirs, importEdges, fileToAncestorDirs);
|
|
261
303
|
|
|
262
304
|
db.transaction(() => {
|
|
263
305
|
for (const [dir, files] of dirFiles) {
|
|
@@ -265,21 +307,7 @@ function computeDirectoryMetrics(
|
|
|
265
307
|
if (!dirRow) continue;
|
|
266
308
|
|
|
267
309
|
const fileCount = files.length;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
for (const f of files) {
|
|
271
|
-
const sym = fileSymbols.get(f);
|
|
272
|
-
if (sym) {
|
|
273
|
-
const seen = new Set<string>();
|
|
274
|
-
for (const d of sym.definitions) {
|
|
275
|
-
const key = `${d.name}|${d.kind}|${d.line}`;
|
|
276
|
-
if (!seen.has(key)) {
|
|
277
|
-
seen.add(key);
|
|
278
|
-
symbolCount++;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
310
|
+
const symbolCount = countSymbolsInFiles(files, fileSymbols);
|
|
283
311
|
|
|
284
312
|
const counts = dirEdgeCounts.get(dir) || { intra: 0, fanIn: 0, fanOut: 0 };
|
|
285
313
|
const totalEdges = counts.intra + counts.fanIn + counts.fanOut;
|