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

167 statements  

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

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

2 

3from __future__ import annotations 

4 

5from common import _count_bool_ops 

6 

7 

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

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

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

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

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

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

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

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

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

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

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

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

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