Coverage for tests/test_cognitive/test_typescript.py: 100%

1"""Tests for TypeScript cognitive complexity."""

2

3 from analyzers.typescript import analyze_source

4

5

6 def _score(code: str, tsx: bool = False) -> int:

7 """Helper: return total complexity of the first function in code."""

8 result = analyze_source(code, tsx=tsx)

9 assert result.functions, f"No functions found in:\n{code}"

10 return result.functions[0].complexity

11

12

13 def _scores(code: str, tsx: bool = False) -> dict[str, int]:

14 """Helper: return {name: complexity} for all functions in code."""

15 result = analyze_source(code, tsx=tsx)

16 return {f.name: f.complexity for f in result.functions}

17

18

19# --- Basic control flow ---

20

21

22 def test_empty_function():

23 assert _score("function f() {}") == 0

24

25

26 def test_single_if():

27 code = """

28 function f(x) {

29 if (x) { // +1

30 }

31}

32"""

33 assert _score(code) == 1

34

35

36 def test_if_else():

37 code = """

38 function f(x) {

39 if (x) { // +1

40 } else { // +1

41 }

42}

43"""

44 assert _score(code) == 2

45

46

47 def test_if_else_if_else():

48 code = """

49 function f(x) {

50 if (x > 0) { // +1

51 } else if (x < 0) { // +1

52 } else { // +1

53 }

54}

55"""

56 assert _score(code) == 3

57

58

59# --- Loops ---

60

61

62 def test_for_loop():

63 code = """

64 function f(items) {

65 for (let i = 0; i < items.length; i++) { // +1

66 }

67}

68"""

69 assert _score(code) == 1

70

71

72 def test_for_in_loop():

73 code = """

74 function f(obj) {

75 for (const key in obj) { // +1

76 }

77}

78"""

79 assert _score(code) == 1

80

81

82 def test_while_loop():

83 code = """

84 function f(x) {

85 while (x > 0) { // +1

86 x--;

87 }

88}

89"""

90 assert _score(code) == 1

91

92

93 def test_do_while_loop():

94 code = """

95 function f(x) {

96 do { // +1

97 x--;

98 } while (x > 0);

99}

100"""

101 assert _score(code) == 1

102

103

104# --- Nesting ---

105

106

107 def test_nested_if():

108 code = """

109 function f(a, b) {

110 if (a) { // +1 (nesting 0)

111 if (b) { // +1 + 1 (nesting 1)

112 }

113 }

114}

115"""

116 assert _score(code) == 3

117

118

119 def test_deeply_nested():

120 code = """

121 function f(a, b, c) {

122 if (a) { // +1 (nesting 0)

123 for (let x of b) { // +1 + 1 (nesting 1)

124 if (c) { // +1 + 2 (nesting 2)

125 }

126 }

127 }

128}

129"""

130 assert _score(code) == 6

131

132

133 def test_if_else_nesting():

134 code = """

135 function f(a, b) {

136 if (a) { // +1 (nesting 0)

137 } else { // +1 (no nesting bonus)

138 if (b) { // +1 + 1 (nesting 1, inside else body)

139 }

140 }

141}

142"""

143 assert _score(code) == 4

144

145

146# --- Try/Catch ---

147

148

149 def test_try_catch():

150 code = """

151 function f() {

152 try {

153 } catch (e) { // +1

154 }

155}

156"""

157 assert _score(code) == 1

158

159

160 def test_try_catch_nested():

161 code = """

162 function f(x) {

163 if (x) { // +1 (nesting 0)

164 try {

165 } catch (e) { // +1 + 1 (nesting 1)

166 }

167 }

168}

169"""

170 assert _score(code) == 3

171

172

173# --- Boolean operators ---

174

175

176 def test_single_and():

177 code = """

178 function f(a, b) {

179 if (a && b) { // +1 (if) + 1 (boolean)

180 }

181}

182"""

183 assert _score(code) == 2

184

185

186 def test_chained_same_op():

187 code = """

188 function f(a, b, c) {

189 if (a && b && c) { // +1 (if) + 1 (boolean chain, same op)

190 }

191}

192"""

193 assert _score(code) == 2

194

195

196 def test_mixed_boolean():

197 code = """

198 function f(a, b, c) {

199 if (a && b || c) { // +1 (if) + 2 (&&->|| switch)

200 }

201}

202"""

203 assert _score(code) == 3

204

205

206 def test_complex_boolean():

