Coverage for lintro / parsers / taplo / taplo_parser.py: 100%
41 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"""Parser for taplo output.
3This module provides parsing functionality for taplo TOML linter/formatter
4text output format.
5"""
7from __future__ import annotations
9import re
11from lintro.parsers.base_parser import strip_ansi_codes
12from lintro.parsers.taplo.taplo_issue import TaploIssue
14# Pre-compiled regex patterns for taplo output parsing
15# Pattern for taplo error header: error[code]: message or warning[code]: message
16_HEADER_PATTERN: re.Pattern[str] = re.compile(
17 r"^(error|warning)\[([^\]]+)\]:\s*(.+)$",
18)
20# Pattern for location line: --> file:line:column
21_LOCATION_PATTERN: re.Pattern[str] = re.compile(
22 r"^\s*-->\s*(.+):(\d+):(\d+)\s*$",
23)
25# Pattern for taplo fmt --check output:
26# ERROR taplo:format_files: the file is not properly formatted path="..."
27# Also handles RUST_LOG=error format:
28# ERROR the file is not properly formatted path="..."
29_FMT_CHECK_PATTERN: re.Pattern[str] = re.compile(
30 r'^ERROR\s+(?:taplo:format_files:\s*)?(.+?)\s+path="([^"]+)"',
31)
34def parse_taplo_output(output: str | None) -> list[TaploIssue]:
35 """Parse taplo output into a list of TaploIssue objects.
37 Taplo outputs errors in the format:
38 error[code]: message
39 --> file:line:column
40 |
41 N | code line content
42 | ^ error indicator
44 Example output:
45 error[invalid_value]: invalid value
46 --> pyproject.toml:5:10
47 |
48 5 | version =
49 | ^ expected a value
51 Args:
52 output: The raw output from taplo, or None.
54 Returns:
55 List of TaploIssue objects parsed from the output.
56 """
57 issues: list[TaploIssue] = []
59 # Handle None or empty output
60 if not output or not output.strip():
61 return issues
63 # Strip ANSI codes for consistent parsing across environments
64 output = strip_ansi_codes(output)
66 lines: list[str] = output.splitlines()
67 i: int = 0
69 while i < len(lines):
70 line: str = lines[i]
72 # Try to match taplo fmt --check output first
73 fmt_match: re.Match[str] | None = _FMT_CHECK_PATTERN.match(line)
74 if fmt_match:
75 message: str = fmt_match.group(1).strip()
76 file_path: str = fmt_match.group(2)
77 issues.append(
78 TaploIssue(
79 file=file_path,
80 line=0,
81 column=0,
82 level="error",
83 code="format",
84 message=message,
85 ),
86 )
87 i += 1
88 continue
90 # Try to match error/warning header
91 header_match: re.Match[str] | None = _HEADER_PATTERN.match(line)
92 if header_match:
93 level: str = header_match.group(1)
94 code: str = header_match.group(2)
95 message = header_match.group(3).strip()
97 # Look for location in next lines
98 file_path = ""
99 line_num: int = 0
100 column: int = 0
102 # Search ahead for the location line (usually within 1-2 lines)
103 for j in range(i + 1, min(i + 5, len(lines))):
104 location_match: re.Match[str] | None = _LOCATION_PATTERN.match(lines[j])
105 if location_match:
106 file_path = location_match.group(1)
107 line_num = int(location_match.group(2))
108 column = int(location_match.group(3))
109 break
111 issues.append(
112 TaploIssue(
113 file=file_path,
114 line=line_num,
115 column=column,
116 level=level,
117 code=code,
118 message=message,
119 ),
120 )
122 i += 1
124 return issues