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

1"""Cognitive complexity for Go (SonarSource spec).""" 

2 

3from __future__ import annotations 

4 

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 

8 

9_NESTING_CONTAINERS = { 

10 "function_declaration", 

11 "method_declaration", 

12 "func_literal", 

13} 

14 

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} 

22 

23 

24def _walk(node, nesting: int, func_name: str, top_func) -> int: 

25 """Recursively walk AST and compute cognitive complexity.""" 

26 total = 0 

27 

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 

33 

34 if child.type == "if_statement": 

35 total += 1 + nesting 

36 total += _walk_if(child, nesting, func_name, top_func) 

37 continue 

38 

39 if child.type in _GO_NESTING_INCREMENTORS: 

40 total += 1 + nesting 

41 total += _walk(child, nesting + 1, func_name, top_func) 

42 continue 

43 

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 

48 

49 if child.type == "call_expression": 

50 total += _check_recursion(child, func_name) 

51 

52 total += _walk(child, nesting, func_name, top_func) 

53 

54 return total 

55 

56 

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") 

62 

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) 

81 

82 return total 

83 

84 

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 

98 

99 

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 

108 

109 

110compute_cognitive_go = make_compute_cognitive(_walk)