207 code = """

208 function f(a, b, c, d) {

209 if (a || b || (c && d)) { // +1 (if) + 2 (|| sequence, then && subtree)

210 }

211}

212"""

213 assert _score(code) == 3

214

215

216# --- Ternary ---

217

218

219 def test_ternary():

220 code = """

221 function f(x) {

222 return x > 0 ? x : -x; // +1 (ternary, nesting 0)

223}

224"""

225 assert _score(code) == 1

226

227

228 def test_ternary_nested_in_if():

229 code = """

230 function f(x) {

231 if (x) { // +1 (nesting 0)

232 return x > 0 ? x : -x; // +1 + 1 (ternary, nesting 1)

233 }

234}

235"""

236 assert _score(code) == 3

237

238

239# --- Recursion ---

240

241

242 def test_recursion():

243 code = """

244 function factorial(n) {

245 if (n <= 1) { // +1

246 return 1;

247 }

248 return n * factorial(n - 1); // +1 (recursion)

249}

250"""

251 assert _score(code) == 2

252

253

254 def test_no_false_recursion():

255 code = """

256 function f(x) {

257 return g(x);

258}

259"""

260 assert _score(code) == 0

261

262

263# --- Nested functions ---

264

265

266 def test_nested_function_separate_scoring():

267 code = """

268 function outer(x) {

269 if (x) {} // +1 for outer

270 function inner(y) {

271 if (y) {} // +1 for inner

272 }

273}

274"""

275 scores = _scores(code)

276 assert scores["outer"] == 1

277 assert scores["inner"] == 1

278

279

280 def test_nested_function_nesting_reset():

281 code = """

282 function outer(x) {

283 if (x) { // +1 for outer

284 function inner(y) {

285 if (y) {} // +1 for inner (nesting 0, not 1)

286 }

287 }

288}

289"""

290 scores = _scores(code)

291 assert scores["outer"] == 1

292 assert scores["inner"] == 1

293

294

295# --- Arrow functions ---

296

297

298 def test_inline_arrow_increases_nesting():

299 code = """

300 function f(items) {

301 return items.map(x => x > 0 ? x : 0);

302}

303"""

304 assert _score(code) == 2

305

306

307 def test_named_arrow_scored_separately():

308 code = """

309 const f = (x) => {

310 if (x) {} // +1

311};

312 const g = (a, b) => {

313 if (a) { // +1

314 if (b) {} // +1 + 1

315 }

316};

317"""

318 scores = _scores(code)

319 assert scores["f"] == 1

320 assert scores["g"] == 3

321

322

323# --- Switch ---

324

325

326 def test_switch_statement():

327 code = """

328 function f(x) {

329 switch (x) { // +1 (nesting 0)

330 case 1:

331 break;

332 case 2:

333 break;

334 default:

335 break;

336 }

337}

338"""

339 assert _score(code) == 1

340

341

342# --- Method definition ---

343

344

345 def test_method_definition():

346 code = """

347 class Foo {

348 bar(x) {

349 if (x) {} // +1

350 }

351}

352"""

353 scores = _scores(code)

354 assert scores["bar"] == 1

355

356

357# --- Comprehensive example ---

358

359

360 def test_comprehensive():

361 code = """

362 function process(items, flag) {

363 if (flag) { // +1 (nesting 0)

364 for (let i = 0; i < items.length; i++) { // +1 +1 (nesting 1)

365 if (items[i] > 0) { // +1 +2 (nesting 2)

366 } else if (items[i] < 0) { // +1 (no nesting bonus)

367 } else { // +1 (no nesting bonus)

368 try {

369 } catch (e) { // +1 +3 (nesting 3)

370 }

371 }

372 }

373 } else { // +1 (no nesting bonus)

374 }

375}

376"""

377 assert _score(code) == 13

378

379

380# --- Edge cases ---

381

382

383 def test_not_operator_no_increment():

384 code = """

385 function f(x) {

386 if (!x) { // +1 (if only)

387 }

388}

389"""

390 assert _score(code) == 1

391

392

393 def test_async_function():

394 code = """

395 async function f(x) {

396 if (x) {} // +1

397}

398"""

399 assert _score(code) == 1

400

401

402 def test_generator_function():

403 code = """

404 function* gen(items) {

405 for (const item of items) { // +1

406 if (item) { // +1 + 1

407 yield item;

408 }

409 }

410}

411"""

412 assert _score(code) == 3