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
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Unit tests for tsc parser."""
3from __future__ import annotations
5from assertpy import assert_that
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)
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)
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")
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)
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")
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)
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")
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)
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")
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()
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)
79 assert_that(issues).is_length(1)
80 assert_that(issues[0].file).is_equal_to("src/components/Button.tsx")
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)
90 assert_that(issues).is_length(1)
91 assert_that(issues[0].file).is_equal_to("src/components/Button.tsx")
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)
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")
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)
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 )
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)
123 assert_that(issues).is_empty()
126# Tests for TscIssue.to_display_row
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()
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")
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()
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")
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)
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")
170# Tests for error categorization
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")
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 ]
209 type_errors, dep_errors = categorize_tsc_issues(issues)
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")
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 ]
238 type_errors, dep_errors = categorize_tsc_issues(issues)
240 assert_that(type_errors).is_length(2)
241 assert_that(dep_errors).is_empty()
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 ]
265 type_errors, dep_errors = categorize_tsc_issues(issues)
267 assert_that(type_errors).is_empty()
268 assert_that(dep_errors).is_length(2)
271def test_categorize_tsc_issues_empty_list() -> None:
272 """Handle empty issues list."""
273 type_errors, dep_errors = categorize_tsc_issues([])
275 assert_that(type_errors).is_empty()
276 assert_that(dep_errors).is_empty()
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 ]
292 type_errors, dep_errors = categorize_tsc_issues(issues)
294 assert_that(type_errors).is_length(1)
295 assert_that(dep_errors).is_empty()
298# Tests for extract_missing_modules
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 ]
322 modules = extract_missing_modules(errors)
324 assert_that(modules).contains("react", "@types/node")
325 assert_that(modules).is_length(2)
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 ]
341 modules = extract_missing_modules(errors)
343 assert_that(modules).contains("node")
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 ]
359 modules = extract_missing_modules(errors)
361 assert_that(modules).contains("lodash")
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 ]
385 modules = extract_missing_modules(errors)
387 assert_that(modules).is_length(1)
388 assert_that(modules).contains("react")
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 ]
420 modules = extract_missing_modules(errors)
422 assert_that(modules).is_equal_to(["axios", "lodash", "zod"])
425def test_extract_missing_modules_empty_list() -> None:
426 """Handle empty errors list."""
427 modules = extract_missing_modules([])
429 assert_that(modules).is_empty()
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 ]
445 modules = extract_missing_modules(errors)
447 assert_that(modules).is_empty()