Files
2026-09-27 19:20:43 +08:00

57 lines
3.1 KiB
TOML

# Ruff configuration — pinned deliberately.
#
# Before this file existed the repo had no Ruff config, so `ruff check .` ran whatever the
# installed Ruff version enables by default (Ruff 0.16 turns on a very broad set: UP/RUF/PLR/
# S/TRY/PERF/FURB/YTT/ASYNC...). The same tree therefore produced different findings on
# different machines and between a developer and a runner. Never rely on tool defaults.
#
# Policy, decided from the measured findings rather than from taste:
#
# * `select` is explicit. High-signal correctness rules first, then only the mechanical
# modernisation rules we are willing to apply without human review.
# * BLE001 is selected so the existing `# noqa: BLE001 - <reason>` suppressions stay *used*
# and RUF100 can never delete their rationale — a broad `except Exception` is a legitimate
# choice in hardware, GL and teardown paths. It is deliberately NOT part of the gate.
# * S110 / S112 / E722 ARE part of the gate: a silent handler hides a failure. Silence is
# acceptable only with a reasoned suppression, or with a handler that reports/re-raises.
# * RUF100 reports unused suppressions but is `unfixable`: a machine must not remove a
# suppression whose comment records why it exists. Triage those by hand.
# * The mechanical sweep never uses `--unsafe-fixes`.
#
# The gate (pre-merge / runner) selects the narrow set explicitly, so BLE001 cannot fail a
# build, and only ever touches changed files:
#
# tools/lint_gate.py # the gate: changed files, machine-readable
# ruff format --check <changed files> # never a whole-tree rewrite mid-diff
target-version = "py310"
line-length = 88
[lint]
select = [
"E9", "E722", # syntax/IO errors, and bare `except:`
"E402", # module import not at top of file — SELECTED for visibility, NOT gated.
# `import` after `os.environ[...]` is deliberate in this codebase (the
# environment must be set before Qt/cv2 load), so gating it would be noise;
# but it is the only rule that would catch an accidental import moved above
# that setup, so it stays reported. An intentional site belongs in a
# `# noqa: E402 - <reason>` when it is touched.
"F", # pyflakes: F401 unused import, F811 redefinition, F821/F822 undefined name
"S110", "S112", # try-except-pass / try-except-continue — an invisible failure
"B023", # function definition does not bind loop variable
"PLW1510", # subprocess.run without an explicit check
"RUF100", # unused noqa — reported for triage, never auto-removed
"UP", # pyupgrade (Optional[X] -> X | None, deprecated imports)
"I", # import sorting
"RUF022", "RUF023", # __all__ / __slots__ ordering
"PIE", # flake8-pie
"RET501", "PLR1711", # useless return / return None
"SIM114", # if-with-same-arms
"BLE001", # selected for suppression hygiene only — excluded from the gate
]
unfixable = ["RUF100"]
[format]
quote-style = "double"
indent-style = "space"