Coverage for metrics / cognitive / go.py: 87%
71 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 15:04 -0800
1"""Cognitive complexity for Go (SonarSource spec)."""
3from __future__ import annotations
5from common import _is_logical_binary, _score_boolean_chain
6from metrics.cognitive.common import make_compute_cognitive
7from parsers.go import is_named_func_literal
9_NESTING_CONTAINERS = {
10 "function_declaration",
11 "method_declaration",
12 "func_literal",
13}
15# Control flow nodes that add 1 + nesting and then recurse at nesting + 1
16_GO_NESTING_INCREMENTORS = {
17 "for_statement",
18 "expression_switch_statement",
19 "type_switch_statement",
20 "select_statement",
21}
24def _walk(node, nesting: int, func_name: str, top_func) -> int:
25 """Recursively walk AST and compute cognitive complexity."""
26 total = 0
28 for child in node.children:
29 if child.type in _NESTING_CONTAINERS and child is not top_func:
30 if child.type == "func_literal" and not is_named_func_literal(child):
31 total += _walk(child, nesting + 1, func_name, top_func)
32 continue
34 if child.type == "if_statement":
35 total += 1 + nesting
36 total += _walk_if(child, nesting, func_name, top_func)
37 continue
39 if child.type in _GO_NESTING_INCREMENTORS:
40 total += 1 + nesting
41 total += _walk(child, nesting + 1, func_name, top_func)
42 continue
44 if _is_logical_binary(child):
45 total += _score_boolean_chain(child)
46 total += _walk_non_boolean(child, nesting, func_name, top_func)
47 continue
49 if child.type == "call_expression":
50 total += _check_recursion(child, func_name)
52 total += _walk(child, nesting, func_name, top_func)
54 return total
57def _walk_if(node, nesting: int, func_name: str, top_func) -> int:
58 """Walk children of a Go if_statement."""
59 total = 0
60 consequence = node.child_by_field_name("consequence")
61 alternative = node.child_by_field_name("alternative")
63 for child in node.children:
64 if consequence is not None and child == consequence:
65 total += _walk(child, nesting + 1, func_name, top_func)
66 elif alternative is not None and child == alternative:
67 if child.type == "if_statement":
68 total += 1
69 total += _walk_if(child, nesting, func_name, top_func)
70 elif child.type == "block":
71 total += 1
72 total += _walk(child, nesting + 1, func_name, top_func)
73 elif _is_logical_binary(child):
74 total += _score_boolean_chain(child)
75 total += _walk_non_boolean(child, nesting, func_name, top_func)
76 elif child.type == "call_expression":
77 total += _check_recursion(child, func_name)
78 total += _walk(child, nesting, func_name, top_func)
79 else:
80 total += _walk(child, nesting, func_name, top_func)
82 return total
85def _walk_non_boolean(node, nesting: int, func_name: str, top_func) -> int:
86 """Walk inside boolean expressions looking only for nested structures and recursion."""
87 total = 0
88 for child in node.children:
89 if _is_logical_binary(child):
90 total += _walk_non_boolean(child, nesting, func_name, top_func)
91 continue
92 if child.type == "call_expression":
93 total += _check_recursion(child, func_name)
94 total += _walk(child, nesting, func_name, top_func)
95 continue
96 total += _walk_non_boolean(child, nesting, func_name, top_func)
97 return total
100def _check_recursion(call_node, func_name: str) -> int:
101 """Check if a call_expression is a recursive call to the enclosing function."""
102 if not func_name:
103 return 0
104 callee = call_node.child_by_field_name("function")
105 if callee and callee.type == "identifier" and callee.text.decode() == func_name:
106 return 1
107 return 0
110compute_cognitive_go = make_compute_cognitive(_walk)