diff --git a/pyproject.toml b/pyproject.toml index eefffbb..75ec902 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -191,3 +191,54 @@ pythonVersion = "3.10" typeCheckingMode = "standard" reportMissingImports = "warning" reportUninitializedInstanceVariable = "warning" + +# Pin the ruff rule set explicitly. +# +# Without a `select`, ruff applies its *default* rule set, and that +# default is not stable across releases: +# +# ruff 0.15.22 -> 59 rules across 2 categories (E, F) +# ruff 0.16.0 -> 413 rules across 38 categories +# +# A routine pre-commit autoupdate crossing that boundary changed which +# rules run and broke repositories across this organisation, on code +# nobody had touched. +# +# The selection below is deliberately narrow: it is exactly the rule set +# this repository already satisfies, so pinning changes no behaviour +# today. Widening it to the organisation superset +# ("E", "W", "F", "I", "B", "C4", "UP") is worthwhile but is a code +# change, not a configuration one: it currently reports 22 findings, +# 14 of them auto-fixable, with 6 B904 (raise-without-from) needing +# review by hand. That belongs in its own reviewed pull request rather +# than riding along with a version bump. +# +# Note that this pins the rule set at CATEGORY level, not rule level. +# Ruff selectors are prefixes, so a future release adding new E* or F* +# rules would enable them here automatically. Pinning every rule code +# individually would be exhaustive and unmaintainable; the aim is to +# bound the blast radius, reducing "413 rules across 38 categories" +# back to two, not to freeze the set outright. +# +# `line-length` and `target-version` are intentionally left unset so +# ruff's own defaults continue to apply unchanged. + +[tool.ruff.lint] +select = ["E", "F"] + +ignore = [ + "E501", # line-too-long: the formatter owns line length +] + +[tool.ruff.lint.per-file-ignores] +# Test suites legitimately break rules that matter in library code. +# +# None of these codes is reachable under the current `select`, and only +# B-prefixed ones would be reachable under the organisation superset. +# They are listed anyway so that a later widening beyond that superset, +# to "S", "ARG" or "PL", does not immediately flag every test module. +# PLR0917 is the concrete case already seen elsewhere: tests stacking +# @patch decorators cannot satisfy it, because unittest.mock injects +# its mocks positionally, so a keyword-only signature is impossible +# rather than merely inconvenient. +"tests/**/*.py" = ["S101", "ARG", "PLR0913", "PLR0917", "PLR2004"]