@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.
- package/README.md +29 -72
- package/package.json +10 -8
- package/src/ast-analysis/engine.js +260 -246
- package/src/ast-analysis/shared.js +2 -14
- package/src/ast-analysis/visitors/cfg-visitor.js +635 -649
- package/src/ast-analysis/visitors/complexity-visitor.js +135 -139
- package/src/ast-analysis/visitors/dataflow-visitor.js +230 -224
- package/src/cli/commands/ast.js +4 -7
- package/src/cli/commands/audit.js +11 -11
- package/src/cli/commands/batch.js +6 -5
- package/src/cli/commands/branch-compare.js +1 -1
- package/src/cli/commands/brief.js +12 -0
- package/src/cli/commands/build.js +1 -1
- package/src/cli/commands/cfg.js +5 -8
- package/src/cli/commands/check.js +28 -36
- package/src/cli/commands/children.js +9 -7
- package/src/cli/commands/co-change.js +5 -3
- package/src/cli/commands/communities.js +2 -6
- package/src/cli/commands/complexity.js +5 -3
- package/src/cli/commands/context.js +9 -8
- package/src/cli/commands/cycles.js +12 -8
- package/src/cli/commands/dataflow.js +5 -8
- package/src/cli/commands/deps.js +9 -8
- package/src/cli/commands/diff-impact.js +2 -6
- package/src/cli/commands/embed.js +1 -1
- package/src/cli/commands/export.js +34 -31
- package/src/cli/commands/exports.js +2 -6
- package/src/cli/commands/flow.js +5 -8
- package/src/cli/commands/fn-impact.js +9 -8
- package/src/cli/commands/impact.js +2 -6
- package/src/cli/commands/info.js +2 -2
- package/src/cli/commands/map.js +1 -1
- package/src/cli/commands/mcp.js +1 -1
- package/src/cli/commands/models.js +1 -1
- package/src/cli/commands/owners.js +5 -3
- package/src/cli/commands/path.js +2 -2
- package/src/cli/commands/plot.js +40 -31
- package/src/cli/commands/query.js +9 -8
- package/src/cli/commands/registry.js +2 -2
- package/src/cli/commands/roles.js +5 -8
- package/src/cli/commands/search.js +9 -3
- package/src/cli/commands/sequence.js +5 -8
- package/src/cli/commands/snapshot.js +6 -1
- package/src/cli/commands/stats.js +1 -1
- package/src/cli/commands/structure.js +5 -4
- package/src/cli/commands/triage.js +41 -30
- package/src/cli/commands/watch.js +1 -1
- package/src/cli/commands/where.js +2 -6
- package/src/cli/index.js +11 -5
- package/src/cli/shared/open-graph.js +13 -0
- package/src/cli/shared/options.js +22 -2
- package/src/cli.js +1 -1
- package/src/db/connection.js +140 -11
- package/src/{db.js → db/index.js} +12 -5
- package/src/db/migrations.js +42 -65
- package/src/db/query-builder.js +72 -9
- package/src/db/repository/base.js +1 -1
- package/src/db/repository/graph-read.js +3 -3
- package/src/db/repository/in-memory-repository.js +30 -28
- package/src/db/repository/nodes.js +10 -17
- package/src/domain/analysis/brief.js +155 -0
- package/src/domain/analysis/context.js +392 -0
- package/src/domain/analysis/dependencies.js +395 -0
- package/src/{analysis → domain/analysis}/exports.js +11 -6
- package/src/domain/analysis/impact.js +581 -0
- package/src/domain/analysis/module-map.js +348 -0
- package/src/{analysis → domain/analysis}/roles.js +12 -9
- package/src/{analysis → domain/analysis}/symbol-lookup.js +19 -11
- package/src/{builder → domain/graph/builder}/helpers.js +4 -4
- package/src/{builder → domain/graph/builder}/incremental.js +119 -93
- package/src/domain/graph/builder/pipeline.js +156 -0
- package/src/domain/graph/builder/stages/build-edges.js +376 -0
- package/src/{builder → domain/graph/builder}/stages/build-structure.js +4 -4
- package/src/{builder → domain/graph/builder}/stages/collect-files.js +2 -2
- package/src/{builder → domain/graph/builder}/stages/detect-changes.js +204 -183
- package/src/{builder → domain/graph/builder}/stages/finalize.js +4 -4
- package/src/domain/graph/builder/stages/insert-nodes.js +203 -0
- package/src/{builder → domain/graph/builder}/stages/parse-files.js +2 -2
- package/src/{builder → domain/graph/builder}/stages/resolve-imports.js +1 -1
- package/src/{builder → domain/graph/builder}/stages/run-analyses.js +2 -2
- package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
- package/src/{cycles.js → domain/graph/cycles.js} +4 -4
- package/src/{journal.js → domain/graph/journal.js} +1 -1
- package/src/{resolve.js → domain/graph/resolve.js} +2 -2
- package/src/{watcher.js → domain/graph/watcher.js} +7 -7
- package/src/{parser.js → domain/parser.js} +24 -15
- package/src/{queries.js → domain/queries.js} +17 -16
- package/src/{embeddings → domain/search}/generator.js +3 -3
- package/src/{embeddings → domain/search}/models.js +2 -2
- package/src/{embeddings → domain/search}/search/cli-formatter.js +1 -1
- package/src/{embeddings → domain/search}/search/filters.js +9 -5
- package/src/{embeddings → domain/search}/search/hybrid.js +1 -1
- package/src/{embeddings → domain/search}/search/keyword.js +13 -6
- package/src/{embeddings → domain/search}/search/prepare.js +15 -7
- package/src/{embeddings → domain/search}/search/semantic.js +1 -1
- package/src/{embeddings → domain/search}/strategies/structured.js +1 -1
- package/src/extractors/csharp.js +224 -207
- package/src/extractors/go.js +176 -172
- package/src/extractors/hcl.js +94 -78
- package/src/extractors/java.js +213 -207
- package/src/extractors/javascript.js +275 -305
- package/src/extractors/php.js +234 -221
- package/src/extractors/python.js +252 -250
- package/src/extractors/ruby.js +192 -185
- package/src/extractors/rust.js +182 -167
- package/src/{ast.js → features/ast.js} +13 -11
- package/src/{audit.js → features/audit.js} +20 -46
- package/src/{batch.js → features/batch.js} +5 -5
- package/src/{boundaries.js → features/boundaries.js} +100 -85
- package/src/{branch-compare.js → features/branch-compare.js} +3 -3
- package/src/{cfg.js → features/cfg.js} +141 -150
- package/src/{check.js → features/check.js} +13 -30
- package/src/{cochange.js → features/cochange.js} +5 -5
- package/src/{communities.js → features/communities.js} +72 -57
- package/src/{complexity.js → features/complexity.js} +154 -143
- package/src/{dataflow.js → features/dataflow.js} +155 -158
- package/src/{export.js → features/export.js} +6 -6
- package/src/{flow.js → features/flow.js} +4 -4
- package/src/{viewer.js → features/graph-enrichment.js} +8 -8
- package/src/{manifesto.js → features/manifesto.js} +15 -12
- package/src/{owners.js → features/owners.js} +6 -5
- package/src/features/sequence.js +300 -0
- package/src/features/shared/find-nodes.js +31 -0
- package/src/{snapshot.js → features/snapshot.js} +3 -3
- package/src/{structure.js → features/structure.js} +139 -108
- package/src/features/triage.js +141 -0
- package/src/graph/builders/dependency.js +33 -14
- package/src/graph/classifiers/risk.js +3 -2
- package/src/graph/classifiers/roles.js +6 -3
- package/src/index.cjs +16 -0
- package/src/index.js +40 -39
- package/src/{native.js → infrastructure/native.js} +1 -1
- package/src/mcp/middleware.js +1 -1
- package/src/mcp/server.js +68 -59
- package/src/mcp/tool-registry.js +15 -2
- package/src/mcp/tools/ast-query.js +1 -1
- package/src/mcp/tools/audit.js +1 -1
- package/src/mcp/tools/batch-query.js +1 -1
- package/src/mcp/tools/branch-compare.js +3 -1
- package/src/mcp/tools/brief.js +8 -0
- package/src/mcp/tools/cfg.js +1 -1
- package/src/mcp/tools/check.js +3 -3
- package/src/mcp/tools/co-changes.js +1 -1
- package/src/mcp/tools/code-owners.js +1 -1
- package/src/mcp/tools/communities.js +1 -1
- package/src/mcp/tools/complexity.js +1 -1
- package/src/mcp/tools/dataflow.js +2 -2
- package/src/mcp/tools/execution-flow.js +2 -2
- package/src/mcp/tools/export-graph.js +2 -2
- package/src/mcp/tools/find-cycles.js +2 -2
- package/src/mcp/tools/index.js +2 -0
- package/src/mcp/tools/list-repos.js +1 -1
- package/src/mcp/tools/sequence.js +1 -1
- package/src/mcp/tools/structure.js +1 -1
- package/src/mcp/tools/triage.js +2 -2
- package/src/{commands → presentation}/audit.js +2 -2
- package/src/{commands → presentation}/batch.js +1 -1
- package/src/{commands → presentation}/branch-compare.js +2 -2
- package/src/presentation/brief.js +51 -0
- package/src/{commands → presentation}/cfg.js +1 -1
- package/src/{commands → presentation}/check.js +2 -2
- package/src/{commands → presentation}/communities.js +1 -1
- package/src/{commands → presentation}/complexity.js +1 -1
- package/src/{commands → presentation}/dataflow.js +1 -1
- package/src/{commands → presentation}/flow.js +2 -2
- package/src/{commands → presentation}/manifesto.js +1 -1
- package/src/{commands → presentation}/owners.js +1 -1
- package/src/presentation/queries-cli/exports.js +53 -0
- package/src/presentation/queries-cli/impact.js +214 -0
- package/src/presentation/queries-cli/index.js +5 -0
- package/src/presentation/queries-cli/inspect.js +329 -0
- package/src/presentation/queries-cli/overview.js +196 -0
- package/src/presentation/queries-cli/path.js +65 -0
- package/src/presentation/queries-cli.js +27 -0
- package/src/{commands → presentation}/query.js +1 -1
- package/src/presentation/result-formatter.js +126 -3
- package/src/{commands → presentation}/sequence.js +2 -2
- package/src/{commands → presentation}/structure.js +1 -1
- package/src/presentation/table.js +0 -8
- package/src/{commands → presentation}/triage.js +1 -1
- package/src/{constants.js → shared/constants.js} +1 -1
- package/src/shared/file-utils.js +2 -2
- package/src/shared/generators.js +9 -5
- package/src/shared/hierarchy.js +1 -1
- package/src/{kinds.js → shared/kinds.js} +1 -1
- package/src/analysis/context.js +0 -408
- package/src/analysis/dependencies.js +0 -341
- package/src/analysis/impact.js +0 -463
- package/src/analysis/module-map.js +0 -322
- package/src/builder/pipeline.js +0 -130
- package/src/builder/stages/build-edges.js +0 -297
- package/src/builder/stages/insert-nodes.js +0 -195
- package/src/mcp.js +0 -2
- package/src/queries-cli.js +0 -866
- package/src/sequence.js +0 -289
- package/src/triage.js +0 -126
- /package/src/{builder → domain/graph/builder}/context.js +0 -0
- /package/src/{builder.js → domain/graph/builder.js} +0 -0
- /package/src/{embeddings → domain/search}/index.js +0 -0
- /package/src/{embeddings → domain/search}/stores/fts5.js +0 -0
- /package/src/{embeddings → domain/search}/stores/sqlite-blob.js +0 -0
- /package/src/{embeddings → domain/search}/strategies/source.js +0 -0
- /package/src/{embeddings → domain/search}/strategies/text-utils.js +0 -0
- /package/src/{config.js → infrastructure/config.js} +0 -0
- /package/src/{logger.js → infrastructure/logger.js} +0 -0
- /package/src/{registry.js → infrastructure/registry.js} +0 -0
- /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
- /package/src/{commands → presentation}/cochange.js +0 -0
- /package/src/{errors.js → shared/errors.js} +0 -0
- /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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
135
|
-
if (nestingLevel > maxNesting) maxNesting = nestingLevel;
|
|
198
|
+
if (nestingLevel > acc.maxNesting) acc.maxNesting = nestingLevel;
|
|
136
199
|
|
|
137
|
-
//
|
|
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
|
-
//
|
|
154
|
-
if (type === cRules.optionalChainType)
|
|
155
|
-
cyclomatic++;
|
|
156
|
-
}
|
|
215
|
+
// Optional chaining (cyclomatic only)
|
|
216
|
+
if (type === cRules.optionalChainType) acc.cyclomatic++;
|
|
157
217
|
|
|
158
|
-
//
|
|
218
|
+
// Branch/control flow nodes (skip keyword leaf tokens)
|
|
159
219
|
if (cRules.branchNodes.has(type) && node.childCount > 0) {
|
|
160
|
-
|
|
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
|
-
//
|
|
219
|
-
|
|
220
|
-
cyclomatic++;
|
|
221
|
-
}
|
|
223
|
+
// Pattern C plain else (Go/Java)
|
|
224
|
+
classifyPlainElse(node, type, cRules, acc);
|
|
222
225
|
|
|
223
|
-
//
|
|
224
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|