Coverage for metrics/npath/typescript.py: 90%

1"""NPath complexity for TypeScript."""

2

3 from __future__ import annotations

4

5 from common import _count_bool_ops

6

7

8 def compute_npath_ts(func_node) -> int:

9 """Compute NPath complexity for a TypeScript function node."""

10 for child in func_node.children:

11 if child.type == "statement_block":

12 return _npath_block_ts(child, func_node)

13 if func_node.type == "arrow_function":

14 children = [c for c in func_node.children if c.type not in ("=>", "formal_parameters", "identifier")]

15 for child in children:

16 return _npath_expr_ts(child, func_node)

17 return 1

18

19

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

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

22 result = 1

23 for child in block.children:

24 if child.type in ("{", "}"):

25 continue

26 result *= _npath_stmt_ts(child, top_func)

27 return result

28

29

30 def _npath_stmt_ts(node, top_func) -> int:

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

32 if node.type in ("function_declaration", "function_expression",

33 "method_definition", "generator_function_declaration") and node is not top_func:

34 return 1

35 if node.type == "class_declaration":

36 return 1

37 if node.type == "arrow_function" and node is not top_func:

38 if node.parent and node.parent.type == "variable_declarator":

39 return 1

40 return compute_npath_ts(node)

41

42 if node.type == "if_statement":

43 return _npath_if_ts(node, top_func)

44 if node.type in ("for_statement", "for_in_statement", "while_statement"):

45 return _npath_loop_ts(node, top_func)

46 if node.type == "do_statement":

47 return _npath_do_while_ts(node, top_func)

48 if node.type == "try_statement":

49 return _npath_try_ts(node, top_func)

50 if node.type == "switch_statement":

51 return _npath_switch_ts(node, top_func)

52 if node.type == "ternary_expression":

53 return _npath_ternary_ts(node, top_func)

54 if node.type == "statement_block":

55 return _npath_block_ts(node, top_func)

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

57 "variable_declaration", "lexical_declaration"):

58 return _npath_expr_ts(node, top_func)

59 return 1

60

61

62 def _npath_if_condition_ops(node) -> int:

63 """Count boolean operators in the if condition (before the body)."""

64 ops = 0

65 for child in node.children:

66 if child.type in ("statement_block", "else_clause"):

67 break

68 if child.type not in ("if", "(", ")"):

69 ops += _count_bool_ops(child)

70 return ops

71

72

73 def _npath_if_body(node, top_func) -> int:

74 """Compute NPath for the if-body (the first statement_block)."""

75 for child in node.children:

76 if child.type == "statement_block":

77 return _npath_block_ts(child, top_func)

78 return 0

79

80

81 def _npath_else_clause(else_node, top_func):

82 """Compute NPath for an else clause (may contain else-if or plain block)."""

83 for sub in else_node.children:

84 if sub.type == "if_statement":

85 return _npath_if_ts(sub, top_func)

86 if sub.type == "statement_block":

87 return _npath_block_ts(sub, top_func)

88 return 0

89

90

91 def _npath_if_ts(node, top_func) -> int:

92 """NPath for if_statement with else-if/else chains."""

93 total = _npath_if_body(node, top_func)

94 has_else = False

95

96 for child in node.children:

97 if child.type == "else_clause":

98 has_else = True

99 total += _npath_else_clause(child, top_func)

100

101 if not has_else:

102 total += 1

103

104 return total + _npath_if_condition_ops(node)

105

106

107 def _npath_loop_ts(node, top_func) -> int:

108 """NPath for for/while loops."""

109 body_npath = 1

110 bool_ops = 0

111

112 for child in node.children:

113 if child.type == "statement_block":

114 body_npath = _npath_block_ts(child, top_func)

115

116 if node.type == "while_statement":

117 for child in node.children:

118 if child.type == "statement_block":

119 break

120 if child.type not in ("while", "(", ")"):

121 bool_ops += _count_bool_ops(child)

122

123 return body_npath + 1 + bool_ops

124

125

126 def _npath_do_while_ts(node, top_func) -> int:

127 """NPath for do-while: NP(body) + 1 + bool_ops(condition)."""

128 body_npath = 1

129 bool_ops = 0

130

131 for child in node.children:

132 if child.type == "statement_block":

133 body_npath = _npath_block_ts(child, top_func)

134

135 seen_while = False

136 for child in node.children:

137 if child.type == "while":

138 seen_while = True

139 continue

140 if seen_while and child.type not in ("(", ")", ";"):

141 bool_ops += _count_bool_ops(child)

142

143 return body_npath + 1 + bool_ops

144

145

146 def _npath_try_ts(node, top_func) -> int:

147 """NPath for try_statement: sum of try/catch/finally bodies."""

148 total = 0

149 for child in node.children:

150 if child.type == "statement_block":

151 total += _npath_block_ts(child, top_func)

152 elif child.type == "catch_clause":

153 for sub in child.children:

154 if sub.type == "statement_block":

155 total += _npath_block_ts(sub, top_func)

156 elif child.type == "finally_clause":

157 for sub in child.children:

158 if sub.type == "statement_block":

159 total += _npath_block_ts(sub, top_func)

160 return max(total, 1)

161

162

163 def _npath_switch_ts(node, top_func) -> int:

164 """NPath for switch_statement: sum of case bodies (+1 if no default)."""

165 total = 0

166 has_default = False

167 for child in node.children:

168 if child.type == "switch_body":

169 for case_node in child.children:

170 if case_node.type == "switch_case":

171 case_npath = 1

172 for sub in case_node.children:

173 if sub.type not in ("case", ":", "{", "}") and sub.type != "switch_case":

174 sub_np = _npath_stmt_ts(sub, top_func)

175 if sub_np > 1:

176 case_npath *= sub_np

177 total += case_npath

178 elif case_node.type == "switch_default":

179 has_default = True

180 case_npath = 1

181 for sub in case_node.children:

182 if sub.type not in ("default", ":", "{", "}"):

183 sub_np = _npath_stmt_ts(sub, top_func)

184 if sub_np > 1:

185 case_npath *= sub_np

186 total += case_npath

187 if not has_default:

188 total += 1

189 return max(total, 1)

190

191

192 def _npath_ternary_ts(node, top_func) -> int:

193 """NPath for ternary_expression: NP(true) + NP(false) + bool_ops(cond)."""

194 non_punct = [c for c in node.children if c.type not in ("?", ":")]

195 if len(non_punct) >= 3:

196 condition = non_punct[0]

197 true_expr = non_punct[1]

198 false_expr = non_punct[2]

199 np_true = _npath_expr_ts(true_expr, top_func)

200 np_false = _npath_expr_ts(false_expr, top_func)

201 bool_ops = _count_bool_ops(condition)

202 return np_true + np_false + bool_ops

203 return 2

204

205

206 def _npath_expr_ts(node, top_func) -> int:

207 """Scan expression for ternaries/inline arrows, multiplying results."""

208 if node.type == "ternary_expression":

209 return _npath_ternary_ts(node, top_func)

210 if node.type == "arrow_function" and node is not top_func:

211 if node.parent and node.parent.type == "variable_declarator":

212 return 1

213 return compute_npath_ts(node)

214 if node.type in ("function_expression", "function_declaration",

215 "class_declaration", "class_expression"):

216 return 1

217

218 result = 1

219 for child in node.children:

220 child_np = _npath_expr_ts(child, top_func)

221 if child_np > 1:

222 result *= child_np

223 return result