93 lines
2.6 KiB
Python
93 lines
2.6 KiB
Python
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:
|
|
# "<file>\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 <path-to-eslint-output.txt>", 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))
|