Skip to content

[ADD] ruff: integrate Ruff as opt-in replacement for flake8, black, isort, autoflake and pyupgrade#225

Open
hbto wants to merge 3 commits into
mainfrom
feature/ruff-integration
Open

[ADD] ruff: integrate Ruff as opt-in replacement for flake8, black, isort, autoflake and pyupgrade#225
hbto wants to merge 3 commits into
mainfrom
feature/ruff-integration

Conversation

@hbto

@hbto hbto commented Jun 6, 2026

Copy link
Copy Markdown

# Description

Summary

Adds Ruff support to pre-commit-vauxoo via the existing
--compatibility-version / LINT_COMPATIBILITY_VERSION matrix system.
No behavioral change for existing consumers — Ruff is disabled by
default (position 8 = 10 in the current default string).

What changed

  • pre_commit_vauxoo.py — adds ruff_matrix_value as position 8
    in TOOLS_ORDER. parse_matrix_compatibility() and copy_cfg_files()
    handle it automatically; no signature changes needed.

  • cli.py — documents position 8 = Ruff in the
    --compatibility-version help text.

  • cfg/ruff.toml.jinja (new) — Copier template rendered as
    ruff.toml in the target repo when ruff_matrix_value >= 20.
    Config derived from the actual cfg/.flake8 and cfg/.isort.cfg:
    line-length = 119, all existing ignores carried forward,
    Odoo-specific B006/B008 suppressed, odoo/odoo.addons mapped
    to known-first-party. skip-string-normalization wired from the
    same Copier variable already used by pyproject.toml.jinja.

  • Three .jinja templates — ruff hooks added behind matrix gates:

    ruff_matrix_value mandatory autofix optional
    < 20 (default) flake8 only black + autoflake + isort + pyupgrade flake8 + bugbear
    >= 20 ruff + flake8 ruff check+format + legacy tools ruff UP/SIM/RUF/C90 + flake8
    >= 30 ruff only ruff only ruff only

How to opt in

# Enable ruff alongside legacy tools (transition)
LINT_COMPATIBILITY_VERSION=10.10.10.10.10.10.10.20 pre-commit-vauxoo

# Enable ruff only, remove legacy tools
LINT_COMPATIBILITY_VERSION=10.10.10.10.10.10.10.30 pre-commit-vauxoo

Known behavioral delta

isort's ODOO / ODOO_ADDONS custom sections are not supported by
ruff's isort. Both are mapped to known-first-party; odoo and
odoo.addons imports merge into one group instead of two. Cosmetic
only — no CI failures. Documented in workspace/04-rule-mapping.md.

Tools replaced

┌────────────────┬───────────────────────────────────┐
│     Legacy     │          Ruff equivalent          │
├────────────────┼───────────────────────────────────┤
│ flake8         │ ruff check                        │
├────────────────┼───────────────────────────────────┤
│ flake8-bugbear │ ruff check (B rules)              │
├────────────────┼───────────────────────────────────┤
│ black          │ ruff format                       │
├────────────────┼───────────────────────────────────┤
│ isort          │ ruff check (I rules)              │
├────────────────┼───────────────────────────────────┤
│ autoflake      │ ruff check --fix (F401/F811/F841) │
├────────────────┼───────────────────────────────────┤
│ pyupgrade      │ ruff check --select UP            │
└────────────────┴───────────────────────────────────┘

pylint, pylint-odoo, OCA hooks, ESLint and Prettier are unchanged.
  [ADD] ruff integration via matrix position 8 (ruff_matrix_value)

Adds ruff as an opt-in linter/formatter via --compatibility-version.
Position 8 controls the behaviour:

  < 20 (default=10): disabled — existing consumers unaffected
  >= 20: ruff check+fix runs alongside flake8 and black (transition)
  >= 30: ruff replaces flake8/black/isort/autoflake/pyupgrade entirely

