mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-31 21:17:06 +08:00
132 lines
5.0 KiB
Python
132 lines
5.0 KiB
Python
"""维护 Ruff 诊断只降不增的全仓基线。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
DEFAULT_BASELINE = PROJECT_ROOT / "tests/fixtures/architecture/ruff-baseline.json"
|
|
RUFF_TARGETS = ("app", "tests", "scripts")
|
|
|
|
|
|
def run_ruff() -> list[dict[str, Any]]:
|
|
"""运行 Ruff 并返回结构化诊断;发现存量问题时非零退出属于预期。"""
|
|
result = subprocess.run(
|
|
[sys.executable, "-m", "ruff", "check", *RUFF_TARGETS, "--output-format", "json"],
|
|
cwd=PROJECT_ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
if result.returncode not in {0, 1}:
|
|
raise RuntimeError(result.stderr or result.stdout or "Ruff 执行失败")
|
|
diagnostics = json.loads(result.stdout or "[]")
|
|
if not isinstance(diagnostics, list):
|
|
raise RuntimeError("Ruff JSON 输出不是诊断列表")
|
|
if result.returncode == 0 and diagnostics:
|
|
raise RuntimeError("Ruff 返回成功但仍输出诊断")
|
|
if result.returncode == 1 and not diagnostics:
|
|
raise RuntimeError("Ruff 返回诊断状态但输出为空")
|
|
return diagnostics
|
|
|
|
|
|
def aggregate_diagnostics(
|
|
diagnostics: list[dict[str, Any]],
|
|
) -> dict[str, dict[str, int]]:
|
|
"""把 Ruff 诊断聚合为文件、规则和数量三级基线。"""
|
|
report: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
|
|
for diagnostic in diagnostics:
|
|
path = Path(diagnostic["filename"]).resolve().relative_to(PROJECT_ROOT).as_posix()
|
|
report[path][diagnostic["code"]] += 1
|
|
return {path: dict(sorted(codes.items())) for path, codes in sorted(report.items())}
|
|
|
|
|
|
def classify_counts(
|
|
baseline: dict[str, dict[str, int]],
|
|
current: dict[str, dict[str, int]],
|
|
) -> tuple[list[str], list[str]]:
|
|
"""把计数差异分为不可写入的回退和可固化的低水位下降。"""
|
|
regressions: list[str] = []
|
|
stale: list[str] = []
|
|
for path in sorted(baseline.keys() | current.keys()):
|
|
previous = baseline.get(path, {})
|
|
latest = current.get(path, {})
|
|
for code in sorted(previous.keys() | latest.keys()):
|
|
old_count = previous.get(code, 0)
|
|
new_count = latest.get(code, 0)
|
|
if new_count > old_count:
|
|
if old_count == 0:
|
|
regressions.append(f"{path}: 新增 Ruff 诊断 [{code}] x{new_count}")
|
|
else:
|
|
regressions.append(
|
|
f"{path}: Ruff 诊断增长 [{code}] {old_count}->{new_count}"
|
|
)
|
|
elif new_count < old_count:
|
|
stale.append(
|
|
f"{path}: Ruff 低水位未固化 [{code}] {old_count}->{new_count}"
|
|
)
|
|
return regressions, stale
|
|
|
|
|
|
def compare_counts(
|
|
baseline: dict[str, dict[str, int]],
|
|
current: dict[str, dict[str, int]],
|
|
) -> list[str]:
|
|
"""返回 Ruff 诊断增长和尚未固化的新低水位。"""
|
|
regressions, stale = classify_counts(baseline, current)
|
|
return [*regressions, *stale]
|
|
|
|
|
|
def main() -> int:
|
|
"""执行 Ruff baseline check 或显式 write。"""
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--write", action="store_true", help="写入当前 Ruff 基线")
|
|
parser.add_argument("--baseline", type=Path, default=DEFAULT_BASELINE)
|
|
args = parser.parse_args()
|
|
current = aggregate_diagnostics(run_ruff())
|
|
baseline_exists = args.baseline.exists()
|
|
baseline = (
|
|
json.loads(args.baseline.read_text(encoding="utf-8"))
|
|
if baseline_exists
|
|
else {}
|
|
)
|
|
regressions, stale = classify_counts(baseline, current)
|
|
if args.write:
|
|
if baseline_exists and regressions:
|
|
print("\n".join(regressions))
|
|
print("拒绝写入:当前结果包含 Ruff 回退,--write 只能固化下降后的低水位。")
|
|
return 1
|
|
args.baseline.parent.mkdir(parents=True, exist_ok=True)
|
|
args.baseline.write_text(
|
|
json.dumps(current, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
display_path = (
|
|
args.baseline.relative_to(PROJECT_ROOT)
|
|
if args.baseline.is_relative_to(PROJECT_ROOT)
|
|
else args.baseline
|
|
)
|
|
print(f"已写入 {display_path}")
|
|
return 0
|
|
problems = [*regressions, *stale]
|
|
if problems:
|
|
print("\n".join(problems))
|
|
if regressions:
|
|
print("先消除 Ruff 回退;存在增长时禁止用 --write 覆盖基线。")
|
|
else:
|
|
print("提示:当前只有债务下降,可用 --write 固化新的低水位。")
|
|
return 1
|
|
total = sum(count for codes in current.values() for count in codes.values())
|
|
print(f"Ruff ratchet 通过(低水位已同步:{total} 个诊断)")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|