@optave/codegraph 3.7.0 → 3.8.1
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 +28 -16
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +195 -30
- 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/rules/javascript.d.ts.map +1 -1
- package/dist/ast-analysis/rules/javascript.js +0 -1
- package/dist/ast-analysis/rules/javascript.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 +52 -129
- 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 +32 -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/ast.js +2 -2
- package/dist/cli/commands/ast.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 +10 -0
- package/dist/db/repository/base.d.ts.map +1 -1
- package/dist/db/repository/base.js +17 -0
- package/dist/db/repository/base.js.map +1 -1
- package/dist/db/repository/native-repository.d.ts +6 -1
- package/dist/db/repository/native-repository.d.ts.map +1 -1
- package/dist/db/repository/native-repository.js +77 -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 +3 -0
- package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
- package/dist/db/repository/sqlite-repository.js +26 -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 +53 -52
- 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 +315 -43
- 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 +106 -1
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.js +17 -5
- 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 +99 -51
- 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 +34 -7
- 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 +50 -26
- 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 +1 -6
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +101 -32
- 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 +18 -5
- 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/clojure.d.ts +12 -0
- package/dist/extractors/clojure.d.ts.map +1 -0
- package/dist/extractors/clojure.js +245 -0
- package/dist/extractors/clojure.js.map +1 -0
- package/dist/extractors/cuda.d.ts +11 -0
- package/dist/extractors/cuda.d.ts.map +1 -0
- package/dist/extractors/cuda.js +302 -0
- package/dist/extractors/cuda.js.map +1 -0
- package/dist/extractors/erlang.d.ts +14 -0
- package/dist/extractors/erlang.d.ts.map +1 -0
- package/dist/extractors/erlang.js +239 -0
- package/dist/extractors/erlang.js.map +1 -0
- package/dist/extractors/fsharp.d.ts +13 -0
- package/dist/extractors/fsharp.d.ts.map +1 -0
- package/dist/extractors/fsharp.js +218 -0
- package/dist/extractors/fsharp.js.map +1 -0
- package/dist/extractors/gleam.d.ts +14 -0
- package/dist/extractors/gleam.d.ts.map +1 -0
- package/dist/extractors/gleam.js +229 -0
- package/dist/extractors/gleam.js.map +1 -0
- package/dist/extractors/go.js +36 -33
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/groovy.d.ts +10 -0
- package/dist/extractors/groovy.d.ts.map +1 -0
- package/dist/extractors/groovy.js +304 -0
- package/dist/extractors/groovy.js.map +1 -0
- 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/index.d.ts +11 -0
- package/dist/extractors/index.d.ts.map +1 -1
- package/dist/extractors/index.js +11 -0
- package/dist/extractors/index.js.map +1 -1
- package/dist/extractors/java.js +58 -46
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.js +46 -45
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/julia.d.ts +16 -0
- package/dist/extractors/julia.d.ts.map +1 -0
- package/dist/extractors/julia.js +287 -0
- package/dist/extractors/julia.js.map +1 -0
- package/dist/extractors/kotlin.js +84 -78
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/objc.d.ts +9 -0
- package/dist/extractors/objc.d.ts.map +1 -0
- package/dist/extractors/objc.js +406 -0
- package/dist/extractors/objc.js.map +1 -0
- package/dist/extractors/ocaml.js +74 -0
- package/dist/extractors/ocaml.js.map +1 -1
- package/dist/extractors/python.js +29 -24
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/r.d.ts +13 -0
- package/dist/extractors/r.d.ts.map +1 -0
- package/dist/extractors/r.js +251 -0
- package/dist/extractors/r.js.map +1 -0
- package/dist/extractors/rust.js +41 -32
- package/dist/extractors/rust.js.map +1 -1
- package/dist/extractors/solidity.d.ts +9 -0
- package/dist/extractors/solidity.d.ts.map +1 -0
- package/dist/extractors/solidity.js +365 -0
- package/dist/extractors/solidity.js.map +1 -0
- package/dist/extractors/swift.js +83 -81
- package/dist/extractors/swift.js.map +1 -1
- package/dist/extractors/verilog.d.ts +9 -0
- package/dist/extractors/verilog.d.ts.map +1 -0
- package/dist/extractors/verilog.js +286 -0
- package/dist/extractors/verilog.js.map +1 -0
- 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 +84 -83
- 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/bfs.d.ts +2 -0
- package/dist/graph/algorithms/bfs.d.ts.map +1 -1
- package/dist/graph/algorithms/bfs.js +27 -0
- package/dist/graph/algorithms/bfs.js.map +1 -1
- package/dist/graph/algorithms/centrality.d.ts +2 -0
- package/dist/graph/algorithms/centrality.d.ts.map +1 -1
- package/dist/graph/algorithms/centrality.js +28 -0
- package/dist/graph/algorithms/centrality.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/algorithms/louvain.d.ts +3 -4
- package/dist/graph/algorithms/louvain.d.ts.map +1 -1
- package/dist/graph/algorithms/louvain.js +29 -0
- package/dist/graph/algorithms/louvain.js.map +1 -1
- package/dist/graph/algorithms/shortest-path.d.ts +2 -0
- package/dist/graph/algorithms/shortest-path.d.ts.map +1 -1
- package/dist/graph/algorithms/shortest-path.js +18 -1
- package/dist/graph/algorithms/shortest-path.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 +129 -3
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-clojure.wasm +0 -0
- package/grammars/tree-sitter-cuda.wasm +0 -0
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/grammars/tree-sitter-fsharp.wasm +0 -0
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/grammars/tree-sitter-groovy.wasm +0 -0
- package/grammars/tree-sitter-julia.wasm +0 -0
- package/grammars/tree-sitter-objc.wasm +0 -0
- package/grammars/tree-sitter-ocaml_interface.wasm +0 -0
- package/grammars/tree-sitter-r.wasm +0 -0
- package/grammars/tree-sitter-solidity.wasm +0 -0
- package/grammars/tree-sitter-verilog.wasm +0 -0
- package/package.json +18 -7
- package/src/ast-analysis/engine.ts +245 -42
- package/src/ast-analysis/metrics.ts +33 -11
- package/src/ast-analysis/rules/javascript.ts +0 -1
- 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 +49 -119
- package/src/ast-analysis/visitors/complexity-visitor.ts +35 -40
- package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
- package/src/cli/commands/ast.ts +2 -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 +20 -0
- package/src/db/repository/native-repository.ts +79 -1
- package/src/db/repository/nodes.ts +13 -8
- package/src/db/repository/sqlite-repository.ts +29 -0
- package/src/domain/analysis/brief.ts +15 -25
- package/src/domain/analysis/context.ts +17 -10
- package/src/domain/analysis/dependencies.ts +67 -76
- 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 +366 -41
- package/src/domain/graph/builder/stages/build-edges.ts +162 -1
- package/src/domain/graph/builder/stages/collect-files.ts +18 -7
- package/src/domain/graph/builder/stages/detect-changes.ts +110 -56
- package/src/domain/graph/builder/stages/finalize.ts +41 -11
- package/src/domain/graph/builder/stages/insert-nodes.ts +75 -39
- 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 +122 -28
- package/src/domain/search/generator.ts +1 -1
- package/src/domain/search/models.ts +17 -4
- package/src/domain/search/search/hybrid.ts +69 -51
- package/src/extractors/clojure.ts +273 -0
- package/src/extractors/cuda.ts +316 -0
- package/src/extractors/erlang.ts +252 -0
- package/src/extractors/fsharp.ts +253 -0
- package/src/extractors/gleam.ts +246 -0
- package/src/extractors/go.ts +43 -33
- package/src/extractors/groovy.ts +332 -0
- package/src/extractors/helpers.ts +37 -23
- package/src/extractors/index.ts +11 -0
- package/src/extractors/java.ts +66 -47
- package/src/extractors/javascript.ts +45 -46
- package/src/extractors/julia.ts +318 -0
- package/src/extractors/kotlin.ts +84 -77
- package/src/extractors/objc.ts +431 -0
- package/src/extractors/ocaml.ts +78 -0
- package/src/extractors/python.ts +31 -25
- package/src/extractors/r.ts +253 -0
- package/src/extractors/rust.ts +37 -29
- package/src/extractors/solidity.ts +394 -0
- package/src/extractors/swift.ts +81 -80
- package/src/extractors/verilog.ts +315 -0
- package/src/extractors/zig.ts +58 -61
- package/src/features/ast.ts +131 -112
- 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/bfs.ts +34 -0
- package/src/graph/algorithms/centrality.ts +30 -0
- package/src/graph/algorithms/leiden/optimiser.ts +99 -55
- package/src/graph/algorithms/leiden/partition.ts +359 -294
- package/src/graph/algorithms/louvain.ts +31 -4
- package/src/graph/algorithms/shortest-path.ts +20 -1
- 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 +138 -3
|
@@ -15,10 +15,13 @@
|
|
|
15
15
|
* output). This eliminates redundant tree traversals per file.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import fs from 'node:fs';
|
|
18
19
|
import path from 'node:path';
|
|
19
20
|
import { performance } from 'node:perf_hooks';
|
|
20
21
|
import { bulkNodeIdsByFile } from '../db/index.js';
|
|
21
22
|
import { debug } from '../infrastructure/logger.js';
|
|
23
|
+
import { loadNative } from '../infrastructure/native.js';
|
|
24
|
+
import { toErrorMessage } from '../shared/errors.js';
|
|
22
25
|
import type {
|
|
23
26
|
AnalysisOpts,
|
|
24
27
|
AnalysisTiming,
|
|
@@ -30,6 +33,9 @@ import type {
|
|
|
30
33
|
Definition,
|
|
31
34
|
EngineOpts,
|
|
32
35
|
ExtractorOutput,
|
|
36
|
+
NativeAddon,
|
|
37
|
+
NativeFunctionCfgResult,
|
|
38
|
+
NativeFunctionComplexityResult,
|
|
33
39
|
TreeSitterNode,
|
|
34
40
|
Visitor,
|
|
35
41
|
WalkOptions,
|
|
@@ -95,6 +101,214 @@ async function getParserModule(): Promise<typeof import('../domain/parser.js')>
|
|
|
95
101
|
return _parserModule;
|
|
96
102
|
}
|
|
97
103
|
|
|
104
|
+
// ─── Native standalone analysis ─────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
interface NativeAnalysisNeeds {
|
|
107
|
+
complexity: boolean;
|
|
108
|
+
cfg: boolean;
|
|
109
|
+
dataflow: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Try native Rust analysis for files missing complexity/CFG/dataflow data.
|
|
114
|
+
* Reads source from disk, calls the native standalone functions, and stores
|
|
115
|
+
* results directly on definitions/symbols.
|
|
116
|
+
*/
|
|
117
|
+
|
|
118
|
+
/** Determine which native analyses a file still needs. */
|
|
119
|
+
function detectNativeNeeds(
|
|
120
|
+
symbols: ExtractorOutput,
|
|
121
|
+
ext: string,
|
|
122
|
+
langId: string,
|
|
123
|
+
opts: { doComplexity: boolean; doCfg: boolean; doDataflow: boolean },
|
|
124
|
+
): NativeAnalysisNeeds {
|
|
125
|
+
const defs = symbols.definitions || [];
|
|
126
|
+
const langSupportsComplexity = COMPLEXITY_EXTENSIONS.has(ext) || COMPLEXITY_RULES.has(langId);
|
|
127
|
+
const langSupportsCfg = CFG_EXTENSIONS.has(ext) || CFG_RULES.has(langId);
|
|
128
|
+
const langSupportsDataflow = DATAFLOW_EXTENSIONS.has(ext) || DATAFLOW_RULES.has(langId);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
complexity:
|
|
132
|
+
opts.doComplexity &&
|
|
133
|
+
langSupportsComplexity &&
|
|
134
|
+
defs.some((d) => hasFuncBody(d) && !d.complexity),
|
|
135
|
+
cfg:
|
|
136
|
+
opts.doCfg &&
|
|
137
|
+
langSupportsCfg &&
|
|
138
|
+
defs.some((d) => hasFuncBody(d) && d.cfg !== null && !Array.isArray(d.cfg?.blocks)),
|
|
139
|
+
dataflow: opts.doDataflow && !symbols.dataflow && langSupportsDataflow,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Run native analysis passes for a single file. */
|
|
144
|
+
function runNativeFileAnalysis(
|
|
145
|
+
native: NativeAddon,
|
|
146
|
+
source: string,
|
|
147
|
+
absPath: string,
|
|
148
|
+
relPath: string,
|
|
149
|
+
langId: string,
|
|
150
|
+
symbols: ExtractorOutput,
|
|
151
|
+
needs: NativeAnalysisNeeds,
|
|
152
|
+
): void {
|
|
153
|
+
const defs = symbols.definitions || [];
|
|
154
|
+
|
|
155
|
+
if (needs.complexity && native.analyzeComplexity) {
|
|
156
|
+
try {
|
|
157
|
+
const results = native.analyzeComplexity(source, absPath, langId);
|
|
158
|
+
storeNativeComplexityResults(results, defs);
|
|
159
|
+
} catch (err: unknown) {
|
|
160
|
+
debug(`native analyzeComplexity failed for ${relPath}: ${toErrorMessage(err)}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (needs.cfg && native.buildCfgAnalysis) {
|
|
165
|
+
try {
|
|
166
|
+
const results = native.buildCfgAnalysis(source, absPath, langId);
|
|
167
|
+
storeNativeCfgResults(results, defs);
|
|
168
|
+
} catch (err: unknown) {
|
|
169
|
+
debug(`native buildCfgAnalysis failed for ${relPath}: ${toErrorMessage(err)}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (needs.dataflow && native.extractDataflowAnalysis) {
|
|
174
|
+
try {
|
|
175
|
+
const result = native.extractDataflowAnalysis(source, absPath, langId);
|
|
176
|
+
if (result) symbols.dataflow = result;
|
|
177
|
+
} catch (err: unknown) {
|
|
178
|
+
debug(`native extractDataflowAnalysis failed for ${relPath}: ${toErrorMessage(err)}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function runNativeAnalysis(
|
|
184
|
+
native: NativeAddon,
|
|
185
|
+
fileSymbols: Map<string, ExtractorOutput>,
|
|
186
|
+
rootDir: string,
|
|
187
|
+
opts: AnalysisOpts,
|
|
188
|
+
extToLang: Map<string, string>,
|
|
189
|
+
): void {
|
|
190
|
+
const optsFlags = {
|
|
191
|
+
doComplexity: opts.complexity !== false,
|
|
192
|
+
doCfg: opts.cfg !== false,
|
|
193
|
+
doDataflow: opts.dataflow !== false,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
for (const [relPath, symbols] of fileSymbols) {
|
|
197
|
+
if (symbols._tree) continue;
|
|
198
|
+
const ext = path.extname(relPath).toLowerCase();
|
|
199
|
+
const langId = symbols._langId || extToLang.get(ext);
|
|
200
|
+
if (!langId) continue;
|
|
201
|
+
|
|
202
|
+
const needs = detectNativeNeeds(symbols, ext, langId, optsFlags);
|
|
203
|
+
if (!needs.complexity && !needs.cfg && !needs.dataflow) continue;
|
|
204
|
+
|
|
205
|
+
const absPath = path.join(rootDir, relPath);
|
|
206
|
+
let source: string;
|
|
207
|
+
try {
|
|
208
|
+
source = fs.readFileSync(absPath, 'utf-8');
|
|
209
|
+
} catch (e) {
|
|
210
|
+
debug(`runNativeAnalysis: failed to read ${relPath}: ${toErrorMessage(e)}`);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
runNativeFileAnalysis(native, source, absPath, relPath, langId, symbols, needs);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Store native complexity results on definitions, matched by line number. */
|
|
219
|
+
function storeNativeComplexityResults(
|
|
220
|
+
results: NativeFunctionComplexityResult[],
|
|
221
|
+
defs: Definition[],
|
|
222
|
+
): void {
|
|
223
|
+
const byLine = new Map<number, NativeFunctionComplexityResult[]>();
|
|
224
|
+
for (const r of results) {
|
|
225
|
+
if (!byLine.has(r.line)) byLine.set(r.line, []);
|
|
226
|
+
byLine.get(r.line)!.push(r);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for (const def of defs) {
|
|
230
|
+
if ((def.kind === 'function' || def.kind === 'method') && def.line && !def.complexity) {
|
|
231
|
+
const candidates = byLine.get(def.line);
|
|
232
|
+
if (!candidates) continue;
|
|
233
|
+
const match =
|
|
234
|
+
candidates.length === 1
|
|
235
|
+
? candidates[0]
|
|
236
|
+
: (candidates.find((r) => r.name === def.name) ?? candidates[0]);
|
|
237
|
+
if (!match) continue;
|
|
238
|
+
const { complexity: c } = match;
|
|
239
|
+
def.complexity = {
|
|
240
|
+
cognitive: c.cognitive,
|
|
241
|
+
cyclomatic: c.cyclomatic,
|
|
242
|
+
maxNesting: c.maxNesting,
|
|
243
|
+
halstead: c.halstead
|
|
244
|
+
? {
|
|
245
|
+
volume: c.halstead.volume,
|
|
246
|
+
difficulty: c.halstead.difficulty,
|
|
247
|
+
effort: c.halstead.effort,
|
|
248
|
+
bugs: c.halstead.bugs,
|
|
249
|
+
}
|
|
250
|
+
: undefined,
|
|
251
|
+
loc: c.loc
|
|
252
|
+
? { loc: c.loc.loc, sloc: c.loc.sloc, commentLines: c.loc.commentLines }
|
|
253
|
+
: undefined,
|
|
254
|
+
maintainabilityIndex: c.maintainabilityIndex ?? undefined,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Override a definition's cyclomatic complexity with a CFG-derived value and recompute MI. */
|
|
261
|
+
function overrideCyclomaticFromCfg(def: Definition, cfgCyclomatic: number): void {
|
|
262
|
+
if (!def.complexity) return;
|
|
263
|
+
if (cfgCyclomatic <= 0) {
|
|
264
|
+
debug(`overrideCyclomaticFromCfg: skipping ${def.name} — cfgCyclomatic=${cfgCyclomatic}`);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
def.complexity.cyclomatic = cfgCyclomatic;
|
|
268
|
+
const { loc, halstead } = def.complexity;
|
|
269
|
+
const volume = halstead ? halstead.volume : 0;
|
|
270
|
+
const commentRatio = loc && loc.loc > 0 ? loc.commentLines / loc.loc : 0;
|
|
271
|
+
def.complexity.maintainabilityIndex = computeMaintainabilityIndex(
|
|
272
|
+
volume,
|
|
273
|
+
cfgCyclomatic,
|
|
274
|
+
loc?.sloc ?? 0,
|
|
275
|
+
commentRatio,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Store native CFG results on definitions, matched by line number. */
|
|
280
|
+
function storeNativeCfgResults(results: NativeFunctionCfgResult[], defs: Definition[]): void {
|
|
281
|
+
const byLine = new Map<number, NativeFunctionCfgResult[]>();
|
|
282
|
+
for (const r of results) {
|
|
283
|
+
if (!byLine.has(r.line)) byLine.set(r.line, []);
|
|
284
|
+
byLine.get(r.line)!.push(r);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
for (const def of defs) {
|
|
288
|
+
if (
|
|
289
|
+
(def.kind === 'function' || def.kind === 'method') &&
|
|
290
|
+
def.line &&
|
|
291
|
+
def.cfg !== null &&
|
|
292
|
+
!def.cfg?.blocks?.length
|
|
293
|
+
) {
|
|
294
|
+
const candidates = byLine.get(def.line);
|
|
295
|
+
if (!candidates) continue;
|
|
296
|
+
const match =
|
|
297
|
+
candidates.length === 1
|
|
298
|
+
? candidates[0]
|
|
299
|
+
: (candidates.find((r) => r.name === def.name) ?? candidates[0]);
|
|
300
|
+
if (!match) continue;
|
|
301
|
+
def.cfg = match.cfg;
|
|
302
|
+
|
|
303
|
+
// Override complexity cyclomatic with CFG-derived value
|
|
304
|
+
const { edges, blocks } = match.cfg;
|
|
305
|
+
if (edges && blocks) {
|
|
306
|
+
overrideCyclomaticFromCfg(def, edges.length - blocks.length + 2);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
98
312
|
// ─── WASM pre-parse ─────────────────────────────────────────────────────
|
|
99
313
|
|
|
100
314
|
async function ensureWasmTreesIfNeeded(
|
|
@@ -115,34 +329,22 @@ async function ensureWasmTreesIfNeeded(
|
|
|
115
329
|
const ext = path.extname(relPath).toLowerCase();
|
|
116
330
|
const defs = symbols.definitions || [];
|
|
117
331
|
|
|
118
|
-
// Only consider definitions with a real function body.
|
|
119
|
-
// Interface/type property signatures are extracted as methods but correctly
|
|
120
|
-
// lack complexity/CFG data from the native engine. Exclude them by:
|
|
121
|
-
// 1. Single-line span (endLine === line) — type property on one line
|
|
122
|
-
// 2. Dotted names (e.g. "Interface.prop") — child definitions of types
|
|
123
|
-
const hasFuncBody = (d: {
|
|
124
|
-
name: string;
|
|
125
|
-
kind: string;
|
|
126
|
-
line: number;
|
|
127
|
-
endLine?: number | null;
|
|
128
|
-
}) =>
|
|
129
|
-
(d.kind === 'function' || d.kind === 'method') &&
|
|
130
|
-
d.line > 0 &&
|
|
131
|
-
d.endLine != null &&
|
|
132
|
-
d.endLine > d.line &&
|
|
133
|
-
!d.name.includes('.');
|
|
134
|
-
|
|
135
332
|
// AST: need tree when native didn't provide non-call astNodes
|
|
136
|
-
const
|
|
333
|
+
const lid = symbols._langId || '';
|
|
334
|
+
const needsAst =
|
|
335
|
+
doAst &&
|
|
336
|
+
!Array.isArray(symbols.astNodes) &&
|
|
337
|
+
(WALK_EXTENSIONS.has(ext) || AST_TYPE_MAPS.has(lid));
|
|
137
338
|
const needsComplexity =
|
|
138
339
|
doComplexity &&
|
|
139
|
-
COMPLEXITY_EXTENSIONS.has(ext) &&
|
|
340
|
+
(COMPLEXITY_EXTENSIONS.has(ext) || COMPLEXITY_RULES.has(lid)) &&
|
|
140
341
|
defs.some((d) => hasFuncBody(d) && !d.complexity);
|
|
141
342
|
const needsCfg =
|
|
142
343
|
doCfg &&
|
|
143
|
-
CFG_EXTENSIONS.has(ext) &&
|
|
344
|
+
(CFG_EXTENSIONS.has(ext) || CFG_RULES.has(lid)) &&
|
|
144
345
|
defs.some((d) => hasFuncBody(d) && d.cfg !== null && !Array.isArray(d.cfg?.blocks));
|
|
145
|
-
const needsDataflow =
|
|
346
|
+
const needsDataflow =
|
|
347
|
+
doDataflow && !symbols.dataflow && (DATAFLOW_EXTENSIONS.has(ext) || DATAFLOW_RULES.has(lid));
|
|
146
348
|
|
|
147
349
|
if (needsAst || needsComplexity || needsCfg || needsDataflow) {
|
|
148
350
|
needsWasmTrees = true;
|
|
@@ -155,7 +357,7 @@ async function ensureWasmTreesIfNeeded(
|
|
|
155
357
|
const { ensureWasmTrees } = await getParserModule();
|
|
156
358
|
await ensureWasmTrees(fileSymbols, rootDir);
|
|
157
359
|
} catch (err: unknown) {
|
|
158
|
-
debug(`ensureWasmTrees failed: ${(err
|
|
360
|
+
debug(`ensureWasmTrees failed: ${toErrorMessage(err)}`);
|
|
159
361
|
}
|
|
160
362
|
}
|
|
161
363
|
}
|
|
@@ -224,9 +426,9 @@ function setupComplexityVisitorForFile(
|
|
|
224
426
|
}
|
|
225
427
|
|
|
226
428
|
/** Set up CFG visitor if any definitions need WASM CFG analysis. */
|
|
227
|
-
function setupCfgVisitorForFile(defs: Definition[], langId: string
|
|
429
|
+
function setupCfgVisitorForFile(defs: Definition[], langId: string): Visitor | null {
|
|
228
430
|
const cfgRulesForLang = CFG_RULES.get(langId);
|
|
229
|
-
if (!cfgRulesForLang
|
|
431
|
+
if (!cfgRulesForLang) return null;
|
|
230
432
|
|
|
231
433
|
const needsWasmCfg = defs.some(
|
|
232
434
|
(d) => hasFuncBody(d) && d.cfg !== null && !Array.isArray(d.cfg?.blocks),
|
|
@@ -260,12 +462,12 @@ function setupVisitors(
|
|
|
260
462
|
opts.complexity !== false ? setupComplexityVisitorForFile(defs, langId, walkerOpts) : null;
|
|
261
463
|
if (complexityVisitor) visitors.push(complexityVisitor);
|
|
262
464
|
|
|
263
|
-
const cfgVisitor = opts.cfg !== false ? setupCfgVisitorForFile(defs, langId
|
|
465
|
+
const cfgVisitor = opts.cfg !== false ? setupCfgVisitorForFile(defs, langId) : null;
|
|
264
466
|
if (cfgVisitor) visitors.push(cfgVisitor);
|
|
265
467
|
|
|
266
468
|
let dataflowVisitor: Visitor | null = null;
|
|
267
469
|
const dfRules = DATAFLOW_RULES.get(langId);
|
|
268
|
-
if (opts.dataflow !== false && dfRules &&
|
|
470
|
+
if (opts.dataflow !== false && dfRules && !symbols.dataflow) {
|
|
269
471
|
dataflowVisitor = createDataflowVisitor(dfRules);
|
|
270
472
|
visitors.push(dataflowVisitor);
|
|
271
473
|
}
|
|
@@ -338,17 +540,8 @@ function storeCfgResults(results: WalkResults, defs: Definition[]): void {
|
|
|
338
540
|
def.cfg = { blocks: cfgResult.blocks, edges: cfgResult.edges };
|
|
339
541
|
|
|
340
542
|
// Override complexity's cyclomatic with CFG-derived value (single source of truth)
|
|
341
|
-
if (
|
|
342
|
-
def
|
|
343
|
-
const { loc, halstead } = def.complexity;
|
|
344
|
-
const volume = halstead ? halstead.volume : 0;
|
|
345
|
-
const commentRatio = loc && loc.loc > 0 ? loc.commentLines / loc.loc : 0;
|
|
346
|
-
def.complexity.maintainabilityIndex = computeMaintainabilityIndex(
|
|
347
|
-
volume,
|
|
348
|
-
cfgResult.cyclomatic,
|
|
349
|
-
loc?.sloc ?? 0,
|
|
350
|
-
commentRatio,
|
|
351
|
-
);
|
|
543
|
+
if (cfgResult.cyclomatic != null) {
|
|
544
|
+
overrideCyclomaticFromCfg(def, cfgResult.cyclomatic);
|
|
352
545
|
}
|
|
353
546
|
}
|
|
354
547
|
}
|
|
@@ -370,7 +563,7 @@ async function delegateToBuildFunctions(
|
|
|
370
563
|
const { buildAstNodes } = await import('../features/ast.js');
|
|
371
564
|
await buildAstNodes(db, fileSymbols as Map<string, any>, rootDir, engineOpts);
|
|
372
565
|
} catch (err: unknown) {
|
|
373
|
-
debug(`buildAstNodes failed: ${(err
|
|
566
|
+
debug(`buildAstNodes failed: ${toErrorMessage(err)}`);
|
|
374
567
|
}
|
|
375
568
|
timing.astMs = performance.now() - t0;
|
|
376
569
|
}
|
|
@@ -381,7 +574,7 @@ async function delegateToBuildFunctions(
|
|
|
381
574
|
const { buildComplexityMetrics } = await import('../features/complexity.js');
|
|
382
575
|
await buildComplexityMetrics(db, fileSymbols as Map<string, any>, rootDir, engineOpts);
|
|
383
576
|
} catch (err: unknown) {
|
|
384
|
-
debug(`buildComplexityMetrics failed: ${(err
|
|
577
|
+
debug(`buildComplexityMetrics failed: ${toErrorMessage(err)}`);
|
|
385
578
|
}
|
|
386
579
|
timing.complexityMs = performance.now() - t0;
|
|
387
580
|
}
|
|
@@ -392,7 +585,7 @@ async function delegateToBuildFunctions(
|
|
|
392
585
|
const { buildCFGData } = await import('../features/cfg.js');
|
|
393
586
|
await buildCFGData(db, fileSymbols, rootDir, engineOpts);
|
|
394
587
|
} catch (err: unknown) {
|
|
395
|
-
debug(`buildCFGData failed: ${(err
|
|
588
|
+
debug(`buildCFGData failed: ${toErrorMessage(err)}`);
|
|
396
589
|
}
|
|
397
590
|
timing.cfgMs = performance.now() - t0;
|
|
398
591
|
}
|
|
@@ -403,7 +596,7 @@ async function delegateToBuildFunctions(
|
|
|
403
596
|
const { buildDataflowEdges } = await import('../features/dataflow.js');
|
|
404
597
|
await buildDataflowEdges(db, fileSymbols, rootDir, engineOpts);
|
|
405
598
|
} catch (err: unknown) {
|
|
406
|
-
debug(`buildDataflowEdges failed: ${(err
|
|
599
|
+
debug(`buildDataflowEdges failed: ${toErrorMessage(err)}`);
|
|
407
600
|
}
|
|
408
601
|
timing.dataflowMs = performance.now() - t0;
|
|
409
602
|
}
|
|
@@ -429,7 +622,17 @@ export async function runAnalyses(
|
|
|
429
622
|
|
|
430
623
|
const extToLang = buildExtToLangMap();
|
|
431
624
|
|
|
432
|
-
//
|
|
625
|
+
// Native analysis pass: try Rust standalone functions before WASM fallback.
|
|
626
|
+
// This fills in complexity/CFG/dataflow for files that the native parse pipeline
|
|
627
|
+
// missed, avoiding the need to parse with WASM + run JS visitors.
|
|
628
|
+
const native = loadNative();
|
|
629
|
+
if (native?.analyzeComplexity ?? native?.buildCfgAnalysis ?? native?.extractDataflowAnalysis) {
|
|
630
|
+
const t0native = performance.now();
|
|
631
|
+
runNativeAnalysis(native, fileSymbols, rootDir, opts, extToLang);
|
|
632
|
+
debug(`native standalone analysis: ${(performance.now() - t0native).toFixed(1)}ms`);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// WASM pre-parse for files that still need it (AST store, or native gaps)
|
|
433
636
|
await ensureWasmTreesIfNeeded(fileSymbols, opts, rootDir);
|
|
434
637
|
|
|
435
638
|
// Unified pre-walk: run all applicable visitors in a single DFS per file
|
|
@@ -9,6 +9,16 @@ import type { HalsteadDerivedMetrics, LOCMetrics, TreeSitterNode } from '../type
|
|
|
9
9
|
|
|
10
10
|
// ─── Halstead Derived Metrics ─────────────────────────────────────────────
|
|
11
11
|
|
|
12
|
+
/** Halstead delivered-bugs denominator (industry standard: V / 3000). */
|
|
13
|
+
const HALSTEAD_BUGS_DIVISOR = 3000;
|
|
14
|
+
|
|
15
|
+
/** Sum all values in a count map. */
|
|
16
|
+
function sumCounts(map: Map<string, number>): number {
|
|
17
|
+
let total = 0;
|
|
18
|
+
for (const c of map.values()) total += c;
|
|
19
|
+
return total;
|
|
20
|
+
}
|
|
21
|
+
|
|
12
22
|
/**
|
|
13
23
|
* Compute Halstead derived metrics from raw operator/operand counts.
|
|
14
24
|
*
|
|
@@ -22,17 +32,15 @@ export function computeHalsteadDerived(
|
|
|
22
32
|
): HalsteadDerivedMetrics {
|
|
23
33
|
const n1 = operators.size;
|
|
24
34
|
const n2 = operands.size;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
let bigN2 = 0;
|
|
28
|
-
for (const c of operands.values()) bigN2 += c;
|
|
35
|
+
const bigN1 = sumCounts(operators);
|
|
36
|
+
const bigN2 = sumCounts(operands);
|
|
29
37
|
|
|
30
38
|
const vocabulary = n1 + n2;
|
|
31
39
|
const length = bigN1 + bigN2;
|
|
32
40
|
const volume = vocabulary > 0 ? length * Math.log2(vocabulary) : 0;
|
|
33
41
|
const difficulty = n2 > 0 ? (n1 / 2) * (bigN2 / n2) : 0;
|
|
34
42
|
const effort = difficulty * volume;
|
|
35
|
-
const bugs = volume /
|
|
43
|
+
const bugs = volume / HALSTEAD_BUGS_DIVISOR;
|
|
36
44
|
|
|
37
45
|
return {
|
|
38
46
|
n1,
|
|
@@ -97,10 +105,20 @@ export function computeLOCMetrics(functionNode: TreeSitterNode, language?: strin
|
|
|
97
105
|
// ─── Maintainability Index ────────────────────────────────────────────────
|
|
98
106
|
|
|
99
107
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* Original SEI formula: MI = 171 - 5.2*ln(V) - 0.23*G - 16.2*ln(LOC) + 50*sin(sqrt(2.4*CM))
|
|
108
|
+
* SEI Maintainability Index formula coefficients.
|
|
109
|
+
* Original: MI = 171 - 5.2*ln(V) - 0.23*G - 16.2*ln(LOC) + 50*sin(sqrt(2.4*CM))
|
|
103
110
|
* Microsoft normalization: max(0, min(100, MI * 100/171))
|
|
111
|
+
*/
|
|
112
|
+
const MI_BASE = 171;
|
|
113
|
+
const MI_VOLUME_COEFF = 5.2;
|
|
114
|
+
const MI_CYCLOMATIC_COEFF = 0.23;
|
|
115
|
+
const MI_LOC_COEFF = 16.2;
|
|
116
|
+
const MI_COMMENT_AMPLITUDE = 50;
|
|
117
|
+
const MI_COMMENT_SCALE = 2.4;
|
|
118
|
+
const MI_NORMALIZE_SCALE = 100;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Compute normalized Maintainability Index (0-100 scale).
|
|
104
122
|
*
|
|
105
123
|
* @param {number} volume - Halstead volume
|
|
106
124
|
* @param {number} cyclomatic - Cyclomatic complexity
|
|
@@ -117,12 +135,16 @@ export function computeMaintainabilityIndex(
|
|
|
117
135
|
const safeVolume = Math.max(volume, 1);
|
|
118
136
|
const safeSLOC = Math.max(sloc, 1);
|
|
119
137
|
|
|
120
|
-
let mi =
|
|
138
|
+
let mi =
|
|
139
|
+
MI_BASE -
|
|
140
|
+
MI_VOLUME_COEFF * Math.log(safeVolume) -
|
|
141
|
+
MI_CYCLOMATIC_COEFF * cyclomatic -
|
|
142
|
+
MI_LOC_COEFF * Math.log(safeSLOC);
|
|
121
143
|
|
|
122
144
|
if (commentRatio != null && commentRatio > 0) {
|
|
123
|
-
mi +=
|
|
145
|
+
mi += MI_COMMENT_AMPLITUDE * Math.sin(Math.sqrt(MI_COMMENT_SCALE * commentRatio));
|
|
124
146
|
}
|
|
125
147
|
|
|
126
|
-
const normalized = Math.max(0, Math.min(
|
|
148
|
+
const normalized = Math.max(0, Math.min(MI_NORMALIZE_SCALE, (mi * MI_NORMALIZE_SCALE) / MI_BASE));
|
|
127
149
|
return +normalized.toFixed(1);
|
|
128
150
|
}
|
|
@@ -237,7 +237,6 @@ export const dataflow: DataflowRulesConfig = makeDataflowRules({
|
|
|
237
237
|
// ─── AST Node Types ───────────────────────────────────────────────────────
|
|
238
238
|
|
|
239
239
|
export const astTypes: Record<string, string> | null = {
|
|
240
|
-
call_expression: 'call',
|
|
241
240
|
new_expression: 'new',
|
|
242
241
|
throw_statement: 'throw',
|
|
243
242
|
await_expression: 'await',
|
|
@@ -150,6 +150,38 @@ export function makeDataflowRules(overrides: Partial<DataflowRulesConfig>): Data
|
|
|
150
150
|
|
|
151
151
|
// ─── AST Helpers ──────────────────────────────────────────────────────────
|
|
152
152
|
|
|
153
|
+
/** Compute the span (row count) of a tree-sitter node. */
|
|
154
|
+
function nodeSpan(node: TreeSitterNode): number {
|
|
155
|
+
return node.endPosition.row - node.startPosition.row;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Recursively search for the narrowest function node at the target line.
|
|
160
|
+
*/
|
|
161
|
+
function searchFunctionNode(
|
|
162
|
+
node: TreeSitterNode,
|
|
163
|
+
targetStart: number,
|
|
164
|
+
functionNodeTypes: Set<string>,
|
|
165
|
+
best: TreeSitterNode | null,
|
|
166
|
+
): TreeSitterNode | null {
|
|
167
|
+
const nodeStart = node.startPosition.row;
|
|
168
|
+
const nodeEnd = node.endPosition.row;
|
|
169
|
+
|
|
170
|
+
// Prune branches outside range
|
|
171
|
+
if (nodeEnd < targetStart || nodeStart > targetStart + 1) return best;
|
|
172
|
+
|
|
173
|
+
if (functionNodeTypes.has(node.type) && nodeStart === targetStart) {
|
|
174
|
+
if (!best || nodeSpan(node) < nodeSpan(best)) {
|
|
175
|
+
best = node;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
180
|
+
best = searchFunctionNode(node.child(i)!, targetStart, functionNodeTypes, best);
|
|
181
|
+
}
|
|
182
|
+
return best;
|
|
183
|
+
}
|
|
184
|
+
|
|
153
185
|
export function findFunctionNode(
|
|
154
186
|
rootNode: TreeSitterNode,
|
|
155
187
|
startLine: number,
|
|
@@ -158,30 +190,7 @@ export function findFunctionNode(
|
|
|
158
190
|
): TreeSitterNode | null {
|
|
159
191
|
// tree-sitter lines are 0-indexed
|
|
160
192
|
const targetStart = startLine - 1;
|
|
161
|
-
|
|
162
|
-
let best: TreeSitterNode | null = null;
|
|
163
|
-
|
|
164
|
-
function search(node: TreeSitterNode): void {
|
|
165
|
-
const nodeStart = node.startPosition.row;
|
|
166
|
-
const nodeEnd = node.endPosition.row;
|
|
167
|
-
|
|
168
|
-
// Prune branches outside range
|
|
169
|
-
if (nodeEnd < targetStart || nodeStart > targetStart + 1) return;
|
|
170
|
-
|
|
171
|
-
if (rules.functionNodes.has(node.type) && nodeStart === targetStart) {
|
|
172
|
-
// Found a function node at the right position — pick it
|
|
173
|
-
if (!best || nodeEnd - nodeStart < best.endPosition.row - best.startPosition.row) {
|
|
174
|
-
best = node;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
179
|
-
search(node.child(i)!);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
search(rootNode);
|
|
184
|
-
return best;
|
|
193
|
+
return searchFunctionNode(rootNode, targetStart, rules.functionNodes, null);
|
|
185
194
|
}
|
|
186
195
|
|
|
187
196
|
// ─── Extension / Language Mapping ─────────────────────────────────────────
|
|
@@ -88,6 +88,41 @@ export function extractParams(
|
|
|
88
88
|
return result;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/** Extract names from a rest parameter (e.g. `...args`). */
|
|
92
|
+
function extractRestParamNames(node: TreeSitterNode, rules: LanguageRules): string[] {
|
|
93
|
+
const nameNode = node.childForFieldName('name');
|
|
94
|
+
if (nameNode) return [nameNode.text];
|
|
95
|
+
for (const child of node.namedChildren) {
|
|
96
|
+
if (child.type === rules.paramIdentifier) return [child.text];
|
|
97
|
+
}
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Extract names from an object destructuring pattern (e.g. `{ a, b: c }`). */
|
|
102
|
+
function extractObjectDestructNames(node: TreeSitterNode, rules: LanguageRules): string[] {
|
|
103
|
+
const names: string[] = [];
|
|
104
|
+
for (const child of node.namedChildren) {
|
|
105
|
+
if (rules.shorthandPropPattern && child.type === rules.shorthandPropPattern) {
|
|
106
|
+
names.push(child.text);
|
|
107
|
+
} else if (rules.pairPatternType && child.type === rules.pairPatternType) {
|
|
108
|
+
const value = child.childForFieldName('value');
|
|
109
|
+
if (value) names.push(...extractParamNames(value, rules));
|
|
110
|
+
} else if (rules.restParamType && child.type === rules.restParamType) {
|
|
111
|
+
names.push(...extractParamNames(child, rules));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return names;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Extract names from an array destructuring pattern (e.g. `[a, b]`). */
|
|
118
|
+
function extractArrayDestructNames(node: TreeSitterNode, rules: LanguageRules): string[] {
|
|
119
|
+
const names: string[] = [];
|
|
120
|
+
for (const child of node.namedChildren) {
|
|
121
|
+
names.push(...extractParamNames(child, rules));
|
|
122
|
+
}
|
|
123
|
+
return names;
|
|
124
|
+
}
|
|
125
|
+
|
|
91
126
|
/**
|
|
92
127
|
* Extract parameter names from a single parameter node.
|
|
93
128
|
*/
|
|
@@ -113,35 +148,15 @@ export function extractParamNames(node: TreeSitterNode | null, rules: LanguageRu
|
|
|
113
148
|
}
|
|
114
149
|
|
|
115
150
|
if (rules.restParamType && t === rules.restParamType) {
|
|
116
|
-
|
|
117
|
-
if (nameNode) return [nameNode.text];
|
|
118
|
-
for (const child of node.namedChildren) {
|
|
119
|
-
if (child.type === rules.paramIdentifier) return [child.text];
|
|
120
|
-
}
|
|
121
|
-
return [];
|
|
151
|
+
return extractRestParamNames(node, rules);
|
|
122
152
|
}
|
|
123
153
|
|
|
124
154
|
if (rules.objectDestructType && t === rules.objectDestructType) {
|
|
125
|
-
|
|
126
|
-
for (const child of node.namedChildren) {
|
|
127
|
-
if (rules.shorthandPropPattern && child.type === rules.shorthandPropPattern) {
|
|
128
|
-
names.push(child.text);
|
|
129
|
-
} else if (rules.pairPatternType && child.type === rules.pairPatternType) {
|
|
130
|
-
const value = child.childForFieldName('value');
|
|
131
|
-
if (value) names.push(...extractParamNames(value, rules));
|
|
132
|
-
} else if (rules.restParamType && child.type === rules.restParamType) {
|
|
133
|
-
names.push(...extractParamNames(child, rules));
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return names;
|
|
155
|
+
return extractObjectDestructNames(node, rules);
|
|
137
156
|
}
|
|
138
157
|
|
|
139
158
|
if (rules.arrayDestructType && t === rules.arrayDestructType) {
|
|
140
|
-
|
|
141
|
-
for (const child of node.namedChildren) {
|
|
142
|
-
names.push(...extractParamNames(child, rules));
|
|
143
|
-
}
|
|
144
|
-
return names;
|
|
159
|
+
return extractArrayDestructNames(node, rules);
|
|
145
160
|
}
|
|
146
161
|
|
|
147
162
|
return [];
|
|
@@ -155,6 +170,19 @@ export function isIdent(nodeType: string, rules: LanguageRules): boolean {
|
|
|
155
170
|
return rules.extraIdentifierTypes ? rules.extraIdentifierTypes.has(nodeType) : false;
|
|
156
171
|
}
|
|
157
172
|
|
|
173
|
+
/** Resolve callee name from an optional chain node (e.g. `obj?.method()`). */
|
|
174
|
+
function resolveOptionalChainCallee(fn: TreeSitterNode, rules: LanguageRules): string | null {
|
|
175
|
+
const target = fn.namedChildren[0];
|
|
176
|
+
if (!target) return null;
|
|
177
|
+
if (target.type === rules.memberNode) {
|
|
178
|
+
const prop = target.childForFieldName(rules.memberPropertyField);
|
|
179
|
+
return prop ? prop.text : null;
|
|
180
|
+
}
|
|
181
|
+
if (target.type === 'identifier') return target.text;
|
|
182
|
+
const prop = fn.childForFieldName(rules.memberPropertyField);
|
|
183
|
+
return prop ? prop.text : null;
|
|
184
|
+
}
|
|
185
|
+
|
|
158
186
|
/**
|
|
159
187
|
* Resolve the name a call expression is calling using rules.
|
|
160
188
|
*/
|
|
@@ -170,15 +198,7 @@ export function resolveCalleeName(callNode: TreeSitterNode, rules: LanguageRules
|
|
|
170
198
|
return prop ? prop.text : null;
|
|
171
199
|
}
|
|
172
200
|
if (rules.optionalChainNode && fn.type === rules.optionalChainNode) {
|
|
173
|
-
|
|
174
|
-
if (!target) return null;
|
|
175
|
-
if (target.type === rules.memberNode) {
|
|
176
|
-
const prop = target.childForFieldName(rules.memberPropertyField);
|
|
177
|
-
return prop ? prop.text : null;
|
|
178
|
-
}
|
|
179
|
-
if (target.type === 'identifier') return target.text;
|
|
180
|
-
const prop = fn.childForFieldName(rules.memberPropertyField);
|
|
181
|
-
return prop ? prop.text : null;
|
|
201
|
+
return resolveOptionalChainCallee(fn, rules);
|
|
182
202
|
}
|
|
183
203
|
return null;
|
|
184
204
|
}
|