Coverage for tests / unit / parsers / test_tsc_parser.py: 100%

142 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-04-03 18:53 +0000

1"""Unit tests for tsc parser.""" 

2 

3from __future__ import annotations 

4 

5from assertpy import assert_that 

6 

7from lintro.parsers.tsc.tsc_issue import TscIssue 

8from lintro.parsers.tsc.tsc_parser import ( 

9 DEPENDENCY_ERROR_CODES, 

10 categorize_tsc_issues, 

11 extract_missing_modules, 

12 parse_tsc_output, 

13) 

14 

15 

16def test_parse_tsc_output_single_error() -> None: 

17 """Parse a single tsc error from text output.""" 

18 output = "src/main.ts(10,5): error TS2322: Type 'string' is not assignable to type 'number'." 

19 issues = parse_tsc_output(output) 

20 

21 assert_that(issues).is_length(1) 

22 assert_that(issues[0].file).is_equal_to("src/main.ts") 

23 assert_that(issues[0].line).is_equal_to(10) 

24 assert_that(issues[0].column).is_equal_to(5) 

25 assert_that(issues[0].code).is_equal_to("TS2322") 

26 assert_that(issues[0].severity).is_equal_to("error") 

27 assert_that(issues[0].message).contains("not assignable") 

28 

29 

30def test_parse_tsc_output_single_warning() -> None: 

31 """Parse a single tsc warning from text output.""" 

32 output = "src/utils.ts(15,1): warning TS6133: 'x' is declared but its value is never read." 

33 issues = parse_tsc_output(output) 

34 

35 assert_that(issues).is_length(1) 

36 assert_that(issues[0].severity).is_equal_to("warning") 

37 assert_that(issues[0].code).is_equal_to("TS6133") 

38 

39 

40def test_parse_tsc_output_multiple_errors() -> None: 

41 """Parse multiple errors from tsc output.""" 

42 output = """src/main.ts(10,5): error TS2322: Type 'string' is not assignable to type 'number'. 

43src/main.ts(15,10): error TS2339: Property 'foo' does not exist on type 'Bar'. 

44src/utils.ts(3,1): warning TS6133: 'x' is declared but its value is never read.""" 

45 issues = parse_tsc_output(output) 

46 

47 assert_that(issues).is_length(3) 

48 assert_that(issues[0].code).is_equal_to("TS2322") 

49 assert_that(issues[1].code).is_equal_to("TS2339") 

50 assert_that(issues[2].code).is_equal_to("TS6133") 

51 assert_that(issues[2].severity).is_equal_to("warning") 

52 

53 

54def test_parse_tsc_output_mixed_with_non_errors() -> None: 

55 """Parse errors mixed with non-error output lines.""" 

56 output = """Starting compilation... 

57src/main.ts(10,5): error TS2322: Type 'string' is not assignable to type 'number'. 

58Processing files... 

59src/utils.ts(3,1): error TS2304: Cannot find name 'foo'. 

60Found 2 errors.""" 

61 issues = parse_tsc_output(output) 

62 

63 assert_that(issues).is_length(2) 

64 assert_that(issues[0].file).is_equal_to("src/main.ts") 

65 assert_that(issues[1].file).is_equal_to("src/utils.ts") 

66 

67 

68def test_parse_tsc_output_empty() -> None: 

69 """Handle empty output.""" 

70 assert_that(parse_tsc_output("")).is_empty() 

71 assert_that(parse_tsc_output(" \n\n ")).is_empty() 

72 

73 

74def test_parse_tsc_output_windows_paths() -> None: 

75 """Normalize Windows backslashes to forward slashes.""" 

76 output = r"src\components\Button.tsx(10,5): error TS2322: Type mismatch." 

77 issues = parse_tsc_output(output) 

78 

79 assert_that(issues).is_length(1) 

80 assert_that(issues[0].file).is_equal_to("src/components/Button.tsx") 

81 

82 

83def test_parse_tsc_output_tsx_files() -> None: 

