import json
import os
import subprocess
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
import ratchet
LINTS = [
"clippy::pedantic",
"clippy::nursery",
"clippy::unwrap_used",
"clippy::panic",
"clippy::wildcard_enum_match_arm",
"clippy::string_slice",
"clippy::trivially_copy_pass_by_ref",
"clippy::many_single_char_names",
]
def collect() -> tuple[dict[str, int], dict[str, list[str]]]:
cmd = [
"cargo",
"clippy",
"--workspace",
"--all-targets",
"--message-format=json",
"--",
]
for lint in LINTS:
cmd += ["--force-warn", lint]
proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8")
if proc.returncode != 0 and not proc.stdout.strip():
print("cargo clippy failed to run:\n" + proc.stderr[-4000:])
raise SystemExit(2)
seen: set[tuple[str, str, int, int]] = set()
findings: dict[str, int] = {}
occurrences: dict[str, list[str]] = {}
for line in proc.stdout.splitlines():
try:
msg = json.loads(line)
except json.JSONDecodeError:
continue
if msg.get("reason") != "compiler-message":
continue
body = msg.get("message") or {}
code = (body.get("code") or {}).get("code") or ""
if not code.startswith("clippy::"):
continue
spans = [s for s in body.get("spans", []) if s.get("is_primary")]
if not spans:
continue
span = spans[0]
path = Path(span["file_name"]).as_posix()
if path.startswith("/") or ":" in path.split("/")[0]:
continue
position = (code, path, span["line_start"], span["column_start"])
if position in seen:
continue
seen.add(position)
findings[code] = findings.get(code, 0) + 1
occurrences.setdefault(code, []).append(
f"{path}:{span['line_start']}: {body.get('message', '')}"
)
return findings, occurrences
def main(argv: list[str]) -> int:
if not Path("Cargo.toml").is_file():
print("check_clippy_ratchet: run from the workspace root")
return 2
if "CLIPPY_RATCHET_TARGET_DIR" in os.environ:
os.environ["CARGO_TARGET_DIR"] = os.environ["CLIPPY_RATCHET_TARGET_DIR"]
findings, occurrences = collect()
title = "pedantic + nursery debt"
regen = "just clippy-debt-record"
rc = ratchet.ratchet(
"clippy_pedantic", title, findings, occurrences, argv, regen=regen
)
if rc != 0 and "--write-baseline" not in argv:
print("\n--- .github/baselines/clippy_pedantic.txt as measured here ---")
print(ratchet.render_baseline(findings, title, regen), end="")
print("--- end ---")
return rc
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))