@optave/codegraph 3.1.4 → 3.2.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.
Files changed (210) hide show
  1. package/README.md +29 -72
  2. package/package.json +10 -8
  3. package/src/ast-analysis/engine.js +260 -246
  4. package/src/ast-analysis/shared.js +2 -14
  5. package/src/ast-analysis/visitors/cfg-visitor.js +635 -649
  6. package/src/ast-analysis/visitors/complexity-visitor.js +135 -139
  7. package/src/ast-analysis/visitors/dataflow-visitor.js +230 -224
  8. package/src/cli/commands/ast.js +4 -7
  9. package/src/cli/commands/audit.js +11 -11
  10. package/src/cli/commands/batch.js +6 -5
  11. package/src/cli/commands/branch-compare.js +1 -1
  12. package/src/cli/commands/brief.js +12 -0
  13. package/src/cli/commands/build.js +1 -1
  14. package/src/cli/commands/cfg.js +5 -8
  15. package/src/cli/commands/check.js +28 -36
  16. package/src/cli/commands/children.js +9 -7
  17. package/src/cli/commands/co-change.js +5 -3
  18. package/src/cli/commands/communities.js +2 -6
  19. package/src/cli/commands/complexity.js +5 -3
  20. package/src/cli/commands/context.js +9 -8
  21. package/src/cli/commands/cycles.js +12 -8
  22. package/src/cli/commands/dataflow.js +5 -8
  23. package/src/cli/commands/deps.js +9 -8
  24. package/src/cli/commands/diff-impact.js +2 -6
  25. package/src/cli/commands/embed.js +1 -1
  26. package/src/cli/commands/export.js +34 -31
  27. package/src/cli/commands/exports.js +2 -6
  28. package/src/cli/commands/flow.js +5 -8
  29. package/src/cli/commands/fn-impact.js +9 -8
  30. package/src/cli/commands/impact.js +2 -6
  31. package/src/cli/commands/info.js +2 -2
  32. package/src/cli/commands/map.js +1 -1
  33. package/src/cli/commands/mcp.js +1 -1
  34. package/src/cli/commands/models.js +1 -1
  35. package/src/cli/commands/owners.js +5 -3
  36. package/src/cli/commands/path.js +2 -2
  37. package/src/cli/commands/plot.js +40 -31
  38. package/src/cli/commands/query.js +9 -8
  39. package/src/cli/commands/registry.js +2 -2
  40. package/src/cli/commands/roles.js +5 -8
  41. package/src/cli/commands/search.js +9 -3
  42. package/src/cli/commands/sequence.js +5 -8
  43. package/src/cli/commands/snapshot.js +6 -1
  44. package/src/cli/commands/stats.js +1 -1
  45. package/src/cli/commands/structure.js +5 -4
  46. package/src/cli/commands/triage.js +41 -30
  47. package/src/cli/commands/watch.js +1 -1
  48. package/src/cli/commands/where.js +2 -6
  49. package/src/cli/index.js +11 -5
  50. package/src/cli/shared/open-graph.js +13 -0
  51. package/src/cli/shared/options.js +22 -2
  52. package/src/cli.js +1 -1
  53. package/src/db/connection.js +140 -11
  54. package/src/{db.js → db/index.js} +12 -5
  55. package/src/db/migrations.js +42 -65
  56. package/src/db/query-builder.js +72 -9
  57. package/src/db/repository/base.js +1 -1
  58. package/src/db/repository/graph-read.js +3 -3
  59. package/src/db/repository/in-memory-repository.js +30 -28
  60. package/src/db/repository/nodes.js +10 -17
  61. package/src/domain/analysis/brief.js +155 -0
  62. package/src/domain/analysis/context.js +392 -0
  63. package/src/domain/analysis/dependencies.js +395 -0
  64. package/src/{analysis → domain/analysis}/exports.js +11 -6
  65. package/src/domain/analysis/impact.js +581 -0
  66. package/src/domain/analysis/module-map.js +348 -0
  67. package/src/{analysis → domain/analysis}/roles.js +12 -9
  68. package/src/{analysis → domain/analysis}/symbol-lookup.js +19 -11
  69. package/src/{builder → domain/graph/builder}/helpers.js +4 -4
  70. package/src/{builder → domain/graph/builder}/incremental.js +119 -93
  71. package/src/domain/graph/builder/pipeline.js +156 -0
  72. package/src/domain/graph/builder/stages/build-edges.js +376 -0
  73. package/src/{builder → domain/graph/builder}/stages/build-structure.js +4 -4
  74. package/src/{builder → domain/graph/builder}/stages/collect-files.js +2 -2
  75. package/src/{builder → domain/graph/builder}/stages/detect-changes.js +204 -183
  76. package/src/{builder → domain/graph/builder}/stages/finalize.js +4 -4
  77. package/src/domain/graph/builder/stages/insert-nodes.js +203 -0
  78. package/src/{builder → domain/graph/builder}/stages/parse-files.js +2 -2
  79. package/src/{builder → domain/graph/builder}/stages/resolve-imports.js +1 -1
  80. package/src/{builder → domain/graph/builder}/stages/run-analyses.js +2 -2
  81. package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
  82. package/src/{cycles.js → domain/graph/cycles.js} +4 -4
  83. package/src/{journal.js → domain/graph/journal.js} +1 -1
  84. package/src/{resolve.js → domain/graph/resolve.js} +2 -2
  85. package/src/{watcher.js → domain/graph/watcher.js} +7 -7
  86. package/src/{parser.js → domain/parser.js} +24 -15
  87. package/src/{queries.js → domain/queries.js} +17 -16
  88. package/src/{embeddings → domain/search}/generator.js +3 -3
  89. package/src/{embeddings → domain/search}/models.js +2 -2
  90. package/src/{embeddings → domain/search}/search/cli-formatter.js +1 -1
  91. package/src/{embeddings → domain/search}/search/filters.js +9 -5
  92. package/src/{embeddings → domain/search}/search/hybrid.js +1 -1
  93. package/src/{embeddings → domain/search}/search/keyword.js +13 -6
  94. package/src/{embeddings → domain/search}/search/prepare.js +15 -7
  95. package/src/{embeddings → domain/search}/search/semantic.js +1 -1
  96. package/src/{embeddings → domain/search}/strategies/structured.js +1 -1
  97. package/src/extractors/csharp.js +224 -207
  98. package/src/extractors/go.js +176 -172
  99. package/src/extractors/hcl.js +94 -78
  100. package/src/extractors/java.js +213 -207
  101. package/src/extractors/javascript.js +275 -305
  102. package/src/extractors/php.js +234 -221
  103. package/src/extractors/python.js +252 -250
  104. package/src/extractors/ruby.js +192 -185
  105. package/src/extractors/rust.js +182 -167
  106. package/src/{ast.js → features/ast.js} +13 -11
  107. package/src/{audit.js → features/audit.js} +20 -46
  108. package/src/{batch.js → features/batch.js} +5 -5
  109. package/src/{boundaries.js → features/boundaries.js} +100 -85
  110. package/src/{branch-compare.js → features/branch-compare.js} +3 -3
  111. package/src/{cfg.js → features/cfg.js} +141 -150
  112. package/src/{check.js → features/check.js} +13 -30
  113. package/src/{cochange.js → features/cochange.js} +5 -5
  114. package/src/{communities.js → features/communities.js} +72 -57
  115. package/src/{complexity.js → features/complexity.js} +154 -143
  116. package/src/{dataflow.js → features/dataflow.js} +155 -158
  117. package/src/{export.js → features/export.js} +6 -6
  118. package/src/{flow.js → features/flow.js} +4 -4
  119. package/src/{viewer.js → features/graph-enrichment.js} +8 -8
  120. package/src/{manifesto.js → features/manifesto.js} +15 -12
  121. package/src/{owners.js → features/owners.js} +6 -5
  122. package/src/features/sequence.js +300 -0
  123. package/src/features/shared/find-nodes.js +31 -0
  124. package/src/{snapshot.js → features/snapshot.js} +3 -3
  125. package/src/{structure.js → features/structure.js} +139 -108
  126. package/src/features/triage.js +141 -0
  127. package/src/graph/builders/dependency.js +33 -14
  128. package/src/graph/classifiers/risk.js +3 -2
  129. package/src/graph/classifiers/roles.js +6 -3
  130. package/src/index.cjs +16 -0
  131. package/src/index.js +40 -39
  132. package/src/{native.js → infrastructure/native.js} +1 -1
  133. package/src/mcp/middleware.js +1 -1
  134. package/src/mcp/server.js +68 -59
  135. package/src/mcp/tool-registry.js +15 -2
  136. package/src/mcp/tools/ast-query.js +1 -1
  137. package/src/mcp/tools/audit.js +1 -1
  138. package/src/mcp/tools/batch-query.js +1 -1
  139. package/src/mcp/tools/branch-compare.js +3 -1
  140. package/src/mcp/tools/brief.js +8 -0
  141. package/src/mcp/tools/cfg.js +1 -1
  142. package/src/mcp/tools/check.js +3 -3
  143. package/src/mcp/tools/co-changes.js +1 -1
  144. package/src/mcp/tools/code-owners.js +1 -1
  145. package/src/mcp/tools/communities.js +1 -1
  146. package/src/mcp/tools/complexity.js +1 -1
  147. package/src/mcp/tools/dataflow.js +2 -2
  148. package/src/mcp/tools/execution-flow.js +2 -2
  149. package/src/mcp/tools/export-graph.js +2 -2
  150. package/src/mcp/tools/find-cycles.js +2 -2
  151. package/src/mcp/tools/index.js +2 -0
  152. package/src/mcp/tools/list-repos.js +1 -1
  153. package/src/mcp/tools/sequence.js +1 -1
  154. package/src/mcp/tools/structure.js +1 -1
  155. package/src/mcp/tools/triage.js +2 -2
  156. package/src/{commands → presentation}/audit.js +2 -2
  157. package/src/{commands → presentation}/batch.js +1 -1
  158. package/src/{commands → presentation}/branch-compare.js +2 -2
  159. package/src/presentation/brief.js +51 -0
  160. package/src/{commands → presentation}/cfg.js +1 -1
  161. package/src/{commands → presentation}/check.js +2 -2
  162. package/src/{commands → presentation}/communities.js +1 -1
  163. package/src/{commands → presentation}/complexity.js +1 -1
  164. package/src/{commands → presentation}/dataflow.js +1 -1
  165. package/src/{commands → presentation}/flow.js +2 -2
  166. package/src/{commands → presentation}/manifesto.js +1 -1
  167. package/src/{commands → presentation}/owners.js +1 -1
  168. package/src/presentation/queries-cli/exports.js +53 -0
  169. package/src/presentation/queries-cli/impact.js +214 -0
  170. package/src/presentation/queries-cli/index.js +5 -0
  171. package/src/presentation/queries-cli/inspect.js +329 -0
  172. package/src/presentation/queries-cli/overview.js +196 -0
  173. package/src/presentation/queries-cli/path.js +65 -0
  174. package/src/presentation/queries-cli.js +27 -0
  175. package/src/{commands → presentation}/query.js +1 -1
  176. package/src/presentation/result-formatter.js +126 -3
  177. package/src/{commands → presentation}/sequence.js +2 -2
  178. package/src/{commands → presentation}/structure.js +1 -1
  179. package/src/presentation/table.js +0 -8
  180. package/src/{commands → presentation}/triage.js +1 -1
  181. package/src/{constants.js → shared/constants.js} +1 -1
  182. package/src/shared/file-utils.js +2 -2
  183. package/src/shared/generators.js +9 -5
  184. package/src/shared/hierarchy.js +1 -1
  185. package/src/{kinds.js → shared/kinds.js} +1 -1
  186. package/src/analysis/context.js +0 -408
  187. package/src/analysis/dependencies.js +0 -341
  188. package/src/analysis/impact.js +0 -463
  189. package/src/analysis/module-map.js +0 -322
  190. package/src/builder/pipeline.js +0 -130
  191. package/src/builder/stages/build-edges.js +0 -297
  192. package/src/builder/stages/insert-nodes.js +0 -195
  193. package/src/mcp.js +0 -2
  194. package/src/queries-cli.js +0 -866
  195. package/src/sequence.js +0 -289
  196. package/src/triage.js +0 -126
  197. /package/src/{builder → domain/graph/builder}/context.js +0 -0
  198. /package/src/{builder.js → domain/graph/builder.js} +0 -0
  199. /package/src/{embeddings → domain/search}/index.js +0 -0
  200. /package/src/{embeddings → domain/search}/stores/fts5.js +0 -0
  201. /package/src/{embeddings → domain/search}/stores/sqlite-blob.js +0 -0
  202. /package/src/{embeddings → domain/search}/strategies/source.js +0 -0
  203. /package/src/{embeddings → domain/search}/strategies/text-utils.js +0 -0
  204. /package/src/{config.js → infrastructure/config.js} +0 -0
  205. /package/src/{logger.js → infrastructure/logger.js} +0 -0
  206. /package/src/{registry.js → infrastructure/registry.js} +0 -0
  207. /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
  208. /package/src/{commands → presentation}/cochange.js +0 -0
  209. /package/src/{errors.js → shared/errors.js} +0 -0
  210. /package/src/{paginate.js → shared/paginate.js} +0 -0
