from __future__ import annotations from collections import Counter from dataclasses import dataclass from pathlib import Path import re import sys @dataclass(frozen=True) class RuleCounts: errors: Counter[str] warnings: Counter[str] def _extract_rule_from_line(line: str) -> str | None: # ESLint flat output lines look like: # "\n 12:34 error Message... rule-name" # Only parse "location lines" to avoid counting explanatory multiline blocks. # Require at least two spaces before the rule token; otherwise we might capture the last # word of the message (e.g. "renders") for multiline explanatory errors that omit rule ids. m = re.match(r"^\s*\d+:\d+\s+(error|warning)\s+.+\s{2,}([@\w\-/]+)\s*$", line) if not m: return None return m.group(2) def parse_eslint_output(path: Path) -> RuleCounts: errors: Counter[str] = Counter() warnings: Counter[str] = Counter() with path.open("r", encoding="utf-8", errors="replace") as f: for line in f: rule = _extract_rule_from_line(line) if not rule: continue if " error " in line: errors[rule] += 1 continue if " warning " in line: warnings[rule] += 1 return RuleCounts(errors=errors, warnings=warnings) def _order_bucket(rule: str) -> int: # User requested ordering: # - file size last: max-lines # - function size before last: max-lines-per-function if rule == "max-lines": return 2 if rule == "max-lines-per-function": return 1 return 0 def print_summary(counts: RuleCounts) -> None: print("ERRORS by rule:") for rule, cnt in sorted( counts.errors.items(), key=lambda kv: (_order_bucket(kv[0]), -kv[1], kv[0]), ): print(f"{cnt:4d} {rule}") print("\nWARNINGS by rule:") for rule, cnt in sorted(counts.warnings.items(), key=lambda kv: (-kv[1], kv[0])): print(f"{cnt:4d} {rule}") total_errors = sum(counts.errors.values()) total_warnings = sum(counts.warnings.values()) print(f"\nTotal errors: {total_errors} | Total warnings: {total_warnings}") def main(argv: list[str]) -> int: if len(argv) != 2: print("Usage: python scripts/lintReportSummary.py ", file=sys.stderr) return 2 path = Path(argv[1]) if not path.exists(): print(f"File not found: {path}", file=sys.stderr) return 2 counts = parse_eslint_output(path) print_summary(counts) return 0 if __name__ == "__main__": raise SystemExit(main(sys.argv))