84 """Parse errors from TSX files.""" 

85 output = ( 

86 "src/components/Button.tsx(25,12): error TS2769: No overload matches this call." 

87 ) 

88 issues = parse_tsc_output(output) 

89 

90 assert_that(issues).is_length(1) 

91 assert_that(issues[0].file).is_equal_to("src/components/Button.tsx") 

92 

93 

94def test_parse_tsc_output_mts_cts_files() -> None: 

95 """Parse errors from .mts and .cts files.""" 

96 output = """src/module.mts(5,1): error TS2322: Type error. 

97src/common.cts(10,1): error TS2322: Type error.""" 

98 issues = parse_tsc_output(output) 

99 

100 assert_that(issues).is_length(2) 

101 assert_that(issues[0].file).is_equal_to("src/module.mts") 

102 assert_that(issues[1].file).is_equal_to("src/common.cts") 

103 

104 

105def test_parse_tsc_output_deep_nested_path() -> None: 

106 """Parse errors with deeply nested file paths.""" 

107 output = "packages/app/src/features/auth/hooks/useAuth.ts(42,15): error TS2345: Argument type mismatch." 

108 issues = parse_tsc_output(output) 

109 

110 assert_that(issues).is_length(1) 

111 assert_that(issues[0].file).is_equal_to( 

112 "packages/app/src/features/auth/hooks/useAuth.ts", 

113 ) 

114 

115 

116def test_parse_tsc_output_skips_non_matching_lines() -> None: 

117 """Skip non-matching lines gracefully.""" 

118 output = """Starting compilation... 

119not valid tsc output 

120error TS6053: File not found.""" 

121 issues = parse_tsc_output(output) 

122 

123 assert_that(issues).is_empty() 

124 

125 

126# Tests for TscIssue.to_display_row 

127 

128 

129def test_tsc_issue_to_display_row() -> None: 

130 """Convert TscIssue to display row format.""" 

131 issue = TscIssue( 

132 file="src/main.ts", 

133 line=10, 

134 column=5, 

135 code="TS2322", 

136 message="Type error", 

137 severity="error", 

138 ) 

139 row = issue.to_display_row() 

140 

141 assert_that(row["file"]).is_equal_to("src/main.ts") 

142 assert_that(row["line"]).is_equal_to("10") 

143 assert_that(row["column"]).is_equal_to("5") 

144 assert_that(row["code"]).is_equal_to("TS2322") 

145 assert_that(row["message"]).is_equal_to("Type error") 

146 assert_that(row["severity"]).is_equal_to("ERROR") 

147 

148 

149def test_tsc_issue_to_display_row_minimal() -> None: 

150 """Convert minimal TscIssue to display row format.""" 

151 issue = TscIssue(file="main.ts", line=1, column=1, message="Error") 

152 row = issue.to_display_row() 

153 

154 assert_that(row["file"]).is_equal_to("main.ts") 

155 assert_that(row["code"]).is_equal_to("") 

156 assert_that(row["severity"]).is_equal_to("WARNING") 

157 

158 

159def test_parse_tsc_output_ansi_codes_stripped() -> None: 

160 """Strip ANSI escape codes from output for consistent CI/local parsing.""" 

161 # Output with ANSI color codes (common in CI environments) 

162 output = "\x1b[31msrc/main.ts(10,5): error TS2322: Type 'string' is not assignable to type 'number'.\x1b[0m" 

163 issues = parse_tsc_output(output) 

164 

165 assert_that(issues).is_length(1) 

166 assert_that(issues[0].file).is_equal_to("src/main.ts") 

167 assert_that(issues[0].code).is_equal_to("TS2322") 

168 

169 

170# Tests for error categorization 

171 

172 

173def test_dependency_error_codes_contains_expected_codes() -> None: 

174 """DEPENDENCY_ERROR_CODES should contain expected TypeScript error codes.""" 

175 assert_that(DEPENDENCY_ERROR_CODES).contains("TS2307") 

176 assert_that(DEPENDENCY_ERROR_CODES).contains("TS2688") 

