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

1"""Unit tests for svelte-check parser.""" 

2 

3from __future__ import annotations 

4 

5import json 

6 

7from assertpy import assert_that 

8 

9from lintro.parsers.svelte_check.svelte_check_issue import SvelteCheckIssue 

10from lintro.parsers.svelte_check.svelte_check_parser import parse_svelte_check_output 

11 

12 

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() 

17 

18 

19# --- NDJSON format tests (modern svelte-check --output machine-verbose) --- 

20 

21 

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) 

35 

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") 

42 

43 

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) 

56 

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") 

64 

65 

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) 

78 

79 assert_that(issues).is_length(1) 

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

81 

82 

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) 

107 

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") 

111 

112 

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) 

125 

126 assert_that(issues).is_length(1) 

127 assert_that(issues[0].file).is_equal_to("src/lib/Button.svelte") 

128 

129 

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) 

142 

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) 

146 

147 

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) 

160 

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() 

164 

165 

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) 

178 

179 assert_that(issues).is_length(1) 

180 assert_that(issues[0].file).is_equal_to("src/lib/Button.svelte") 

181 

182 

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) 

196 

197 assert_that(issues).is_length(1) 

198 assert_that(issues[0].code).is_equal_to("ts-2322") 

199 

200 

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) 

213 

214 assert_that(issues).is_length(1) 

215 assert_that(issues[0].code).is_equal_to("") 

216 

217 

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) 

231 

232 assert_that(issues).is_length(1) 

233 assert_that(issues[0].code).is_equal_to("2322") 

234 

235 

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) 

248 

249 assert_that(issues).is_length(1) 

250 

251 

252# --- Legacy plain-text machine-verbose format tests --- 

253 

254 

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) 

259 

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") 

266 

267 

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) 

274 

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") 

279 

280 

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) 

285 

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") 

291 

292 

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) 

297 

298 assert_that(issues).is_length(1) 

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

300 

301 

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) 

308 

309 assert_that(issues).is_length(1) 

310 assert_that(issues[0].severity).is_equal_to("hint") 

311 

312 

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) 

317 

318 assert_that(issues).is_length(1) 

319 assert_that(issues[0].file).is_equal_to("src/lib/Button.svelte") 

320 

321 

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) 

326 

327 assert_that(issues).is_length(1) 

328 assert_that(issues[0].file).is_equal_to("src/lib/Button.svelte") 

329 

330 

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) 

339 

340 assert_that(issues).is_length(1) 

341 assert_that(issues[0].file).is_equal_to("src/lib/Button.svelte") 

342 

343 

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) 

348 

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) 

352 

353 

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) 

358 

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() 

362 

363 

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) 

368 

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) 

372 

373 

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) 

378 

379 assert_that(issues).is_length(1) 

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

381 

382 

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) 

387 

388 assert_that(issues).is_length(1) 

389 assert_that(issues[0].severity).is_equal_to("hint") 

390 

391 

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) 

396 

397 assert_that(issues).is_length(1) 

398 assert_that(issues[0]).is_instance_of(SvelteCheckIssue)