@optave/codegraph 3.8.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 +9 -8
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +95 -87
- 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 +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/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 +255 -105
- 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 +22 -17
- 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 +0 -5
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +13 -28
- 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/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 +46 -45
- 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 +10 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/ast-analysis/engine.ts +126 -105
- 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 +35 -40
- package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
- 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 +286 -97
- package/src/domain/graph/builder/stages/build-edges.ts +22 -18
- 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 +14 -26
- 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/go.ts +43 -33
- package/src/extractors/helpers.ts +37 -23
- package/src/extractors/java.ts +66 -47
- package/src/extractors/javascript.ts +45 -46
- 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 +24 -4
|
@@ -109,6 +109,163 @@ export const computeMaintainabilityIndex = _computeMaintainabilityIndex;
|
|
|
109
109
|
|
|
110
110
|
// ─── Algorithm: Single-Traversal DFS ──────────────────────────────────────
|
|
111
111
|
|
|
112
|
+
interface ComplexityAccumulator {
|
|
113
|
+
cognitive: number;
|
|
114
|
+
cyclomatic: number;
|
|
115
|
+
maxNesting: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
type WalkFn = (n: TreeSitterNode | null, level: number, isTop: boolean) => void;
|
|
119
|
+
|
|
120
|
+
/** Walk all children at the given nesting level. */
|
|
121
|
+
function walkChildren(node: TreeSitterNode, nestingLevel: number, walkFn: WalkFn): void {
|
|
122
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
123
|
+
walkFn(node.child(i), nestingLevel, false);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Handle logical operators in binary expressions. Returns true if handled. */
|
|
128
|
+
function handleLogicalOperator(
|
|
129
|
+
node: TreeSitterNode,
|
|
130
|
+
type: string,
|
|
131
|
+
rules: ComplexityRules,
|
|
132
|
+
acc: ComplexityAccumulator,
|
|
133
|
+
nestingLevel: number,
|
|
134
|
+
walkFn: WalkFn,
|
|
135
|
+
): boolean {
|
|
136
|
+
if (type !== rules.logicalNodeType) return false;
|
|
137
|
+
|
|
138
|
+
const op = node.child(1)?.type;
|
|
139
|
+
if (!op || !rules.logicalOperators.has(op)) return false;
|
|
140
|
+
|
|
141
|
+
acc.cyclomatic++;
|
|
142
|
+
|
|
143
|
+
// Cognitive: +1 only when operator changes from the previous sibling sequence
|
|
144
|
+
const parent = node.parent;
|
|
145
|
+
const sameSequence = parent?.type === rules.logicalNodeType && parent.child(1)?.type === op;
|
|
146
|
+
if (!sameSequence) acc.cognitive++;
|
|
147
|
+
|
|
148
|
+
walkChildren(node, nestingLevel, walkFn);
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Handle else clause wrapping an if (Pattern A: JS/C#/Rust). Returns true if handled. */
|
|
153
|
+
function handleElseClause(
|
|
154
|
+
node: TreeSitterNode,
|
|
155
|
+
type: string,
|
|
156
|
+
rules: ComplexityRules,
|
|
157
|
+
acc: ComplexityAccumulator,
|
|
158
|
+
nestingLevel: number,
|
|
159
|
+
walkFn: WalkFn,
|
|
160
|
+
): boolean {
|
|
161
|
+
if (!rules.elseNodeType || type !== rules.elseNodeType) return false;
|
|
162
|
+
|
|
163
|
+
const firstChild = node.namedChild(0);
|
|
164
|
+
if (firstChild && firstChild.type === rules.ifNodeType) {
|
|
165
|
+
// else-if: the if_statement child handles its own increment
|
|
166
|
+
walkChildren(node, nestingLevel, walkFn);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
// Plain else
|
|
170
|
+
acc.cognitive++;
|
|
171
|
+
walkChildren(node, nestingLevel, walkFn);
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Detect and handle else-if patterns (Patterns A, B, C). Returns true if handled. */
|
|
176
|
+
function handleElseIf(
|
|
177
|
+
node: TreeSitterNode,
|
|
178
|
+
type: string,
|
|
179
|
+
rules: ComplexityRules,
|
|
180
|
+
acc: ComplexityAccumulator,
|
|
181
|
+
nestingLevel: number,
|
|
182
|
+
walkFn: WalkFn,
|
|
183
|
+
): boolean {
|
|
184
|
+
// Pattern B: explicit elif node (Python/Ruby/PHP)
|
|
185
|
+
if (rules.elifNodeType && type === rules.elifNodeType) {
|
|
186
|
+
acc.cognitive++;
|
|
187
|
+
acc.cyclomatic++;
|
|
188
|
+
walkChildren(node, nestingLevel, walkFn);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Detect else-if via Pattern A or C
|
|
193
|
+
if (type === rules.ifNodeType) {
|
|
194
|
+
let isElseIf = false;
|
|
195
|
+
if (rules.elseViaAlternative) {
|
|
196
|
+
// Pattern C (Go/Java): if_statement is the alternative of parent if_statement
|
|
197
|
+
isElseIf =
|
|
198
|
+
node.parent?.type === rules.ifNodeType &&
|
|
199
|
+
node.parent.childForFieldName('alternative')?.id === node.id;
|
|
200
|
+
} else if (rules.elseNodeType) {
|
|
201
|
+
// Pattern A (JS/C#/Rust): if_statement inside else_clause
|
|
202
|
+
isElseIf = node.parent?.type === rules.elseNodeType;
|
|
203
|
+
}
|
|
204
|
+
if (isElseIf) {
|
|
205
|
+
acc.cognitive++;
|
|
206
|
+
acc.cyclomatic++;
|
|
207
|
+
walkChildren(node, nestingLevel, walkFn);
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Handle branch/control flow nodes. Returns true if handled. */
|
|
216
|
+
function handleBranchNode(
|
|
217
|
+
node: TreeSitterNode,
|
|
218
|
+
type: string,
|
|
219
|
+
rules: ComplexityRules,
|
|
220
|
+
acc: ComplexityAccumulator,
|
|
221
|
+
nestingLevel: number,
|
|
222
|
+
walkFn: WalkFn,
|
|
223
|
+
): boolean {
|
|
224
|
+
if (!rules.branchNodes.has(type) || node.childCount === 0) return false;
|
|
225
|
+
|
|
226
|
+
if (handleElseClause(node, type, rules, acc, nestingLevel, walkFn)) return true;
|
|
227
|
+
if (handleElseIf(node, type, rules, acc, nestingLevel, walkFn)) return true;
|
|
228
|
+
|
|
229
|
+
// Regular branch node
|
|
230
|
+
acc.cognitive += 1 + nestingLevel; // structural + nesting
|
|
231
|
+
acc.cyclomatic++;
|
|
232
|
+
|
|
233
|
+
// Switch-like nodes don't add cyclomatic themselves (cases do)
|
|
234
|
+
if (rules.switchLikeNodes?.has(type)) {
|
|
235
|
+
acc.cyclomatic--;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (rules.nestingNodes.has(type)) {
|
|
239
|
+
walkChildren(node, nestingLevel + 1, walkFn);
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Handle Pattern C plain else: block is the alternative of an if_statement (Go/Java). */
|
|
247
|
+
function handlePatternCElse(
|
|
248
|
+
node: TreeSitterNode,
|
|
249
|
+
type: string,
|
|
250
|
+
rules: ComplexityRules,
|
|
251
|
+
acc: ComplexityAccumulator,
|
|
252
|
+
nestingLevel: number,
|
|
253
|
+
walkFn: WalkFn,
|
|
254
|
+
): boolean {
|
|
255
|
+
if (
|
|
256
|
+
!rules.elseViaAlternative ||
|
|
257
|
+
type === rules.ifNodeType ||
|
|
258
|
+
node.parent?.type !== rules.ifNodeType ||
|
|
259
|
+
node.parent.childForFieldName('alternative')?.id !== node.id
|
|
260
|
+
) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
acc.cognitive++;
|
|
265
|
+
walkChildren(node, nestingLevel, walkFn);
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
|
|
112
269
|
export function computeFunctionComplexity(
|
|
113
270
|
functionNode: TreeSitterNode,
|
|
114
271
|
language: string,
|
|
@@ -116,158 +273,38 @@ export function computeFunctionComplexity(
|
|
|
116
273
|
const rules = COMPLEXITY_RULES.get(language) as ComplexityRules | undefined;
|
|
117
274
|
if (!rules) return null;
|
|
118
275
|
|
|
119
|
-
|
|
120
|
-
let cyclomatic = 1; // McCabe starts at 1
|
|
121
|
-
let maxNesting = 0;
|
|
276
|
+
const acc: ComplexityAccumulator = { cognitive: 0, cyclomatic: 1, maxNesting: 0 };
|
|
122
277
|
|
|
123
278
|
function walk(node: TreeSitterNode | null, nestingLevel: number, isTopFunction: boolean): void {
|
|
124
279
|
if (!node) return;
|
|
125
280
|
|
|
126
281
|
const type = node.type;
|
|
127
282
|
|
|
128
|
-
|
|
129
|
-
if (nestingLevel > maxNesting) maxNesting = nestingLevel;
|
|
130
|
-
|
|
131
|
-
// Handle logical operators in binary expressions
|
|
132
|
-
if (type === rules?.logicalNodeType) {
|
|
133
|
-
const op = node.child(1)?.type;
|
|
134
|
-
if (op && rules?.logicalOperators.has(op)) {
|
|
135
|
-
// Cyclomatic: +1 for every logical operator
|
|
136
|
-
cyclomatic++;
|
|
137
|
-
|
|
138
|
-
// Cognitive: +1 only when operator changes from the previous sibling sequence
|
|
139
|
-
// Walk up to check if parent is same type with same operator
|
|
140
|
-
const parent = node.parent;
|
|
141
|
-
let sameSequence = false;
|
|
142
|
-
if (parent && parent.type === rules?.logicalNodeType) {
|
|
143
|
-
const parentOp = parent.child(1)?.type;
|
|
144
|
-
if (parentOp === op) {
|
|
145
|
-
sameSequence = true;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
if (!sameSequence) {
|
|
149
|
-
cognitive++;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Walk children manually to avoid double-counting
|
|
153
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
154
|
-
walk(node.child(i), nestingLevel, false);
|
|
155
|
-
}
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
283
|
+
if (nestingLevel > acc.maxNesting) acc.maxNesting = nestingLevel;
|
|
159
284
|
|
|
160
|
-
|
|
161
|
-
if (type === rules?.optionalChainType) {
|
|
162
|
-
cyclomatic++;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Handle branch/control flow nodes (skip keyword leaf tokens like Ruby's `if`)
|
|
166
|
-
if (rules?.branchNodes.has(type) && node.childCount > 0) {
|
|
167
|
-
// Pattern A: else clause wraps if (JS/C#/Rust)
|
|
168
|
-
if (rules?.elseNodeType && type === rules?.elseNodeType) {
|
|
169
|
-
const firstChild = node.namedChild(0);
|
|
170
|
-
if (firstChild && firstChild.type === rules?.ifNodeType) {
|
|
171
|
-
// else-if: the if_statement child handles its own increment
|
|
172
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
173
|
-
walk(node.child(i), nestingLevel, false);
|
|
174
|
-
}
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
// Plain else
|
|
178
|
-
cognitive++;
|
|
179
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
180
|
-
walk(node.child(i), nestingLevel, false);
|
|
181
|
-
}
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
285
|
+
if (handleLogicalOperator(node, type, rules!, acc, nestingLevel, walk)) return;
|
|
184
286
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
cognitive++;
|
|
188
|
-
cyclomatic++;
|
|
189
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
190
|
-
walk(node.child(i), nestingLevel, false);
|
|
191
|
-
}
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
287
|
+
// Optional chaining (cyclomatic only)
|
|
288
|
+
if (type === rules!.optionalChainType) acc.cyclomatic++;
|
|
194
289
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (type === rules?.ifNodeType) {
|
|
198
|
-
if (rules?.elseViaAlternative) {
|
|
199
|
-
// Pattern C (Go/Java): if_statement is the alternative of parent if_statement
|
|
200
|
-
isElseIf =
|
|
201
|
-
node.parent?.type === rules?.ifNodeType &&
|
|
202
|
-
node.parent.childForFieldName('alternative')?.id === node.id;
|
|
203
|
-
} else if (rules?.elseNodeType) {
|
|
204
|
-
// Pattern A (JS/C#/Rust): if_statement inside else_clause
|
|
205
|
-
isElseIf = node.parent?.type === rules?.elseNodeType;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
290
|
+
if (handleBranchNode(node, type, rules!, acc, nestingLevel, walk)) return;
|
|
291
|
+
if (handlePatternCElse(node, type, rules!, acc, nestingLevel, walk)) return;
|
|
208
292
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
cyclomatic++;
|
|
212
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
213
|
-
walk(node.child(i), nestingLevel, false);
|
|
214
|
-
}
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
293
|
+
// Case nodes (cyclomatic only, skip keyword leaves)
|
|
294
|
+
if (rules!.caseNodes.has(type) && node.childCount > 0) acc.cyclomatic++;
|
|
217
295
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
// Switch-like nodes don't add cyclomatic themselves (cases do)
|
|
223
|
-
if (rules?.switchLikeNodes?.has(type)) {
|
|
224
|
-
cyclomatic--; // Undo the ++ above; cases handle cyclomatic
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (rules?.nestingNodes.has(type)) {
|
|
228
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
229
|
-
walk(node.child(i), nestingLevel + 1, false);
|
|
230
|
-
}
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Pattern C plain else: block that is the alternative of an if_statement (Go/Java)
|
|
236
|
-
if (
|
|
237
|
-
rules?.elseViaAlternative &&
|
|
238
|
-
type !== rules?.ifNodeType &&
|
|
239
|
-
node.parent?.type === rules?.ifNodeType &&
|
|
240
|
-
node.parent.childForFieldName('alternative')?.id === node.id
|
|
241
|
-
) {
|
|
242
|
-
cognitive++;
|
|
243
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
244
|
-
walk(node.child(i), nestingLevel, false);
|
|
245
|
-
}
|
|
296
|
+
// Nested function definitions (increase nesting)
|
|
297
|
+
if (!isTopFunction && rules!.functionNodes.has(type)) {
|
|
298
|
+
walkChildren(node, nestingLevel + 1, walk);
|
|
246
299
|
return;
|
|
247
300
|
}
|
|
248
301
|
|
|
249
|
-
|
|
250
|
-
if (rules?.caseNodes.has(type) && node.childCount > 0) {
|
|
251
|
-
cyclomatic++;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Handle nested function definitions (increase nesting)
|
|
255
|
-
if (!isTopFunction && rules?.functionNodes.has(type)) {
|
|
256
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
257
|
-
walk(node.child(i), nestingLevel + 1, false);
|
|
258
|
-
}
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Walk children
|
|
263
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
264
|
-
walk(node.child(i), nestingLevel, false);
|
|
265
|
-
}
|
|
302
|
+
walkChildren(node, nestingLevel, walk);
|
|
266
303
|
}
|
|
267
304
|
|
|
268
305
|
walk(functionNode, 0, true);
|
|
269
306
|
|
|
270
|
-
return { cognitive, cyclomatic, maxNesting };
|
|
307
|
+
return { cognitive: acc.cognitive, cyclomatic: acc.cyclomatic, maxNesting: acc.maxNesting };
|
|
271
308
|
}
|
|
272
309
|
|
|
273
310
|
// ─── Merged Single-Pass Computation ───────────────────────────────────────
|