@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
|
@@ -20,6 +20,127 @@ import type {
|
|
|
20
20
|
WalkResults,
|
|
21
21
|
} from '../types.js';
|
|
22
22
|
|
|
23
|
+
/** Merge each visitor's custom functionNodeTypes into the master set. */
|
|
24
|
+
function mergeFunctionNodeTypes(visitors: Visitor[], base: Set<string>): Set<string> {
|
|
25
|
+
const merged = new Set(base);
|
|
26
|
+
for (const v of visitors) {
|
|
27
|
+
if (v.functionNodeTypes) {
|
|
28
|
+
for (const t of v.functionNodeTypes) merged.add(t);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return merged;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Initialize all visitors for a given language. */
|
|
35
|
+
function initVisitors(visitors: Visitor[], langId: string): void {
|
|
36
|
+
for (const v of visitors) {
|
|
37
|
+
if (v.init) v.init(langId);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Check whether a visitor should be skipped at the current depth. */
|
|
42
|
+
function isSkipped(
|
|
43
|
+
skipDepths: Map<number, number>,
|
|
44
|
+
visitorIndex: number,
|
|
45
|
+
currentDepth: number,
|
|
46
|
+
): boolean {
|
|
47
|
+
const skipAt = skipDepths.get(visitorIndex);
|
|
48
|
+
// Skipped if skip was requested at a shallower (or equal) depth
|
|
49
|
+
// We skip descendants, not the node itself, so skip when currentDepth > skipAt
|
|
50
|
+
return skipAt !== undefined && currentDepth > skipAt;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Dispatch enterFunction hooks to all non-skipped visitors. */
|
|
54
|
+
function dispatchEnterFunction(
|
|
55
|
+
visitors: Visitor[],
|
|
56
|
+
skipDepths: Map<number, number>,
|
|
57
|
+
node: TreeSitterNode,
|
|
58
|
+
funcName: string | null,
|
|
59
|
+
context: VisitorContext,
|
|
60
|
+
depth: number,
|
|
61
|
+
): void {
|
|
62
|
+
for (let i = 0; i < visitors.length; i++) {
|
|
63
|
+
const v = visitors[i]!;
|
|
64
|
+
if (v.enterFunction && !isSkipped(skipDepths, i, depth)) {
|
|
65
|
+
v.enterFunction(node, funcName, context);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Dispatch enterNode hooks and track skipChildren requests. */
|
|
71
|
+
function dispatchEnterNode(
|
|
72
|
+
visitors: Visitor[],
|
|
73
|
+
skipDepths: Map<number, number>,
|
|
74
|
+
node: TreeSitterNode,
|
|
75
|
+
context: VisitorContext,
|
|
76
|
+
depth: number,
|
|
77
|
+
): void {
|
|
78
|
+
for (let i = 0; i < visitors.length; i++) {
|
|
79
|
+
const v = visitors[i]!;
|
|
80
|
+
if (v.enterNode && !isSkipped(skipDepths, i, depth)) {
|
|
81
|
+
const result = v.enterNode(node, context);
|
|
82
|
+
if (result?.skipChildren) {
|
|
83
|
+
skipDepths.set(i, depth);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Dispatch exitNode hooks to all non-skipped visitors. */
|
|
90
|
+
function dispatchExitNode(
|
|
91
|
+
visitors: Visitor[],
|
|
92
|
+
skipDepths: Map<number, number>,
|
|
93
|
+
node: TreeSitterNode,
|
|
94
|
+
context: VisitorContext,
|
|
95
|
+
depth: number,
|
|
96
|
+
): void {
|
|
97
|
+
for (let i = 0; i < visitors.length; i++) {
|
|
98
|
+
const v = visitors[i]!;
|
|
99
|
+
if (v.exitNode && !isSkipped(skipDepths, i, depth)) {
|
|
100
|
+
v.exitNode(node, context);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Clear skip flags for visitors that started skipping at this depth. */
|
|
106
|
+
function clearSkipFlags(
|
|
107
|
+
skipDepths: Map<number, number>,
|
|
108
|
+
visitorCount: number,
|
|
109
|
+
depth: number,
|
|
110
|
+
): void {
|
|
111
|
+
for (let i = 0; i < visitorCount; i++) {
|
|
112
|
+
if (skipDepths.get(i) === depth) {
|
|
113
|
+
skipDepths.delete(i);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Dispatch exitFunction hooks to all non-skipped visitors. */
|
|
119
|
+
function dispatchExitFunction(
|
|
120
|
+
visitors: Visitor[],
|
|
121
|
+
skipDepths: Map<number, number>,
|
|
122
|
+
node: TreeSitterNode,
|
|
123
|
+
funcName: string | null,
|
|
124
|
+
context: VisitorContext,
|
|
125
|
+
depth: number,
|
|
126
|
+
): void {
|
|
127
|
+
for (let i = 0; i < visitors.length; i++) {
|
|
128
|
+
const v = visitors[i]!;
|
|
129
|
+
if (v.exitFunction && !isSkipped(skipDepths, i, depth)) {
|
|
130
|
+
v.exitFunction(node, funcName, context);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Collect finish() results from all visitors into a name-keyed map. */
|
|
136
|
+
function collectResults(visitors: Visitor[]): WalkResults {
|
|
137
|
+
const results: WalkResults = {};
|
|
138
|
+
for (const v of visitors) {
|
|
139
|
+
results[v.name] = v.finish ? v.finish() : undefined;
|
|
140
|
+
}
|
|
141
|
+
return results;
|
|
142
|
+
}
|
|
143
|
+
|
|
23
144
|
/**
|
|
24
145
|
* Walk an AST root with multiple visitors in a single DFS pass.
|
|
25
146
|
*
|
|
@@ -44,18 +165,8 @@ export function walkWithVisitors(
|
|
|
44
165
|
getFunctionName = () => null,
|
|
45
166
|
} = options;
|
|
46
167
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
for (const v of visitors) {
|
|
50
|
-
if (v.functionNodeTypes) {
|
|
51
|
-
for (const t of v.functionNodeTypes) allFuncTypes.add(t);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Initialize visitors
|
|
56
|
-
for (const v of visitors) {
|
|
57
|
-
if (v.init) v.init(langId);
|
|
58
|
-
}
|
|
168
|
+
const allFuncTypes = mergeFunctionNodeTypes(visitors, functionNodeTypes);
|
|
169
|
+
initVisitors(visitors, langId);
|
|
59
170
|
|
|
60
171
|
// Shared context object (mutated during walk)
|
|
61
172
|
const scopeStack: ScopeEntry[] = [];
|
|
@@ -66,95 +177,45 @@ export function walkWithVisitors(
|
|
|
66
177
|
scopeStack,
|
|
67
178
|
};
|
|
68
179
|
|
|
69
|
-
// Track which visitors have requested skipChildren at each depth
|
|
70
|
-
// Key: visitor index, Value: depth at which skip was requested
|
|
71
180
|
const skipDepths = new Map<number, number>();
|
|
72
181
|
|
|
73
182
|
function walk(node: TreeSitterNode | null, depth: number): void {
|
|
74
183
|
if (!node) return;
|
|
75
184
|
|
|
76
185
|
const type = node.type;
|
|
77
|
-
const
|
|
186
|
+
const isFuncBoundary = allFuncTypes.has(type);
|
|
78
187
|
let funcName: string | null = null;
|
|
79
188
|
|
|
80
|
-
|
|
81
|
-
if (isFunction) {
|
|
189
|
+
if (isFuncBoundary) {
|
|
82
190
|
funcName = getFunctionName(node);
|
|
83
191
|
context.currentFunction = node;
|
|
84
192
|
scopeStack.push({ funcName, funcNode: node, params: new Map(), locals: new Map() });
|
|
85
|
-
|
|
86
|
-
const v = visitors[i]!;
|
|
87
|
-
if (v.enterFunction && !isSkipped(i, depth)) {
|
|
88
|
-
v.enterFunction(node, funcName, context);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
193
|
+
dispatchEnterFunction(visitors, skipDepths, node, funcName, context, depth);
|
|
91
194
|
}
|
|
92
195
|
|
|
93
|
-
|
|
94
|
-
for (let i = 0; i < visitors.length; i++) {
|
|
95
|
-
const v = visitors[i]!;
|
|
96
|
-
if (v.enterNode && !isSkipped(i, depth)) {
|
|
97
|
-
const result = v.enterNode(node, context);
|
|
98
|
-
if (result?.skipChildren) {
|
|
99
|
-
skipDepths.set(i, depth);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
196
|
+
dispatchEnterNode(visitors, skipDepths, node, context, depth);
|
|
103
197
|
|
|
104
|
-
// Nesting tracking
|
|
105
198
|
const addsNesting = nestingNodeTypes.has(type);
|
|
106
199
|
if (addsNesting) context.nestingLevel++;
|
|
107
200
|
|
|
108
|
-
// Recurse children using node.child(i) (all children, not just named)
|
|
109
201
|
for (let i = 0; i < node.childCount; i++) {
|
|
110
202
|
walk(node.child(i), depth + 1);
|
|
111
203
|
}
|
|
112
204
|
|
|
113
|
-
// Undo nesting
|
|
114
205
|
if (addsNesting) context.nestingLevel--;
|
|
115
206
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const v = visitors[i]!;
|
|
119
|
-
if (v.exitNode && !isSkipped(i, depth)) {
|
|
120
|
-
v.exitNode(node, context);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Clear skip for any visitor that started skipping at this depth
|
|
125
|
-
for (let i = 0; i < visitors.length; i++) {
|
|
126
|
-
if (skipDepths.get(i) === depth) {
|
|
127
|
-
skipDepths.delete(i);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
207
|
+
dispatchExitNode(visitors, skipDepths, node, context, depth);
|
|
208
|
+
clearSkipFlags(skipDepths, visitors.length, depth);
|
|
130
209
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
for (let i = 0; i < visitors.length; i++) {
|
|
134
|
-
const v = visitors[i]!;
|
|
135
|
-
if (v.exitFunction && !isSkipped(i, depth)) {
|
|
136
|
-
v.exitFunction(node, funcName, context);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
210
|
+
if (isFuncBoundary) {
|
|
211
|
+
dispatchExitFunction(visitors, skipDepths, node, funcName, context, depth);
|
|
139
212
|
scopeStack.pop();
|
|
140
213
|
context.currentFunction =
|
|
141
214
|
scopeStack.length > 0 ? scopeStack[scopeStack.length - 1]!.funcNode : null;
|
|
142
215
|
}
|
|
143
216
|
}
|
|
144
217
|
|
|
145
|
-
function isSkipped(visitorIndex: number, currentDepth: number): boolean {
|
|
146
|
-
const skipAt = skipDepths.get(visitorIndex);
|
|
147
|
-
// Skipped if skip was requested at a shallower (or equal) depth
|
|
148
|
-
// We skip descendants, not the node itself, so skip when currentDepth > skipAt
|
|
149
|
-
return skipAt !== undefined && currentDepth > skipAt;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
218
|
walk(rootNode, 0);
|
|
153
219
|
|
|
154
|
-
|
|
155
|
-
const results: WalkResults = {};
|
|
156
|
-
for (const v of visitors) {
|
|
157
|
-
results[v.name] = v.finish ? v.finish() : undefined;
|
|
158
|
-
}
|
|
159
|
-
return results;
|
|
220
|
+
return collectResults(visitors);
|
|
160
221
|
}
|
|
@@ -44,33 +44,33 @@ function extractExpressionText(node: TreeSitterNode): string | null {
|
|
|
44
44
|
return truncate(node.text);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
if (child.type === 'identifier') return child.text;
|
|
47
|
+
/** Extract the name from a throw statement's child nodes. */
|
|
48
|
+
function extractThrowName(node: TreeSitterNode): string | null {
|
|
49
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
50
|
+
const child = node.child(i);
|
|
51
|
+
if (!child) continue;
|
|
52
|
+
if (child.type === 'new_expression') return extractNewName(child);
|
|
53
|
+
if (child.type === 'call_expression') {
|
|
54
|
+
const fn = child.childForFieldName('function');
|
|
55
|
+
return fn ? fn.text : child.text?.split('(')[0] || '?';
|
|
58
56
|
}
|
|
59
|
-
|
|
57
|
+
if (child.type === 'identifier') return child.text;
|
|
60
58
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
59
|
+
return truncate(node.text);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Extract the name from an await expression's child nodes. */
|
|
63
|
+
function extractAwaitName(node: TreeSitterNode): string | null {
|
|
64
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
65
|
+
const child = node.child(i);
|
|
66
|
+
if (!child) continue;
|
|
67
|
+
if (child.type === 'call_expression') {
|
|
68
|
+
const fn = child.childForFieldName('function');
|
|
69
|
+
return fn ? fn.text : child.text?.split('(')[0] || '?';
|
|
70
|
+
}
|
|
71
|
+
if (child.type === 'identifier' || child.type === 'member_expression') {
|
|
72
|
+
return child.text;
|
|
72
73
|
}
|
|
73
|
-
return truncate(node.text);
|
|
74
74
|
}
|
|
75
75
|
return truncate(node.text);
|
|
76
76
|
}
|
|
@@ -102,40 +102,43 @@ export function createAstStoreVisitor(
|
|
|
102
102
|
return nodeIdMap.get(`${parentDef.name}|${parentDef.kind}|${parentDef.line}`) || null;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
function resolveNameAndText(
|
|
106
|
+
node: TreeSitterNode,
|
|
107
|
+
kind: string,
|
|
108
|
+
): { name: string | null | undefined; text: string | null; skip?: boolean } {
|
|
109
|
+
switch (kind) {
|
|
110
|
+
case 'new':
|
|
111
|
+
return { name: extractNewName(node), text: truncate(node.text) };
|
|
112
|
+
case 'throw':
|
|
113
|
+
return { name: extractThrowName(node), text: extractExpressionText(node) };
|
|
114
|
+
case 'await':
|
|
115
|
+
return { name: extractAwaitName(node), text: extractExpressionText(node) };
|
|
116
|
+
case 'string': {
|
|
117
|
+
const content = node.text?.replace(/^['"`]|['"`]$/g, '') || '';
|
|
118
|
+
if (content.length < 2) return { name: null, text: null, skip: true };
|
|
119
|
+
return { name: truncate(content, 100), text: truncate(node.text) };
|
|
120
|
+
}
|
|
121
|
+
case 'regex':
|
|
122
|
+
return { name: node.text || '?', text: truncate(node.text) };
|
|
123
|
+
default:
|
|
124
|
+
return { name: undefined, text: null };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
105
128
|
function collectNode(node: TreeSitterNode, kind: string): void {
|
|
106
129
|
if (matched.has(node.id)) return;
|
|
107
130
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
let text: string | null = null;
|
|
111
|
-
|
|
112
|
-
if (kind === 'new') {
|
|
113
|
-
name = extractNewName(node);
|
|
114
|
-
text = truncate(node.text);
|
|
115
|
-
} else if (kind === 'throw') {
|
|
116
|
-
name = extractName('throw', node);
|
|
117
|
-
text = extractExpressionText(node);
|
|
118
|
-
} else if (kind === 'await') {
|
|
119
|
-
name = extractName('await', node);
|
|
120
|
-
text = extractExpressionText(node);
|
|
121
|
-
} else if (kind === 'string') {
|
|
122
|
-
const content = node.text?.replace(/^['"`]|['"`]$/g, '') || '';
|
|
123
|
-
if (content.length < 2) return;
|
|
124
|
-
name = truncate(content, 100);
|
|
125
|
-
text = truncate(node.text);
|
|
126
|
-
} else if (kind === 'regex') {
|
|
127
|
-
name = node.text || '?';
|
|
128
|
-
text = truncate(node.text);
|
|
129
|
-
}
|
|
131
|
+
const resolved = resolveNameAndText(node, kind);
|
|
132
|
+
if (resolved.skip) return;
|
|
130
133
|
|
|
131
134
|
rows.push({
|
|
132
135
|
file: relPath,
|
|
133
|
-
line,
|
|
136
|
+
line: node.startPosition.row + 1,
|
|
134
137
|
kind,
|
|
135
|
-
name,
|
|
136
|
-
text,
|
|
138
|
+
name: resolved.name,
|
|
139
|
+
text: resolved.text,
|
|
137
140
|
receiver: null,
|
|
138
|
-
parentNodeId: resolveParentNodeId(
|
|
141
|
+
parentNodeId: resolveParentNodeId(node.startPosition.row + 1),
|
|
139
142
|
});
|
|
140
143
|
|
|
141
144
|
matched.add(node.id);
|
|
@@ -40,6 +40,32 @@ function classifyHalstead(node: TreeSitterNode, hRules: AnyRules, acc: Complexit
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Detect whether a branch node is an else-if that the DFS walk would NOT
|
|
45
|
+
* increment nesting for. Returns true for:
|
|
46
|
+
* - Pattern A (JS/C#/Rust): if_statement whose parent is else_clause
|
|
47
|
+
* - Pattern C (Go/Java): if_statement that is the alternative of parent if
|
|
48
|
+
*
|
|
49
|
+
* Pattern B (Python elif_clause) is not an issue because elif_clause is
|
|
50
|
+
* never in nestingNodes.
|
|
51
|
+
*/
|
|
52
|
+
function isElseIfNonNesting(node: TreeSitterNode, type: string, cRules: AnyRules): boolean {
|
|
53
|
+
if (type !== cRules.ifNodeType) return false;
|
|
54
|
+
|
|
55
|
+
if (cRules.elseViaAlternative) {
|
|
56
|
+
// Pattern C
|
|
57
|
+
return (
|
|
58
|
+
node.parent?.type === cRules.ifNodeType &&
|
|
59
|
+
node.parent?.childForFieldName('alternative')?.id === node.id
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (cRules.elseNodeType) {
|
|
63
|
+
// Pattern A
|
|
64
|
+
return node.parent?.type === cRules.elseNodeType;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
43
69
|
function classifyBranchNode(
|
|
44
70
|
node: TreeSitterNode,
|
|
45
71
|
type: string,
|
|
@@ -156,6 +182,27 @@ function resetAccumulators(hRules: AnyRules | null | undefined): ComplexityAcc {
|
|
|
156
182
|
};
|
|
157
183
|
}
|
|
158
184
|
|
|
185
|
+
/** Classify a single node for all complexity metrics (Halstead, branching, logical ops, etc.). */
|
|
186
|
+
function classifyNode(
|
|
187
|
+
node: TreeSitterNode,
|
|
188
|
+
nestingLevel: number,
|
|
189
|
+
cRules: AnyRules,
|
|
190
|
+
hRules: AnyRules | null | undefined,
|
|
191
|
+
acc: ComplexityAcc,
|
|
192
|
+
): void {
|
|
193
|
+
const type = node.type;
|
|
194
|
+
|
|
195
|
+
if (hRules) classifyHalstead(node, hRules, acc);
|
|
196
|
+
if (nestingLevel > acc.maxNesting) acc.maxNesting = nestingLevel;
|
|
197
|
+
if (type === cRules.logicalNodeType) classifyLogicalOp(node, cRules, acc);
|
|
198
|
+
if (type === cRules.optionalChainType) acc.cyclomatic++;
|
|
199
|
+
if (cRules.branchNodes.has(type) && node.childCount > 0) {
|
|
200
|
+
classifyBranchNode(node, type, nestingLevel, cRules, acc);
|
|
201
|
+
}
|
|
202
|
+
classifyPlainElse(node, type, cRules, acc);
|
|
203
|
+
if (cRules.caseNodes.has(type) && node.childCount > 0) acc.cyclomatic++;
|
|
204
|
+
}
|
|
205
|
+
|
|
159
206
|
export function createComplexityVisitor(
|
|
160
207
|
cRules: AnyRules,
|
|
161
208
|
hRules?: AnyRules | null,
|
|
@@ -169,6 +216,13 @@ export function createComplexityVisitor(
|
|
|
169
216
|
let funcDepth = 0;
|
|
170
217
|
const results: PerFunctionResult[] = [];
|
|
171
218
|
|
|
219
|
+
// The walker increments context.nestingLevel for ALL nodes in nestingNodeTypes
|
|
220
|
+
// (including if_statement). But the DFS engine does NOT increment nesting for
|
|
221
|
+
// else-if if_statement nodes. Track a correction counter so children of else-if
|
|
222
|
+
// nodes see the correct (non-inflated) nesting level.
|
|
223
|
+
let nestingAdjust = 0;
|
|
224
|
+
const adjustNodeIds = new Set<number>();
|
|
225
|
+
|
|
172
226
|
return {
|
|
173
227
|
name: 'complexity',
|
|
174
228
|
functionNodeTypes: cRules.functionNodes,
|
|
@@ -178,15 +232,13 @@ export function createComplexityVisitor(
|
|
|
178
232
|
funcName: string | null,
|
|
179
233
|
_context: VisitorContext,
|
|
180
234
|
): void {
|
|
181
|
-
if (fileLevelWalk) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
funcDepth++;
|
|
189
|
-
}
|
|
235
|
+
if (fileLevelWalk && !activeFuncNode) {
|
|
236
|
+
acc = resetAccumulators(hRules);
|
|
237
|
+
activeFuncNode = funcNode;
|
|
238
|
+
activeFuncName = funcName;
|
|
239
|
+
funcDepth = 0;
|
|
240
|
+
nestingAdjust = 0;
|
|
241
|
+
adjustNodeIds.clear();
|
|
190
242
|
} else {
|
|
191
243
|
funcDepth++;
|
|
192
244
|
}
|
|
@@ -197,18 +249,14 @@ export function createComplexityVisitor(
|
|
|
197
249
|
_funcName: string | null,
|
|
198
250
|
_context: VisitorContext,
|
|
199
251
|
): void {
|
|
200
|
-
if (fileLevelWalk) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
activeFuncName = null;
|
|
209
|
-
} else {
|
|
210
|
-
funcDepth--;
|
|
211
|
-
}
|
|
252
|
+
if (fileLevelWalk && funcNode === activeFuncNode) {
|
|
253
|
+
results.push({
|
|
254
|
+
funcNode,
|
|
255
|
+
funcName: activeFuncName,
|
|
256
|
+
metrics: collectResult(funcNode, acc, hRules, langId),
|
|
257
|
+
});
|
|
258
|
+
activeFuncNode = null;
|
|
259
|
+
activeFuncName = null;
|
|
212
260
|
} else {
|
|
213
261
|
funcDepth--;
|
|
214
262
|
}
|
|
@@ -217,29 +265,30 @@ export function createComplexityVisitor(
|
|
|
217
265
|
enterNode(node: TreeSitterNode, context: VisitorContext): EnterNodeResult | undefined {
|
|
218
266
|
if (fileLevelWalk && !activeFuncNode) return;
|
|
219
267
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
268
|
+
// In file-level mode, funcDepth starts at 0 for the active function.
|
|
269
|
+
// In function-level mode, funcDepth starts at 1 for the root function
|
|
270
|
+
// (since enterFunction always increments it). Nested functions add +1
|
|
271
|
+
// each level — subtract 1 so the root function contributes 0 nesting
|
|
272
|
+
// and each nested level adds +1, matching the Rust engine's behavior.
|
|
273
|
+
const funcNesting = fileLevelWalk ? funcDepth : Math.max(0, funcDepth - 1);
|
|
274
|
+
const nestingLevel = context.nestingLevel + funcNesting - nestingAdjust;
|
|
275
|
+
classifyNode(node, nestingLevel, cRules, hRules, acc);
|
|
276
|
+
|
|
277
|
+
// If this is an else-if if_statement that the walker will treat as a
|
|
278
|
+
// nesting node (incrementing context.nestingLevel for children), but
|
|
279
|
+
// the DFS walk would NOT increment nesting for, compensate by bumping
|
|
280
|
+
// nestingAdjust so children see the correct level.
|
|
281
|
+
if (cRules.nestingNodes.has(node.type) && isElseIfNonNesting(node, node.type, cRules)) {
|
|
282
|
+
nestingAdjust++;
|
|
283
|
+
adjustNodeIds.add(node.id);
|
|
229
284
|
}
|
|
230
|
-
|
|
231
|
-
if (type === cRules.optionalChainType) acc.cyclomatic++;
|
|
232
|
-
|
|
233
|
-
if (cRules.branchNodes.has(type) && node.childCount > 0) {
|
|
234
|
-
classifyBranchNode(node, type, nestingLevel, cRules, acc);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
classifyPlainElse(node, type, cRules, acc);
|
|
238
|
-
|
|
239
|
-
if (cRules.caseNodes.has(type) && node.childCount > 0) acc.cyclomatic++;
|
|
240
285
|
},
|
|
241
286
|
|
|
242
287
|
exitNode(node: TreeSitterNode): void {
|
|
288
|
+
if (adjustNodeIds.has(node.id)) {
|
|
289
|
+
nestingAdjust--;
|
|
290
|
+
adjustNodeIds.delete(node.id);
|
|
291
|
+
}
|
|
243
292
|
if (hRules?.skipTypes.has(node.type)) acc.halsteadSkipDepth--;
|
|
244
293
|
},
|
|
245
294
|
|