@@ -12,6 +12,122 @@ import {
12
12
  computeMaintainabilityIndex,
13
13
  } from '../metrics.js';
14
14
 
15
+ // ── Halstead classification ─────────────────────────────────────────────
16
+
17
+ function classifyHalstead(node, hRules, acc) {
18
+ const type = node.type;
19
+ if (hRules.skipTypes.has(type)) acc.halsteadSkipDepth++;
20
+ if (acc.halsteadSkipDepth > 0) return;
21
+
22
+ if (hRules.compoundOperators.has(type)) {
23
+ acc.operators.set(type, (acc.operators.get(type) || 0) + 1);
24
+ }
25
+ if (node.childCount === 0) {
26
+ if (hRules.operatorLeafTypes.has(type)) {
27
+ acc.operators.set(type, (acc.operators.get(type) || 0) + 1);
28
+ } else if (hRules.operandLeafTypes.has(type)) {
29
+ const text = node.text;
30
+ acc.operands.set(text, (acc.operands.get(text) || 0) + 1);
31
+ }
32
+ }
33
+ }
34
+
35
+ // ── Branch complexity classification ────────────────────────────────────
36
+
37
+ function classifyBranchNode(node, type, nestingLevel, cRules, acc) {
38
+ // Pattern A: else clause wraps if (JS/C#/Rust)
39
+ if (cRules.elseNodeType && type === cRules.elseNodeType) {
40
+ const firstChild = node.namedChild(0);
41
+ if (firstChild && firstChild.type === cRules.ifNodeType) {
42
+ // else-if: the if_statement child handles its own increment
43
+ return;
44
+ }
45
+ acc.cognitive++;
46
+ return;
47
+ }
48
+
49
+ // Pattern B: explicit elif node (Python/Ruby/PHP)
50
+ if (cRules.elifNodeType && type === cRules.elifNodeType) {
51
+ acc.cognitive++;
52
+ acc.cyclomatic++;
53
+ return;
54
+ }
55
+
56
+ // Detect else-if via Pattern A or C
57
+ let isElseIf = false;
58
+ if (type === cRules.ifNodeType) {
59
+ if (cRules.elseViaAlternative) {
60
+ isElseIf =
61
+ node.parent?.type === cRules.ifNodeType &&
62
+ node.parent.childForFieldName('alternative')?.id === node.id;
63
+ } else if (cRules.elseNodeType) {
64
+ isElseIf = node.parent?.type === cRules.elseNodeType;
65
+ }
66
+ }
67
+
68
+ if (isElseIf) {
69
+ acc.cognitive++;
70
+ acc.cyclomatic++;
71
+ return;
72
+ }
73
+
74
+ // Regular branch node
75
+ acc.cognitive += 1 + nestingLevel;
76
+ acc.cyclomatic++;
77
+
78
+ if (cRules.switchLikeNodes?.has(type)) {
79
+ acc.cyclomatic--;
80
+ }
81
+ }
82
+
83
+ // ── Plain-else detection (Pattern C: Go/Java) ──────────────────────────
84
+
85
+ function classifyPlainElse(node, type, cRules, acc) {
86
+ if (
87
+ cRules.elseViaAlternative &&
88
+ type !== cRules.ifNodeType &&
89
+ node.parent?.type === cRules.ifNodeType &&
90
+ node.parent.childForFieldName('alternative')?.id === node.id
91
+ ) {
92
+ acc.cognitive++;
93
+ }
94
+ }
95
+
96
+ // ── Result collection ───────────────────────────────────────────────────
97
+
98
+ function collectResult(funcNode, acc, hRules, langId) {
99
+ const halstead =
100
+ hRules && acc.operators && acc.operands
101
+ ? computeHalsteadDerived(acc.operators, acc.operands)
102
+ : null;
103
+ const loc = computeLOCMetrics(funcNode, langId);
104
+ const volume = halstead ? halstead.volume : 0;
105
+ const commentRatio = loc.loc > 0 ? loc.commentLines / loc.loc : 0;
106
+ const mi = computeMaintainabilityIndex(volume, acc.cyclomatic, loc.sloc, commentRatio);
107
+
108
+ return {
109
+ cognitive: acc.cognitive,
110
+ cyclomatic: acc.cyclomatic,
111
+ maxNesting: acc.maxNesting,
112
+ halstead,
113
+ loc,
114
+ mi,
115
+ };
116
+ }
117
+
118
+ function resetAccumulators(hRules) {
119
+ return {
120
+ cognitive: 0,
121
+ cyclomatic: 1,
122
+ maxNesting: 0,
123
+ operators: hRules ? new Map() : null,
124
+ operands: hRules ? new Map() : null,
125
+ halsteadSkipDepth: 0,
126
+ };
127
+ }
128
+
129
+ // ── Visitor factory ─────────────────────────────────────────────────────
130
+
15
131
  /**
16
132
  * Create a complexity visitor for use with walkWithVisitors.
17
133
  *
@@ -28,43 +144,12 @@ import {
28
144
  export function createComplexityVisitor(cRules, hRules, options = {}) {
29
145
  const { fileLevelWalk = false, langId = null } = options;
30
146
 
31
- // Per-function accumulators
32
- let cognitive = 0;
33
- let cyclomatic = 1;
34
- let maxNesting = 0;
35
- let operators = hRules ? new Map() : null;
36
- let operands = hRules ? new Map() : null;
37
- let halsteadSkipDepth = 0;
38
-
39
- // In file-level mode, we only count when inside a function
147
+ let acc = resetAccumulators(hRules);
40
148
  let activeFuncNode = null;
41
149
  let activeFuncName = null;
42
- // Nesting depth relative to the active function (for nested functions)
43
150
  let funcDepth = 0;
44
-
45
- // Collected results (one per function)
46
151
  const results = [];
47
152
 
48
- function reset() {
49
- cognitive = 0;
50
- cyclomatic = 1;
51
- maxNesting = 0;
52
- operators = hRules ? new Map() : null;
53
- operands = hRules ? new Map() : null;
54
- halsteadSkipDepth = 0;
55
- }
56
-
57
- function collectResult(funcNode) {
58
- const halstead =
59
- hRules && operators && operands ? computeHalsteadDerived(operators, operands) : null;
60
- const loc = computeLOCMetrics(funcNode, langId);
61
- const volume = halstead ? halstead.volume : 0;
62
- const commentRatio = loc.loc > 0 ? loc.commentLines / loc.loc : 0;
63
- const mi = computeMaintainabilityIndex(volume, cyclomatic, loc.sloc, commentRatio);
64
-
65
- return { cognitive, cyclomatic, maxNesting, halstead, loc, mi };
66
- }
67
-
68
153
  return {
69
154
  name: 'complexity',
70
155
  functionNodeTypes: cRules.functionNodes,
@@ -72,17 +157,14 @@ export function createComplexityVisitor(cRules, hRules, options = {}) {
72
157
  enterFunction(funcNode, funcName, _context) {
73
158
  if (fileLevelWalk) {
74
159
  if (!activeFuncNode) {
75
- // Top-level function: start fresh
76
- reset();
160
+ acc = resetAccumulators(hRules);
77
161
  activeFuncNode = funcNode;
78
162
  activeFuncName = funcName;
79
163
  funcDepth = 0;
80
164
  } else {
81
- // Nested function: increase nesting for complexity
82
165
  funcDepth++;
83
166
  }
84
167
  } else {
85
- // Function-level mode: track nested functions for correct nesting depth
86
168
  funcDepth++;
87
169
  }
88
170
  },
@@ -90,11 +172,10 @@ export function createComplexityVisitor(cRules, hRules, options = {}) {
90
172
  exitFunction(funcNode, _funcName, _context) {
91
173
  if (fileLevelWalk) {
92
174
  if (funcNode === activeFuncNode) {
93
- // Leaving the top-level function: emit result
94
175
  results.push({
95
176
  funcNode,
96
177
  funcName: activeFuncName,
97
- metrics: collectResult(funcNode),
178
+ metrics: collectResult(funcNode, acc, hRules, langId),
98
179
  });
99
180
  activeFuncNode = null;
100
181
  activeFuncName = null;
@@ -107,137 +188,52 @@ export function createComplexityVisitor(cRules, hRules, options = {}) {
107
188
  },
108
189
 
109
190
  enterNode(node, context) {
110
- // In file-level mode, skip nodes outside any function
111
191
  if (fileLevelWalk && !activeFuncNode) return;
112
192
 
113
193
  const type = node.type;
114
194
  const nestingLevel = fileLevelWalk ? context.nestingLevel + funcDepth : context.nestingLevel;
115
195
 
116
- // ── Halstead classification ──
117
- if (hRules) {
118
- if (hRules.skipTypes.has(type)) halsteadSkipDepth++;
119
- if (halsteadSkipDepth === 0) {
120
- if (hRules.compoundOperators.has(type)) {
121
- operators.set(type, (operators.get(type) || 0) + 1);
122
- }
123
- if (node.childCount === 0) {
124
- if (hRules.operatorLeafTypes.has(type)) {
125
- operators.set(type, (operators.get(type) || 0) + 1);
126
- } else if (hRules.operandLeafTypes.has(type)) {
127
- const text = node.text;
128
- operands.set(text, (operands.get(text) || 0) + 1);
129
- }
130
- }
131
- }
132
- }
196
+ if (hRules) classifyHalstead(node, hRules, acc);
133
197
 
134
- // ── Complexity: track nesting depth ──
135
- if (nestingLevel > maxNesting) maxNesting = nestingLevel;
198
+ if (nestingLevel > acc.maxNesting) acc.maxNesting = nestingLevel;
136
199
 
137
- // Handle logical operators in binary expressions
200
+ // Logical operators in binary expressions
138
201
  if (type === cRules.logicalNodeType) {
139
202
  const op = node.child(1)?.type;
140
203
  if (op && cRules.logicalOperators.has(op)) {
141
- cyclomatic++;
204
+ acc.cyclomatic++;
142
205
  const parent = node.parent;
143
206
  let sameSequence = false;
144
207
  if (parent && parent.type === cRules.logicalNodeType) {
145
208
  const parentOp = parent.child(1)?.type;
146
209
  if (parentOp === op) sameSequence = true;
147
210
  }
148
- if (!sameSequence) cognitive++;
149
- // Don't skip children — walker handles recursion
211
+ if (!sameSequence) acc.cognitive++;
150
212
  }
151
213
  }
152
214
 
153
- // Handle optional chaining (cyclomatic only)
154
- if (type === cRules.optionalChainType) {
155
- cyclomatic++;
156
- }
215
+ // Optional chaining (cyclomatic only)
216
+ if (type === cRules.optionalChainType) acc.cyclomatic++;
157
217
 
158
- // Handle branch/control flow nodes (skip keyword leaf tokens)
218
+ // Branch/control flow nodes (skip keyword leaf tokens)
159
219
  if (cRules.branchNodes.has(type) && node.childCount > 0) {
160
- // Pattern A: else clause wraps if (JS/C#/Rust)
161
- if (cRules.elseNodeType && type === cRules.elseNodeType) {
162
- const firstChild = node.namedChild(0);
163
- if (firstChild && firstChild.type === cRules.ifNodeType) {
164
- // else-if: the if_statement child handles its own increment
165
- return;
166
- }
167
- cognitive++;
168
- return;
169
- }
170
-
171
- // Pattern B: explicit elif node (Python/Ruby/PHP)
172
- if (cRules.elifNodeType && type === cRules.elifNodeType) {
173
- cognitive++;
174
- cyclomatic++;
175
- return;
176
- }
177
-
178
- // Detect else-if via Pattern A or C
179
- let isElseIf = false;
180
- if (type === cRules.ifNodeType) {
181
- if (cRules.elseViaAlternative) {
182
- isElseIf =
183
- node.parent?.type === cRules.ifNodeType &&
184
- node.parent.childForFieldName('alternative')?.id === node.id;
185
- } else if (cRules.elseNodeType) {
186
- isElseIf = node.parent?.type === cRules.elseNodeType;
187
- }
188
- }
189
-
190
- if (isElseIf) {
191
- cognitive++;
192
- cyclomatic++;
193
- return;
194
- }
195
-
196
- // Regular branch node
197
- cognitive += 1 + nestingLevel;
198
- cyclomatic++;
199
-
200
- if (cRules.switchLikeNodes?.has(type)) {
201
- cyclomatic--;
202
- }
203
-
204
- // Nesting nodes are handled by the walker's nestingNodeTypes option
205
- // But we still need them to count in complexity — they already do above
206
- }
207
-
208
- // Pattern C plain else: block that is the alternative of an if_statement (Go/Java)
209
- if (
210
- cRules.elseViaAlternative &&
211
- type !== cRules.ifNodeType &&
212
- node.parent?.type === cRules.ifNodeType &&
213
- node.parent.childForFieldName('alternative')?.id === node.id
214
- ) {
215
- cognitive++;
220
+ classifyBranchNode(node, type, nestingLevel, cRules, acc);
216
221
  }
217
222
 
218
- // Handle case nodes (cyclomatic only, skip keyword leaves)
219
- if (cRules.caseNodes.has(type) && node.childCount > 0) {
220
- cyclomatic++;
221
- }
223
+ // Pattern C plain else (Go/Java)
224
+ classifyPlainElse(node, type, cRules, acc);
222
225
 
223
- // Handle nested function definitions (increase nesting)
224
- // In file-level mode funcDepth handles this; in function-level mode the
225
- // nestingNodeTypes option should include function nodes
226
+ // Case nodes (cyclomatic only, skip keyword leaves)
227
+ if (cRules.caseNodes.has(type) && node.childCount > 0) acc.cyclomatic++;
226
228
  },
227
229
 
228
230
  exitNode(node) {
229
- // Decrement skip depth when leaving a skip-type subtree
230
- if (hRules?.skipTypes.has(node.type)) {
231
- halsteadSkipDepth--;
232
- }
231
+ if (hRules?.skipTypes.has(node.type)) acc.halsteadSkipDepth--;
233
232
  },
234
233
 
235
234
  finish() {
236
- if (fileLevelWalk) {
237
- return results;
238
- }
239
- // Function-level mode: return single result (no funcNode reference needed)
240
- return collectResult({ text: '' });
235
+ if (fileLevelWalk) return results;
236
+ return collectResult({ text: '' }, acc, hRules, langId);
241
237
  },
242
238
  };
243
239
  }