GCC Code Coverage Report


Directory: ../
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 95.7% 22 / 0 / 23
Functions: 100.0% 4 / 0 / 4
Branches: 57.7% 30 / 0 / 52

src/linter/rules/CyclomaticComplexityRule.cpp
Line Branch Exec Source
1 // Copyright (c) 2021-2026 ChilliBits. All rights reserved.
2
3 #include "CyclomaticComplexityRule.h"
4
5 #include <ast/ASTNodes.h>
6 #include <util/GlobalDefinitions.h>
7
8 namespace spice::compiler {
9
10 // McCabe's original recommendation for the complexity ceiling of a single routine.
11 static constexpr size_t MAX_CYCLOMATIC_COMPLEXITY = 10;
12
13 /**
14 * Count the decision points in a subtree (each if/loop/case branch counts as 1). Walks into every
15 * descendant, including nested lambda bodies, as a deliberate simplification: a lambda dense
16 * enough to matter on its own will usually also push the enclosing function over the threshold.
17 *
18 * @param node Root of the subtree to walk
19 * @return Number of decision points found
20 */
21 177 static size_t countDecisionPoints(const ASTNode *node) { // NOLINT(misc-no-recursion)
22
1/2
✓ Branch 2 → 3 taken 177 times.
✗ Branch 2 → 4 not taken.
177 size_t count = 0;
23
2/4
✓ Branch 13 → 14 taken 166 times.
✗ Branch 13 → 38 not taken.
✓ Branch 19 → 20 taken 166 times.
✗ Branch 19 → 38 not taken.
332 if (is<const IfStmtNode *>(node) || is<const ForLoopNode *>(node) || is<const ForeachLoopNode *>(node) ||
24
7/10
✓ Branch 7 → 8 taken 166 times.
✓ Branch 7 → 38 taken 11 times.
✓ Branch 25 → 26 taken 166 times.
✗ Branch 25 → 38 not taken.
✓ Branch 31 → 32 taken 166 times.
✗ Branch 31 → 38 not taken.
✗ Branch 37 → 38 not taken.
✓ Branch 37 → 39 taken 166 times.
✓ Branch 40 → 41 taken 11 times.
✓ Branch 40 → 42 taken 166 times.
675 is<const WhileLoopNode *>(node) || is<const DoWhileLoopNode *>(node) || is<const CaseBranchNode *>(node))
25 11 count++;
26
27
3/4
✓ Branch 42 → 43 taken 177 times.
✗ Branch 42 → 63 not taken.
✓ Branch 57 → 45 taken 169 times.
✓ Branch 57 → 58 taken 177 times.
523 for (const ASTNode *child : node->getChildren())
28
1/2
✓ Branch 47 → 48 taken 169 times.
✗ Branch 47 → 61 not taken.
169 count += countDecisionPoints(child);
29
30 177 return count;
31 }
32
33 /**
34 * Emit a finding if the given body's cyclomatic complexity exceeds MAX_CYCLOMATIC_COMPLEXITY.
35 *
36 * @param body Function/procedure body (nullptr for declarations without a body)
37 * @param codeLoc Location to attach the finding to
38 * @param name Function/procedure name
39 * @param kindLabel "Function" or "Procedure", for the finding message
40 * @param ruleId Id of the rule producing the finding
41 * @param findings Findings vector to append to
42 */
43 8 static void checkComplexity(const StmtLstNode *body, const CodeLoc &codeLoc, const std::string &name,
44 std::string_view kindLabel, std::string_view ruleId, std::vector<LintFinding> &findings) {
45
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 8 times.
8 if (body == nullptr)
46 return;
47 8 const size_t complexity = 1 + countDecisionPoints(body);
48
2/2
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 31 taken 7 times.
8 if (complexity > MAX_CYCLOMATIC_COMPLEXITY)
49
1/2
✓ Branch 18 → 19 taken 1 time.
✗ Branch 18 → 32 not taken.
1 findings.emplace_back(codeLoc, ruleId, LintSeverity::WARNING,
50
4/8
✓ Branch 10 → 11 taken 1 time.
✗ Branch 10 → 49 not taken.
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 47 not taken.
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 45 not taken.
✓ Branch 13 → 14 taken 1 time.
✗ Branch 13 → 43 not taken.
2 std::string(kindLabel) + " '" + name + "' has a cyclomatic complexity of " +
51
5/10
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 63 not taken.
✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 58 not taken.
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 41 not taken.
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 39 not taken.
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 37 not taken.
4 std::to_string(complexity) + " (max " + std::to_string(MAX_CYCLOMATIC_COMPLEXITY) +
52
1/2
✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 35 not taken.
1 "); consider splitting it up");
53 }
54
55 7 void CyclomaticComplexityRule::checkFctDef(FctDefNode *node, std::vector<LintFinding> &findings) {
56
1/2
✓ Branch 4 → 5 taken 7 times.
✗ Branch 4 → 6 not taken.
7 checkComplexity(node->body, node->codeLoc, node->name->name, "Function", id(), findings);
57 7 }
58
59 1 void CyclomaticComplexityRule::checkProcDef(ProcDefNode *node, std::vector<LintFinding> &findings) {
60
1/2
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 6 not taken.
1 checkComplexity(node->body, node->codeLoc, node->name->name, "Procedure", id(), findings);
61 1 }
62
63 } // namespace spice::compiler
64