New config template ruff.toml.jinja:
  line-length=119, target-version=py310, select E/W/F/B/C4/I/UP/N.
  Ignores carried forward from .flake8 plus Odoo-specific B006/B008.
  isort: odoo+odoo.addons merged into first-party (cosmetic delta from
  legacy ODOO/ODOO_ADDONS sections — no lint impact).
  skip_string_normalization wired to ruff format quote-style.

Template changes:
  .pre-commit-config.yaml.jinja       ruff mandatory hook (>= 20), flake8 gated (< 30)
  .pre-commit-config-autofix.yaml.jinja  ruff check+fix (>= 20), ruff-format (>= 30),
                                         black/autoflake/isort/pyupgrade gated (< 30)
  .pre-commit-config-optional.yaml.jinja ruff SIM/RUF/C90 via --extend-select (>= 20),
                                         flake8-bugbear gated (< 30)

Deviations discovered during CI validation:
  1. ruff-format gated at >= 30, not >= 20. Running ruff-format and black
     simultaneously causes an infinite format loop: the two formatters
     disagree on wrapping point for multiline assert expressions. At
     value=20, ruff provides lint fixes only; black remains the formatter.
  2. B036 and E123 dropped from ignore list. Neither rule exists in ruff
     (rejected as "Unknown rule selector"). No practical impact.
  3. B018 added to __manifest__.py per-file-ignores. ruff B018 fires on
     Odoo manifest bare dict literals, which are intentional.
  4. Optional hook uses --extend-select, not --select. CLI --select
     overrides the config ignore list, re-triggering UP031.
  5. ci/bootstrap.py, docs/conf.py, pre_commit_vauxoo.py: .format() calls
     converted to f-strings (UP032). The docker test runs ruff on the
     repo's own source; these files had to comply.
  6. tests/test_pre_commit_vauxoo.py: two assert statements reformatted to
     a layout both black and ruff-format accept without conflict.

Benchmark (25 files, ruff 0.15.16 / black 26.5.1 / isort, Ubuntu 24.04):
  ruff check + ruff format              ~130 ms
  black + isort (partial legacy)        ~570 ms   (~4x faster)
  full legacy stack estimate
    (+ flake8 + autoflake + pyupgrade)  ~1270 ms  (~10x faster)

@hbto hbto force-pushed the feature/ruff-integration branch from d6df90d to 5ae14a8 Compare June 6, 2026 02:55
Adds ruff as an opt-in linter/formatter via --compatibility-version.
Position 8 controls the behaviour:

  < 20 (default=10): disabled — existing consumers unaffected
  >= 20: ruff check+fix runs alongside flake8 and black (transition)
  >= 30: ruff replaces flake8/black/isort/autoflake/pyupgrade entirely

New config template ruff.toml.jinja:
  line-length=119, target-version=py310, select E/W/F/B/C4/I/UP/N.
  Ignores carried forward from .flake8 plus Odoo-specific B006/B008.
  isort: odoo+odoo.addons merged into first-party (cosmetic delta from
  legacy ODOO/ODOO_ADDONS sections — no lint impact).
  skip_string_normalization wired to ruff format quote-style.

Template changes:
  .pre-commit-config.yaml.jinja       ruff mandatory hook (>= 20), flake8 gated (< 30)
  .pre-commit-config-autofix.yaml.jinja  ruff check+fix (>= 20), ruff-format (>= 30),
                                         black/autoflake/isort/pyupgrade gated (< 30)
  .pre-commit-config-optional.yaml.jinja ruff SIM/RUF/C90 via --extend-select (>= 20),
                                         flake8-bugbear gated (< 30)

