Coverage for metrics / npath / go.py: 89%

132 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-08 15:04 -0800

1"""NPath complexity for Go.""" 

2 

3from __future__ import annotations 

4 

5from common import _count_bool_ops 

6from parsers.go import is_named_func_literal 

7 

8 

9def compute_npath_go(func_node) -> int: 

10 """Compute NPath complexity for a Go function node.""" 

11 body = func_node.child_by_field_name("body") 

12 if body and body.type == "block": 

13 return _npath_block_go(body, func_node) 

14 for child in func_node.children: 

15 if child.type == "block": 

16 return _npath_block_go(child, func_node) 

17 return 1 

18 

19 

20def _npath_block_go(block, top_func) -> int: 

21 """Multiply NPath of each child statement in a block.""" 

22 result = 1 

23 for child in block.children: 

24 if child.type == "statement_list": 

25 for stmt in child.children: 

26 result *= _npath_stmt_go(stmt, top_func) 

27 elif child.type not in ("{", "}"): 

28 result *= _npath_stmt_go(child, top_func) 

29 return result 

30 

31 

32def _npath_stmt_go(node, top_func) -> int: 

33 """Dispatch a statement node to its NPath handler.""" 

34 if node.type in ("function_declaration", "method_declaration") and node is not top_func: 

35 return 1 

36 if node.type == "func_literal" and node is not top_func: 

37 if is_named_func_literal(node): 

38 return 1 

39 return compute_npath_go(node) 

40 

41 if node.type == "if_statement": 

42 return _npath_if_go(node, top_func) 

43 if node.type == "for_statement": 

44 return _npath_loop_go(node, top_func) 

45 if node.type in ("expression_switch_statement", "type_switch_statement"): 

46 return _npath_switch_go(node, top_func) 

47 if node.type == "select_statement": 

48 return _npath_select_go(node, top_func) 

49 if node.type == "block": 

50 return _npath_block_go(node, top_func) 

51 if node.type in ("expression_statement", "return_statement", 

52 "short_var_declaration", "assignment_statement", 

53 "go_statement", "defer_statement", "send_statement"): 

54 return _npath_expr_go(node, top_func) 

55 return 1 

56 

57 

58def _npath_if_go(node, top_func) -> int: 

59 """NPath for Go if_statement using consequence/alternative fields.""" 

60 total = 0 

61 has_else = False 

62 

63 condition = node.child_by_field_name("condition") 

64 bool_ops = _count_bool_ops(condition) if condition else 0 

65 

66 consequence = node.child_by_field_name("consequence") 

67 if consequence and consequence.type == "block": 

68 total += _npath_block_go(consequence, top_func) 

69 

70 alternative = node.child_by_field_name("alternative") 

71 if alternative is not None: 

72 has_else = True 

73 if alternative.type == "if_statement": 

74 total += _npath_if_go(alternative, top_func) 

75 elif alternative.type == "block": 

76 total += _npath_block_go(alternative, top_func) 

77 

78 if not has_else: 

79 total += 1 

80 

81 total += bool_ops 

82 return total 

83 

84 

85def _npath_loop_go(node, top_func) -> int: 

86 """NPath for Go for_statement (covers for, for-range, infinite loop).""" 

87 body_npath = 1 

88 bool_ops = 0 

89 

90 body = node.child_by_field_name("body") 

91 if body and body.type == "block": 

92 body_npath = _npath_block_go(body, top_func) 

93 

94 condition = node.child_by_field_name("condition") 

95 if condition: 

96 bool_ops = _count_bool_ops(condition) 

97 

98 return body_npath + 1 + bool_ops 

99 

100 

101def _npath_switch_go(node, top_func) -> int: 

102 """NPath for expression_switch_statement / type_switch_statement.""" 

103 total = 0 

104 has_default = False 

105 for child in node.children: 

106 if child.type in ("expression_case", "type_case"): 

107 case_npath = 1 

108 for sub in child.children: 

109 if sub.type in ("expression_list", "type_list", "case", "default", ":"): 

110 continue 

111 sub_np = _npath_stmt_go(sub, top_func) 

112 if sub_np > 1: 

113 case_npath *= sub_np 

114 total += case_npath 

115 elif child.type == "default_case": 

116 has_default = True 

117 case_npath = 1 

118 for sub in child.children: 

119 if sub.type in ("default", ":"): 

120 continue 

121 sub_np = _npath_stmt_go(sub, top_func) 

122 if sub_np > 1: 

123 case_npath *= sub_np 

124 total += case_npath 

125 if not has_default: 

126 total += 1 

127 return max(total, 1) 

128 

129 

130def _npath_select_go(node, top_func) -> int: 

131 """NPath for select_statement with communication_case / default_case.""" 

132 total = 0 

133 has_default = False 

134 for child in node.children: 

135 if child.type == "communication_case": 

136 case_npath = 1 

137 for sub in child.children: 

138 if sub.type in ("case", "default", ":"): 

139 continue 

140 sub_np = _npath_stmt_go(sub, top_func) 

141 if sub_np > 1: 

142 case_npath *= sub_np 

143 total += case_npath 

144 elif child.type == "default_case": 

145 has_default = True 

146 case_npath = 1 

147 for sub in child.children: 

148 if sub.type in ("default", ":"): 

149 continue 

150 sub_np = _npath_stmt_go(sub, top_func) 

151 if sub_np > 1: 

152 case_npath *= sub_np 

153 total += case_npath 

154 if not has_default: 

155 total += 1 

156 return max(total, 1) 

157 

158 

159def _npath_expr_go(node, top_func) -> int: 

160 """Scan expression for inline func_literals, multiplying results.""" 

161 if node.type == "func_literal" and node is not top_func: 

162 if is_named_func_literal(node): 

163 return 1 

164 return compute_npath_go(node) 

165 

166 result = 1 

167 for child in node.children: 

168 child_np = _npath_expr_go(child, top_func) 

169 if child_np > 1: 

170 result *= child_np 

171 return result