177 assert_that(DEPENDENCY_ERROR_CODES).contains("TS7016") 

178 

179 

180def test_categorize_tsc_issues_separates_type_and_dep_errors() -> None: 

181 """Categorize issues into type errors and dependency errors.""" 

182 issues = [ 

183 TscIssue( 

184 file="src/main.ts", 

185 line=10, 

186 column=5, 

187 code="TS2322", 

188 message="Type 'string' is not assignable to type 'number'.", 

189 severity="error", 

190 ), 

191 TscIssue( 

192 file="src/app.ts", 

193 line=1, 

194 column=1, 

195 code="TS2307", 

196 message="Cannot find module 'react' or its corresponding type declarations.", 

197 severity="error", 

198 ), 

199 TscIssue( 

200 file="src/utils.ts", 

201 line=5, 

202 column=1, 

203 code="TS2688", 

204 message="Cannot find type definition file for 'node'.", 

205 severity="error", 

206 ), 

207 ] 

208 

209 type_errors, dep_errors = categorize_tsc_issues(issues) 

210 

211 assert_that(type_errors).is_length(1) 

212 assert_that(type_errors[0].code).is_equal_to("TS2322") 

213 assert_that(dep_errors).is_length(2) 

214 assert_that([e.code for e in dep_errors]).contains("TS2307", "TS2688") 

215 

216 

217def test_categorize_tsc_issues_all_type_errors() -> None: 

218 """All issues are type errors when no dependency codes present.""" 

219 issues = [ 

220 TscIssue( 

221 file="src/main.ts", 

222 line=10, 

223 column=5, 

224 code="TS2322", 

225 message="Type error", 

226 severity="error", 

227 ), 

228 TscIssue( 

229 file="src/main.ts", 

230 line=15, 

231 column=10, 

232 code="TS2339", 

233 message="Property does not exist", 

234 severity="error", 

235 ), 

236 ] 

237 

238 type_errors, dep_errors = categorize_tsc_issues(issues) 

239 

240 assert_that(type_errors).is_length(2) 

241 assert_that(dep_errors).is_empty() 

242 

243 

244def test_categorize_tsc_issues_all_dependency_errors() -> None: 

245 """All issues are dependency errors when all have dependency codes.""" 

246 issues = [ 

247 TscIssue( 

248 file="src/app.ts", 

249 line=1, 

250 column=1, 

251 code="TS2307", 

252 message="Cannot find module 'react'", 

253 severity="error", 

254 ), 

255 TscIssue( 

256 file="src/utils.ts", 

257 line=2, 

258 column=1, 

259 code="TS7016", 

260 message="Could not find declaration file for module 'lodash'.", 

261 severity="error", 

262 ), 

263 ] 

264 

265 type_errors, dep_errors = categorize_tsc_issues(issues) 

266 

267 assert_that(type_errors).is_empty() 

268 assert_that(dep_errors).is_length(2) 

269 

270 

271def test_categorize_tsc_issues_empty_list() -> None: 

272 """Handle empty issues list.""" 

273 type_errors, dep_errors = categorize_tsc_issues([]) 

274 

275 assert_that(type_errors).is_empty() 

276 assert_that(dep_errors).is_empty() 

277 

278 

279def test_categorize_tsc_issues_no_code() -> None: 

280 """Issues without code are treated as type errors.""" 

281 issues = [ 

282 TscIssue( 

283 file="src/main.ts", 

284 line=10, 

285 column=5, 

286 code="", 

287 message="Some error", 

288 severity="error", 

289 ), 

290 ] 

291 

292 type_errors, dep_errors = categorize_tsc_issues(issues) 

293 

294 assert_that(type_errors).is_length(1) 

295 assert_that(dep_errors).is_empty() 

296 

297 

298# Tests for extract_missing_modules 

299 

300 

301def test_extract_missing_modules_from_ts2307() -> None: 

302 """Extract module names from TS2307 errors.""" 