Deviations discovered during CI validation:
  1. ruff-format gated at >= 30, not >= 20. Running ruff-format and black
     simultaneously causes an infinite format loop: the two formatters
     disagree on wrapping point for multiline assert expressions. At
     value=20, ruff provides lint fixes only; black remains the formatter.
  2. B036 and E123 dropped from ignore list. Neither rule exists in ruff
     (rejected as "Unknown rule selector"). No practical impact.
  3. B018 added to __manifest__.py per-file-ignores. ruff B018 fires on
     Odoo manifest bare dict literals, which are intentional.
  4. Optional hook uses --extend-select, not --select. CLI --select
     overrides the config ignore list, re-triggering UP031.
  5. ci/bootstrap.py, docs/conf.py, pre_commit_vauxoo.py: .format() calls
     converted to f-strings (UP032). The docker test runs ruff on the
     repo's own source; these files had to comply.
  6. tests/test_pre_commit_vauxoo.py: two assert statements reformatted to
     a layout both black and ruff-format accept without conflict.

Benchmark (25 files, ruff 0.15.16 / black 26.5.1 / isort, Ubuntu 24.04):
  ruff check + ruff format              ~130 ms
  black + isort (partial legacy)        ~570 ms   (~4x faster)
  full legacy stack estimate
    (+ flake8 + autoflake + pyupgrade)  ~1270 ms  (~10x faster)
@hbto hbto force-pushed the feature/ruff-integration branch from 5ae14a8 to bcbfd2b Compare June 6, 2026 03:00
@hbto

hbto commented Jun 6, 2026

Copy link
Copy Markdown
Author

@moylop260 Could please review?

@hbto hbto requested a review from moylop260 June 6, 2026 03:18
Covers the ruff_matrix_value code path with two new test classes,
appended at the end of tests/test_pre_commit_vauxoo.py without
modifying any existing test.

TestParseMatrixCompatibility (8 unit tests):
- Verifies ruff_matrix_value sits at TOOLS_ORDER index 7 (position 8).
- Confirms parse_matrix_compatibility() extracts the correct value from
  an 8-element dot-separated string: values 10, 20, 30.
- Boundary inputs: 0 → DEFAULT_MAX_COMPATIBILITY, missing 8th position
  → DEFAULT_MAX_COMPATIBILITY, empty string → DEFAULT_MAX_COMPATIBILITY.
- Confirms other tool positions are not contaminated when ruff=20.

TestRuffTemplateRendering (22 integration tests):
- Uses --only-cp-cfg to render config files, then reads the generated
  YAML and ruff.toml directly — no mocking, no subprocess pre-commit run.
- Mandatory config: ruff hook absent at values 10 and 19 (off-by-one
  boundary), present at 20; flake8 coexists at 20, disappears at 30.
- Autofix config: ruff-format absent at 20 and 29 (ADR-007: prevents
  black/ruff-format infinite loop), present at 30; black coexists at 20,
  disappears at 30.
- Optional config: ruff hook uses --extend-select (not bare --select)
  to preserve the UP031 ignore list (ADR-010); flake8 coexists at 20,
  disappears at 30.
- ruff.toml: line-length=119, UP031 in ignore, B018 in __manifest__.py
  per-file-ignores; quote-style="double" by default,
  quote-style="preserve" when BLACK_SKIP_STRING_NORMALIZATION=true.
@hbto hbto force-pushed the feature/ruff-integration branch from 29b941a to 26db2b8 Compare June 9, 2026 15:58
…patterns

Add TestRuffViolationDetection class with 11 end-to-end tests that invoke
ruff directly against Python fixtures using the generated ruff.toml:

- Group A (6 tests): verify Odoo-specific suppressions in ruff.toml.jinja
  prevent false positives (UP031, B018, F401, B006, B008, E501)
- Group B (5 tests): verify modernization rules fire on real improvement
  opportunities (UP032, F841, C400, B007, I001)

Also add ruff to test-requirements.txt.
@hbto hbto force-pushed the feature/ruff-integration branch from 5b79a35 to c624688 Compare June 9, 2026 21:08
@hbto

hbto commented Jun 9, 2026

Copy link
Copy Markdown
Author

@moylop260 Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant