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

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

2

3 from __future__ import annotations

4

5 from common import _count_bool_ops

6 from parsers.go import is_named_func_literal

7

8

9 def 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

20 def _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

32 def _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

58 def _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

85 def _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

101 def _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

130 def _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

159 def _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