303 errors = [ 

304 TscIssue( 

305 file="src/app.ts", 

306 line=1, 

307 column=1, 

308 code="TS2307", 

309 message="Cannot find module 'react' or its corresponding type declarations.", 

310 severity="error", 

311 ), 

312 TscIssue( 

313 file="src/utils.ts", 

314 line=2, 

315 column=1, 

316 code="TS2307", 

317 message="Cannot find module '@types/node' or its corresponding type declarations.", 

318 severity="error", 

319 ), 

320 ] 

321 

322 modules = extract_missing_modules(errors) 

323 

324 assert_that(modules).contains("react", "@types/node") 

325 assert_that(modules).is_length(2) 

326 

327 

328def test_extract_missing_modules_from_ts2688() -> None: 

329 """Extract type definition names from TS2688 errors.""" 

330 errors = [ 

331 TscIssue( 

332 file="src/app.ts", 

333 line=1, 

334 column=1, 

335 code="TS2688", 

336 message="Cannot find type definition file for 'node'.", 

337 severity="error", 

338 ), 

339 ] 

340 

341 modules = extract_missing_modules(errors) 

342 

343 assert_that(modules).contains("node") 

344 

345 

346def test_extract_missing_modules_from_ts7016() -> None: 

347 """Extract module names from TS7016 errors.""" 

348 errors = [ 

349 TscIssue( 

350 file="src/app.ts", 

351 line=1, 

352 column=1, 

353 code="TS7016", 

354 message="Could not find a declaration file for module 'lodash'.", 

355 severity="error", 

356 ), 

357 ] 

358 

359 modules = extract_missing_modules(errors) 

360 

361 assert_that(modules).contains("lodash") 

362 

363 

364def test_extract_missing_modules_deduplicates() -> None: 

365 """Extract unique module names when same module appears multiple times.""" 

366 errors = [ 

367 TscIssue( 

368 file="src/app.ts", 

369 line=1, 

370 column=1, 

371 code="TS2307", 

372 message="Cannot find module 'react'", 

373 severity="error", 

374 ), 

375 TscIssue( 

376 file="src/utils.ts", 

377 line=2, 

378 column=1, 

379 code="TS2307", 

380 message="Cannot find module 'react'", 

381 severity="error", 

382 ), 

383 ] 

384 

385 modules = extract_missing_modules(errors) 

386 

387 assert_that(modules).is_length(1) 

388 assert_that(modules).contains("react") 

389 

390 

391def test_extract_missing_modules_sorted() -> None: 

392 """Module names should be sorted alphabetically.""" 

393 errors = [ 

394 TscIssue( 

395 file="a.ts", 

396 line=1, 

397 column=1, 

398 code="TS2307", 

399 message="Cannot find module 'zod'", 

400 severity="error", 

401 ), 

402 TscIssue( 

403 file="b.ts", 

404 line=1, 

405 column=1, 

406 code="TS2307", 

407 message="Cannot find module 'axios'", 

408 severity="error", 

409 ), 

410 TscIssue( 

411 file="c.ts", 

412 line=1, 

413 column=1, 

414 code="TS2307", 

415 message="Cannot find module 'lodash'", 

416 severity="error", 

417 ), 

418 ] 

419 

420 modules = extract_missing_modules(errors) 

421 

422 assert_that(modules).is_equal_to(["axios", "lodash", "zod"]) 

423 

424 

425def test_extract_missing_modules_empty_list() -> None: 

426 """Handle empty errors list.""" 

427 modules = extract_missing_modules([]) 

428 

429 assert_that(modules).is_empty() 

430 

431 

432def test_extract_missing_modules_no_match() -> None: 

433 """Handle errors without recognizable module patterns.""" 

434 errors = [ 

435 TscIssue( 

436 file="a.ts", 

437 line=1, 

438 column=1, 

439 code="TS2307", 

440 message="Some other error format", 

441 severity="error", 

442 ), 

443 ] 

444 

445 modules = extract_missing_modules(errors) 

446 

447 assert_that(modules).is_empty()