|
2 | 2 | # SPDX-License-Identifier: Apache-2.0 |
3 | 3 |
|
4 | 4 | import ast |
| 5 | +import json |
5 | 6 | import logging |
6 | | -import re |
7 | 7 | import subprocess |
8 | 8 | import tempfile |
9 | 9 | from collections import defaultdict |
@@ -179,50 +179,53 @@ def _run_linter(codebase_path: str) -> dict[str, PythonLinterMessages]: |
179 | 179 | for file in Path(codebase_path).glob("*.py"): |
180 | 180 | processed[file.stem] = PythonLinterMessages() |
181 | 181 |
|
182 | | - # Run ruff linter |
| 182 | + # Run ruff linter with JSON output |
183 | 183 | ruff_bin = find_ruff_bin() |
184 | | - env = {"NO_COLOR": "1"} |
185 | 184 |
|
186 | 185 | ruff_exec = subprocess.run( |
187 | 186 | [ |
188 | 187 | ruff_bin, |
189 | 188 | "check", |
190 | 189 | "--select", |
191 | 190 | "E,F6,F7,F8,SIM,PLC,PLE,PLR,PLW", |
| 191 | + "--output-format=json", |
192 | 192 | codebase_path, |
193 | 193 | ], |
194 | | - env=env, |
195 | 194 | text=True, |
196 | 195 | capture_output=True, |
197 | 196 | check=False, |
198 | 197 | cwd=Path.cwd(), |
199 | 198 | ) |
200 | 199 | ruff_output = ruff_exec.stdout |
201 | 200 |
|
202 | | - # Parse ruff output |
203 | | - if "All checks passed!" in ruff_output: |
204 | | - return processed # no errors or warnings |
205 | | - |
206 | | - pattern = r"(.*):([0-9]*):([0-9]*): ([A-Za-z0-9]*):? (?:\[\*\] )?(.*)\n" |
207 | | - errors = re.findall(pattern, ruff_output) |
| 201 | + # Parse JSON output |
| 202 | + try: |
| 203 | + diagnostics = json.loads(ruff_output) |
| 204 | + except json.JSONDecodeError as e: |
| 205 | + raise RuntimeError(f"Failed to parse ruff JSON output: {e}") |
208 | 206 |
|
209 | | - if errors == []: # output could not be parsed |
210 | | - raise RuntimeError("ruff's output could not be parsed") |
| 207 | + if not diagnostics: |
| 208 | + return processed # no errors or warnings |
211 | 209 |
|
212 | | - try: |
213 | | - for error in errors: |
214 | | - filename, line, column, symbol, message = error |
215 | | - processed[Path(filename).stem].add( |
216 | | - PythonLinterMessage( |
217 | | - type=TYPE_FROM_SYMBOL[re.sub(r"[^A-Za-z]+", "", symbol)], |
218 | | - symbol=symbol, |
219 | | - line=int(line), |
220 | | - column=int(column), |
221 | | - message=message, |
222 | | - ) |
| 210 | + for diagnostic in diagnostics: |
| 211 | + filename = diagnostic["filename"] |
| 212 | + code = diagnostic["code"] |
| 213 | + location = diagnostic["location"] |
| 214 | + message = diagnostic["message"] |
| 215 | + |
| 216 | + # Extract alphabetic prefix from code for type mapping |
| 217 | + alpha_prefix = "".join(c for c in code if c.isalpha()) |
| 218 | + error_type = TYPE_FROM_SYMBOL.get(alpha_prefix, "warning") |
| 219 | + |
| 220 | + processed[Path(filename).stem].add( |
| 221 | + PythonLinterMessage( |
| 222 | + type=error_type, |
| 223 | + symbol=code, |
| 224 | + line=location["row"], |
| 225 | + column=location["column"], |
| 226 | + message=message, |
223 | 227 | ) |
224 | | - except Exception: # output not in expected format |
225 | | - raise RuntimeError("ruff's output not in expected format") |
| 228 | + ) |
226 | 229 |
|
227 | 230 | return processed |
228 | 231 |
|
|
0 commit comments