Coverage for tests / unit / parsers / test_svelte_check_parser.py: 100%
163 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 svelte-check parser."""
3from __future__ import annotations
5import json
7from assertpy import assert_that
9from lintro.parsers.svelte_check.svelte_check_issue import SvelteCheckIssue
10from lintro.parsers.svelte_check.svelte_check_parser import parse_svelte_check_output
13def test_parse_svelte_check_output_empty() -> None:
14 """Handle empty output."""
15 assert_that(parse_svelte_check_output("")).is_empty()
16 assert_that(parse_svelte_check_output(" \n\n ")).is_empty()
19# --- NDJSON format tests (modern svelte-check --output machine-verbose) ---
22def test_parse_ndjson_with_timestamp_prefix() -> None:
23 """Parse NDJSON line with leading millisecond timestamp prefix."""
24 payload = json.dumps(
25 {
26 "type": "ERROR",
27 "fn": "src/lib/Button.svelte",
28 "start": {"line": 15, "character": 5},
29 "end": {"line": 15, "character": 10},
30 "message": "Type 'string' is not assignable to type 'number'.",
31 },
32 )
33 output = f"1590680326283 {payload}"
34 issues = parse_svelte_check_output(output)
36 assert_that(issues).is_length(1)
37 assert_that(issues[0].file).is_equal_to("src/lib/Button.svelte")
38 assert_that(issues[0].line).is_equal_to(15)
39 assert_that(issues[0].column).is_equal_to(5)
40 assert_that(issues[0].severity).is_equal_to("error")
41 assert_that(issues[0].message).contains("not assignable")
44def test_parse_ndjson_single_error() -> None:
45 """Parse a single NDJSON error line."""
46 output = json.dumps(
47 {
48 "type": "ERROR",
49 "fn": "src/lib/Button.svelte",
50 "start": {"line": 15, "character": 5},
51 "end": {"line": 15, "character": 10},
52 "message": "Type 'string' is not assignable to type 'number'.",
53 },
54 )
55 issues = parse_svelte_check_output(output)
57 assert_that(issues).is_length(1)
58 assert_that(issues[0]).is_instance_of(SvelteCheckIssue)
59 assert_that(issues[0].file).is_equal_to("src/lib/Button.svelte")
60 assert_that(issues[0].line).is_equal_to(15)
61 assert_that(issues[0].column).is_equal_to(5)
62 assert_that(issues[0].severity).is_equal_to("error")
63 assert_that(issues[0].message).contains("not assignable")
66def test_parse_ndjson_warning() -> None:
67 """Parse an NDJSON warning line."""
68 output = json.dumps(
69 {
70 "type": "WARNING",
71 "fn": "src/lib/Card.svelte",
72 "start": {"line": 8, "character": 1},
73 "end": {"line": 8, "character": 20},
74 "message": "Unused CSS selector '.unused'.",
75 },
76 )
77 issues = parse_svelte_check_output(output)
79 assert_that(issues).is_length(1)
80 assert_that(issues[0].severity).is_equal_to("warning")
83def test_parse_ndjson_multiple_lines() -> None:
84 """Parse multiple NDJSON lines."""
85 lines = [
86 json.dumps(
87 {
88 "type": "ERROR",
89 "fn": "src/lib/Button.svelte",
90 "start": {"line": 15, "character": 5},
91 "end": {"line": 15, "character": 10},
92 "message": "Type error.",
93 },
94 ),
95 json.dumps(
96 {
97 "type": "WARNING",
98 "fn": "src/lib/Card.svelte",
99 "start": {"line": 8, "character": 1},
100 "end": {"line": 8, "character": 20},
101 "message": "Unused CSS selector.",
102 },
103 ),
104 ]
105 output = "\n".join(lines)
106 issues = parse_svelte_check_output(output)
108 assert_that(issues).is_length(2)
109 assert_that(issues[0].severity).is_equal_to("error")
110 assert_that(issues[1].severity).is_equal_to("warning")
113def test_parse_ndjson_filename_field() -> None:
114 """Parse NDJSON using 'filename' field instead of 'fn'."""
115 output = json.dumps(
116 {
117 "type": "ERROR",
118 "filename": "src/lib/Button.svelte",
119 "start": {"line": 10, "character": 3},
120 "end": {"line": 10, "character": 8},
121 "message": "Type mismatch.",
122 },
123 )
124 issues = parse_svelte_check_output(output)
126 assert_that(issues).is_length(1)
127 assert_that(issues[0].file).is_equal_to("src/lib/Button.svelte")
130def test_parse_ndjson_multiline_span() -> None:
131 """Parse NDJSON issue spanning multiple lines."""
132 output = json.dumps(
133 {
134 "type": "ERROR",
135 "fn": "src/lib/Button.svelte",
136 "start": {"line": 15, "character": 5},
137 "end": {"line": 18, "character": 10},
138 "message": "Multi-line type error.",
139 },
140 )
141 issues = parse_svelte_check_output(output)
143 assert_that(issues).is_length(1)
144 assert_that(issues[0].end_line).is_equal_to(18)
145 assert_that(issues[0].end_column).is_equal_to(10)
148def test_parse_ndjson_same_position() -> None:
149 """NDJSON same start/end position sets end_line/end_column to None."""
150 output = json.dumps(
151 {
152 "type": "ERROR",
153 "fn": "src/lib/Button.svelte",
154 "start": {"line": 15, "character": 5},
155 "end": {"line": 15, "character": 5},
156 "message": "Point error.",
157 },
158 )
159 issues = parse_svelte_check_output(output)
161 assert_that(issues).is_length(1)
162 assert_that(issues[0].end_line).is_none()
163 assert_that(issues[0].end_column).is_none()
166def test_parse_ndjson_windows_paths() -> None:
167 """NDJSON backslash paths are normalized to forward slashes."""
168 output = json.dumps(
169 {
170 "type": "ERROR",
171 "fn": "src\\lib\\Button.svelte",
172 "start": {"line": 15, "character": 5},
173 "end": {"line": 15, "character": 10},
174 "message": "Type mismatch.",
175 },
176 )
177 issues = parse_svelte_check_output(output)
179 assert_that(issues).is_length(1)
180 assert_that(issues[0].file).is_equal_to("src/lib/Button.svelte")
183def test_parse_ndjson_code_field() -> None:
184 """Parse NDJSON with a code field."""
185 output = json.dumps(
186 {
187 "type": "ERROR",
188 "fn": "src/lib/Button.svelte",
189 "start": {"line": 15, "character": 5},
190 "end": {"line": 15, "character": 10},
191 "message": "Type 'string' is not assignable to type 'number'.",
192 "code": "ts-2322",
193 },
194 )
195 issues = parse_svelte_check_output(output)
197 assert_that(issues).is_length(1)
198 assert_that(issues[0].code).is_equal_to("ts-2322")
201def test_parse_ndjson_no_code_field() -> None:
202 """NDJSON without code field leaves code as empty string."""
203 output = json.dumps(
204 {
205 "type": "ERROR",
206 "fn": "src/lib/Button.svelte",
207 "start": {"line": 15, "character": 5},
208 "end": {"line": 15, "character": 10},
209 "message": "Type error.",
210 },
211 )
212 issues = parse_svelte_check_output(output)
214 assert_that(issues).is_length(1)
215 assert_that(issues[0].code).is_equal_to("")
218def test_parse_ndjson_numeric_code_field() -> None:
219 """NDJSON with numeric code is coerced to string."""
220 output = json.dumps(
221 {
222 "type": "ERROR",
223 "fn": "src/lib/Button.svelte",
224 "start": {"line": 15, "character": 5},
225 "end": {"line": 15, "character": 10},
226 "message": "Type error.",
227 "code": 2322,
228 },
229 )
230 issues = parse_svelte_check_output(output)
232 assert_that(issues).is_length(1)
233 assert_that(issues[0].code).is_equal_to("2322")
236def test_parse_ndjson_invalid_json_skipped() -> None:
237 """Non-JSON lines are skipped by NDJSON parser."""
238 output = "not valid json\n" + json.dumps(
239 {
240 "type": "ERROR",
241 "fn": "src/lib/Button.svelte",
242 "start": {"line": 15, "character": 5},
243 "end": {"line": 15, "character": 10},
244 "message": "Type error.",
245 },
246 )
247 issues = parse_svelte_check_output(output)
249 assert_that(issues).is_length(1)
252# --- Legacy plain-text machine-verbose format tests ---
255def test_parse_svelte_check_output_machine_verbose_single_error() -> None:
256 """Parse a single legacy machine-verbose error."""
257 output = "src/lib/Button.svelte:15:5:15:10 Error Type 'string' is not assignable to type 'number'."
258 issues = parse_svelte_check_output(output)
260 assert_that(issues).is_length(1)
261 assert_that(issues[0].file).is_equal_to("src/lib/Button.svelte")
262 assert_that(issues[0].line).is_equal_to(15)
263 assert_that(issues[0].column).is_equal_to(5)
264 assert_that(issues[0].severity).is_equal_to("error")
265 assert_that(issues[0].message).contains("not assignable")
268def test_parse_svelte_check_output_multiple_errors() -> None:
269 """Parse multiple errors from svelte-check output."""
270 output = """src/lib/Button.svelte:15:5:15:10 Error Type 'string' is not assignable to type 'number'.
271src/routes/+page.svelte:20:3:20:15 Error Property 'foo' does not exist on type 'Bar'.
272src/lib/Card.svelte:8:1:8:20 Warning Unused CSS selector '.unused'."""
273 issues = parse_svelte_check_output(output)
275 assert_that(issues).is_length(3)
276 assert_that(issues[0].severity).is_equal_to("error")
277 assert_that(issues[1].severity).is_equal_to("error")
278 assert_that(issues[2].severity).is_equal_to("warning")
281def test_parse_svelte_check_output_machine_format() -> None:
282 """Parse machine format (non-verbose)."""
283 output = "ERROR src/lib/Button.svelte:15:5 Type 'string' is not assignable to type 'number'."
284 issues = parse_svelte_check_output(output)
286 assert_that(issues).is_length(1)
287 assert_that(issues[0].file).is_equal_to("src/lib/Button.svelte")
288 assert_that(issues[0].line).is_equal_to(15)
289 assert_that(issues[0].column).is_equal_to(5)
290 assert_that(issues[0].severity).is_equal_to("error")
293def test_parse_svelte_check_output_warning_severity() -> None:
294 """Parse warning severity level."""
295 output = "src/lib/Card.svelte:8:1:8:20 Warning Unused CSS selector '.unused'."
296 issues = parse_svelte_check_output(output)
298 assert_that(issues).is_length(1)
299 assert_that(issues[0].severity).is_equal_to("warning")
302def test_parse_svelte_check_output_hint_severity() -> None:
303 """Parse hint severity level."""
304 output = (
305 "src/lib/Card.svelte:8:1:8:20 Hint Consider using a more specific selector."
306 )
307 issues = parse_svelte_check_output(output)
309 assert_that(issues).is_length(1)
310 assert_that(issues[0].severity).is_equal_to("hint")
313def test_parse_svelte_check_output_windows_paths() -> None:
314 """Normalize Windows backslashes to forward slashes."""
315 output = r"src\lib\Button.svelte:15:5:15:10 Error Type mismatch."
316 issues = parse_svelte_check_output(output)
318 assert_that(issues).is_length(1)
319 assert_that(issues[0].file).is_equal_to("src/lib/Button.svelte")
322def test_parse_svelte_check_output_ansi_codes() -> None:
323 """Strip ANSI escape codes from output."""
324 output = "\x1b[31msrc/lib/Button.svelte:15:5:15:10 Error Type mismatch.\x1b[0m"
325 issues = parse_svelte_check_output(output)
327 assert_that(issues).is_length(1)
328 assert_that(issues[0].file).is_equal_to("src/lib/Button.svelte")
331def test_parse_svelte_check_output_skips_noise_lines() -> None:
332 """Skip non-error lines like summary and progress."""
333 output = """====================================
334Loading svelte-check in workspace...
335Diagnostics:
336src/lib/Button.svelte:15:5:15:10 Error Type mismatch.
337svelte-check found 1 error"""
338 issues = parse_svelte_check_output(output)
340 assert_that(issues).is_length(1)
341 assert_that(issues[0].file).is_equal_to("src/lib/Button.svelte")
344def test_parse_svelte_check_output_end_line_different() -> None:
345 """Parse issue spanning multiple lines."""
346 output = "src/lib/Button.svelte:15:5:18:10 Error Multi-line type error."
347 issues = parse_svelte_check_output(output)
349 assert_that(issues).is_length(1)
350 assert_that(issues[0].end_line).is_equal_to(18)
351 assert_that(issues[0].end_column).is_equal_to(10)
354def test_parse_svelte_check_output_same_line_same_column() -> None:
355 """End line/column set to None when same as start."""
356 output = "src/lib/Button.svelte:15:5:15:5 Error Point error."
357 issues = parse_svelte_check_output(output)
359 assert_that(issues).is_length(1)
360 assert_that(issues[0].end_line).is_none()
361 assert_that(issues[0].end_column).is_none()
364def test_parse_svelte_check_output_same_line_different_column() -> None:
365 """Same-line span preserves end_column when it differs from start."""
366 output = "src/lib/Button.svelte:15:5:15:10 Error Inline span error."
367 issues = parse_svelte_check_output(output)
369 assert_that(issues).is_length(1)
370 assert_that(issues[0].end_line).is_none()
371 assert_that(issues[0].end_column).is_equal_to(10)
374def test_parse_svelte_check_output_warn_machine_format() -> None:
375 """Parse WARN severity in machine format."""
376 output = "WARN src/lib/Card.svelte:8:1 Unused CSS selector."
377 issues = parse_svelte_check_output(output)
379 assert_that(issues).is_length(1)
380 assert_that(issues[0].severity).is_equal_to("warning")
383def test_parse_svelte_check_output_hint_machine_format() -> None:
384 """Parse HINT severity in machine format."""
385 output = "HINT src/lib/Card.svelte:8:1 Consider refactoring."
386 issues = parse_svelte_check_output(output)
388 assert_that(issues).is_length(1)
389 assert_that(issues[0].severity).is_equal_to("hint")
392def test_svelte_check_issue_type() -> None:
393 """Verify parsed issues are SvelteCheckIssue instances."""
394 output = "src/lib/Button.svelte:15:5:15:10 Error Type error."
395 issues = parse_svelte_check_output(output)
397 assert_that(issues).is_length(1)
398 assert_that(issues[0]).is_instance_of(SvelteCheckIssue)