Skip to content

Expand the default rule set#23385

Merged
ntBre merged 8 commits intomainfrom
brent/default-rules
Feb 19, 2026
Merged

Expand the default rule set#23385
ntBre merged 8 commits intomainfrom
brent/default-rules

Conversation

@ntBre
Copy link
Contributor

@ntBre ntBre commented Feb 17, 2026

Summary

This PR adds the new default rule set in preview. This ended up being pretty non-invasive because the DEFAULT_SELECTORS are only used in one place where preview isn't set to the default value of false.

I've currently listed each rule with a separate RuleSelector, which I generated with the script below. I thought about trying to be more clever by finding the smallest set of prefix selectors that yield the same rule set, but I figured this would be the easiest way to add and remove rules in the future anyway.

Script

import json
import subprocess
import tomllib
from pathlib import Path
from string import ascii_uppercase

RULES = {
    rule["code"]: rule["linter"]
    for rule in json.loads(
        subprocess.run(
            ["ruff", "rule", "--all", "--output-format=json"],
            check=True,
            text=True,
            capture_output=True,
        ).stdout
    )
}

for code, linter in RULES.items():
    if linter == "Ruff-specific rules":
        RULES[code] = "Ruff"


def kebab_to_pascal(s: str) -> str:
    return "".join(part.title() for part in s.split("-"))


rules = tomllib.loads(Path("proposal.toml").read_text())["lint"]["select"]

for rule in rules:
    linter = kebab_to_pascal(RULES[rule])
    suffix = rule.lstrip(ascii_uppercase)

    prefix = "_"
    match linter:
        case "Flake8Comprehensions":
            suffix = suffix.removeprefix("4")
        case "Pycodestyle":
            prefix = ""
            suffix = rule
        case "Flake8Gettext":
            linter = "Flake8GetText"
        case "Pep8Naming":
            linter = "PEP8Naming"
        case "Pylint":
            prefix = ""
            suffix = rule.removeprefix("PL")
        case "Flake8Debugger":
            suffix = suffix.removeprefix("10")

    print(
        " " * 4,
        f"RuleSelector::rule(RuleCodePrefix::{linter}("
        f"codes::{linter}::{prefix}{suffix})),"
        f" // {rule}",
        sep="",
    )

Test Plan

A new CLI test showing the preview default rules. I filtered down the snapshot to just the linter.rules.enabled section, which isn't strictly necessary but was a lot shorter. I also tested manually in VS Code to make sure I didn't miss wiring up the preview defaults in the server:

image

I also tested in the playground.

@ntBre ntBre added preview Related to preview mode features rule-selection Related to enabling or disabling rules labels Feb 17, 2026
@astral-sh-bot
Copy link

astral-sh-bot bot commented Feb 17, 2026

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+21230 -35 violations, +0 -0 fixes in 16 projects; 40 projects unchanged)

aiven/aiven-client (+41 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ aiven/client/__main__.py:1:1: I001 [*] Import block is un-sorted or un-formatted
+ aiven/client/argx.py:111:9: SIM102 Use a single `if` statement instead of nested `if` statements
+ aiven/client/argx.py:112:13: SIM102 Use a single `if` statement instead of nested `if` statements
+ aiven/client/argx.py:5:1: I001 [*] Import block is un-sorted or un-formatted
+ aiven/client/cli.py:2854:20: C417 Unnecessary `map()` usage (rewrite using a list comprehension)
+ aiven/client/cli.py:2903:22: C417 Unnecessary `map()` usage (rewrite using a list comprehension)
+ aiven/client/cli.py:324:35: FURB167 [*] Use of regular expression alias `re.I`
+ aiven/client/cli.py:4162:9: SIM102 Use a single `if` statement instead of nested `if` statements
+ aiven/client/cli.py:5209:13: SIM102 Use a single `if` statement instead of nested `if` statements
+ aiven/client/cli.py:559:27: TRY201 Use `raise` without specifying exception name
... 31 additional changes omitted for project

PlasmaPy/PlasmaPy (+57 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ .github/scripts/authors_in_cff.py:60:9: LOG015 `info()` call on root logger
+ .github/scripts/authors_in_cff.py:92:5: LOG015 `info()` call on root logger
+ docs/conf.py:59:5: LOG015 `info()` call on root logger
+ docs/conf.py:63:5: LOG015 `warning()` call on root logger
+ src/plasmapy/__init__.py:13:11: RUF022 [*] `__all__` is not sorted
+ src/plasmapy/analysis/swept_langmuir/__init__.py:6:11: RUF022 [*] `__all__` is not sorted
+ src/plasmapy/analysis/swept_langmuir/floating_potential.py:3:11: RUF022 [*] `__all__` is not sorted
+ src/plasmapy/analysis/swept_langmuir/ion_saturation_current.py:5:11: RUF022 [*] `__all__` is not sorted
+ src/plasmapy/diagnostics/charged_particle_radiography/detector_stacks.py:6:11: RUF022 [*] `__all__` is not sorted
+ src/plasmapy/diagnostics/langmuir.py:3:11: RUF022 [*] `__all__` is not sorted
... 37 additional changes omitted for rule RUF022
... 47 additional changes omitted for project

docker/docker-py (+209 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ docker/api/build.py:128:9: SIM102 Use a single `if` statement instead of nested `if` statements
+ docker/api/build.py:133:13: SIM118 Use `key in dict` instead of `key in dict.keys()`
+ docker/api/client.py:259:17: TRY004 Prefer `TypeError` exception for invalid type
+ docker/api/client.py:296:21: PERF403 Use `dict.update` instead of a for-loop
+ docker/api/client.py:512:23: TRY201 Use `raise` without specifying exception name
+ docker/api/container.py:867:13: SIM114 [*] Combine `if` branches using logical `or` operator
+ docker/api/container.py:884:13: SIM114 [*] Combine `if` branches using logical `or` operator
+ docker/api/image.py:401:19: RUF059 Unpacked variable `repo_name` is never used
+ docker/api/image.py:476:19: RUF059 Unpacked variable `repo_name` is never used
+ docker/api/plugin.py:132:19: RUF059 Unpacked variable `repo_name` is never used
... 199 additional changes omitted for project

facebookresearch/chameleon (+303 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ chameleon/inference/alignment.py:13:38: FA102 Missing `from __future__ import annotations`, but uses PEP 585 collection
+ chameleon/inference/alignment.py:13:43: FA102 Missing `from __future__ import annotations`, but uses PEP 585 collection
+ chameleon/inference/alignment.py:17:41: FA102 Missing `from __future__ import annotations`, but uses PEP 585 collection
+ chameleon/inference/alignment.py:17:46: FA102 Missing `from __future__ import annotations`, but uses PEP 585 collection
+ chameleon/inference/alignment.py:31:38: FA102 Missing `from __future__ import annotations`, but uses PEP 585 collection
+ chameleon/inference/alignment.py:31:43: FA102 Missing `from __future__ import annotations`, but uses PEP 585 collection
... 261 additional changes omitted for rule FA102
+ chameleon/inference/chameleon.py:433:12: FA100 Add `from __future__ import annotations` to simplify `typing.Union`
+ chameleon/inference/chameleon.py:434:12: FA100 Add `from __future__ import annotations` to simplify `typing.Union`
+ chameleon/inference/chameleon.py:435:17: FA100 Add `from __future__ import annotations` to simplify `typing.Union`
+ chameleon/inference/chameleon.py:436:22: FA100 Add `from __future__ import annotations` to simplify `typing.Union`
... 293 additional changes omitted for project

ing-bank/probatus (+55 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ probatus/feature_elimination/__init__.py:1:1: I001 [*] Import block is un-sorted or un-formatted
+ probatus/feature_elimination/__init__.py:4:11: RUF022 [*] `__all__` is not sorted
+ probatus/feature_elimination/early_stopping_feature_elimination.py:1:1: I001 [*] Import block is un-sorted or un-formatted
+ probatus/feature_elimination/feature_elimination.py:1018:16: RET504 Unnecessary assignment to `ranking` before `return` statement
+ probatus/feature_elimination/feature_elimination.py:1:1: I001 [*] Import block is un-sorted or un-formatted
+ probatus/feature_elimination/feature_elimination.py:669:13: PLR1730 [*] Replace `if` statement with `current_step = max(current_step, 1)`
+ probatus/feature_elimination/feature_elimination.py:866:13: PLW0133 Missing `raise` statement on exception
+ probatus/feature_elimination/feature_elimination.py:976:30: PLW0211 First argument of a static method should not be named `self`
+ probatus/feature_elimination/feature_elimination.py:989:20: SIM210 Remove unnecessary `True if ... else False`
+ probatus/feature_elimination/feature_elimination.py:991:16: RET504 Unnecessary assignment to `support` before `return` statement
... 45 additional changes omitted for project

latchbio/latch (+6 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ src/latch_cli/centromere/ctx.py:158:20: BLE001 Do not catch blind exception: `Exception`
+ src/latch_cli/main.py:1438:16: BLE001 Do not catch blind exception: `Exception`
+ src/latch_cli/services/launch/type_converter.py:340:16: BLE001 Do not catch blind exception: `Exception`
+ src/latch_cli/services/local_dev_old.py:344:16: BLE001 Do not catch blind exception: `Exception`
+ src/latch_cli/services/local_dev_old.py:430:24: BLE001 Do not catch blind exception: `Exception`
+ src/latch_cli/services/login.py:67:12: BLE001 Do not catch blind exception: `Exception`

prefecthq/prefect (+16246 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview

+ benches/__main__.py:18:11: PLW1510 `subprocess.run` without explicit `check` argument
+ client/client_flow.py:18:35: PLW1508 Invalid type for environment variable default; expected `str` or `None`
+ examples/ai_database_cleanup_with_approval.py:255:24: BLE001 Do not catch blind exception: `Exception`
+ examples/per_worker_task_concurrency.py:120:12: RET504 Unnecessary assignment to `result` before `return` statement
+ examples/run_api_sourced_etl.py:105:12: RET504 Unnecessary assignment to `df` before `return` statement
+ examples/run_api_sourced_etl.py:132:9: PERF401 Use a list comprehension to create a transformed list
+ integration-tests/test_automation_assessments.py:132:19: TRY002 Create your own exception
+ integration-tests/test_automation_assessments.py:180:19: TRY002 Create your own exception
+ integration-tests/test_automation_assessments.py:243:19: TRY002 Create your own exception
+ integration-tests/test_automation_assessments.py:30:17: UP006 [*] Use `dict` instead of `Dict` for type annotation
+ integration-tests/test_automation_assessments.py:315:19: TRY002 Create your own exception
+ integration-tests/test_automation_assessments.py:31:21: UP006 [*] Use `dict` instead of `Dict` for type annotation
+ integration-tests/test_automation_assessments.py:6:1: UP035 [*] Import from `collections.abc` instead: `AsyncGenerator`
+ integration-tests/test_automation_assessments.py:6:1: UP035 `typing.Dict` is deprecated, use `dict` instead
+ integration-tests/test_automation_assessments.py:83:19: PLR1704 Redefining argument with the local name `event`
+ integration-tests/test_automation_assessments.py:87:11: TRY002 Create your own exception
+ integration-tests/test_client_context_lifespan.py:114:19: TRY201 Use `raise` without specifying exception name
+ integration-tests/test_client_context_lifespan.py:4:1: UP035 [*] Import from `collections.abc` instead: `Callable`
+ integration-tests/test_concurrency_leases.py:41:16: RET504 Unnecessary assignment to `limit` before `return` statement
+ integration-tests/test_concurrency_leases.py:69:5: SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
+ integration-tests/test_load_flows_concurrently.py:37:16: BLE001 Do not catch blind exception: `Exception`
+ integration-tests/test_load_flows_concurrently.py:38:60: RUF010 [*] Use explicit conversion flag
+ integration-tests/test_runner_resilience.py:83:12: BLE001 Do not catch blind exception: `Exception`
+ integration-tests/test_schedule_statefulness.py:19:1: UP035 [*] Import from `collections.abc` instead: `Callable`
+ integration-tests/test_schedule_statefulness.py:49:16: BLE001 Do not catch blind exception: `Exception`
+ integration-tests/test_task_worker.py:6:1: UP035 [*] Import from `collections.abc` instead: `AsyncGenerator`
+ integration-tests/test_worker.py:16:39: UP006 [*] Use `list` instead of `List` for type annotation
+ integration-tests/test_worker.py:25:13: PERF401 Use `list.extend` with an async comprehension to create a transformed list
+ integration-tests/test_worker.py:28:32: UP006 [*] Use `list` instead of `List` for type annotation
+ integration-tests/test_worker.py:34:45: RUF100 [*] Unused `noqa` directive (unused: `F821`)
+ integration-tests/test_worker.py:35:13: UP006 [*] Use `list` instead of `List` for type annotation
+ integration-tests/test_worker.py:5:1: UP035 `typing.List` is deprecated, use `list` instead
... 793 additional changes omitted for rule UP035
+ scripts/all_links_should_be_ok.py:38:12: BLE001 Do not catch blind exception: `Exception`
+ scripts/all_links_should_be_ok.py:48:12: BLE001 Do not catch blind exception: `Exception`
... 237 additional changes omitted for rule BLE001
+ scripts/backfill_release_notes.py:134:12: PIE810 Call `startswith` once with a `tuple`
+ scripts/backfill_release_notes.py:167:12: PIE810 Call `startswith` once with a `tuple`
+ scripts/backfill_release_notes.py:199:13: PLR1714 Consider merging multiple comparisons. Use a `set` if the elements are hashable.
+ scripts/backfill_release_notes.py:224:13: SIM102 Use a single `if` statement instead of nested `if` statements
... 16208 additional changes omitted for project

... Truncated remaining completed project reports due to GitHub comment length restrictions

Changes by rule (180 rules affected)

code total + violation - violation + fix - fix
UP045 5834 5834 0 0 0
FA102 2916 2916 0 0 0
UP006 2219 2219 0 0 0
C408 1387 1387 0 0 0
UP037 1130 1130 0 0 0
UP007 902 902 0 0 0
UP035 798 798 0 0 0
RUF100 671 671 0 0 0
FA100 484 484 0 0 0
RET504 457 457 0 0 0
B008 446 446 0 0 0
BLE001 345 345 0 0 0
SIM117 300 300 0 0 0
RUF012 185 185 0 0 0
SIM102 178 178 0 0 0
RUF059 173 173 0 0 0
TRY002 122 122 0 0 0
I001 120 120 0 0 0
TRY300 116 116 0 0 0
PERF401 109 109 0 0 0
PLR0402 86 86 0 0 0
RUF022 84 84 0 0 0
C419 78 78 0 0 0
PYI036 73 73 0 0 0
DTZ001 68 68 0 0 0
SIM118 66 66 0 0 0
TRY004 66 66 0 0 0
TRY201 65 65 0 0 0
RUF015 63 63 0 0 0
SIM115 60 60 0 0 0
FLY002 59 59 0 0 0
PYI063 56 56 0 0 0
B018 52 52 0 0 0
B006 47 47 0 0 0
RUF010 45 45 0 0 0
PIE790 44 44 0 0 0
PYI034 42 42 0 0 0
S110 40 40 0 0 0
PLR1714 35 35 0 0 0
RUF013 33 33 0 0 0
B010 32 32 0 0 0
SIM114 32 32 0 0 0
PLR1704 30 30 0 0 0
G201 30 30 0 0 0
SIM103 30 30 0 0 0
B009 30 30 0 0 0
DTZ005 29 29 0 0 0
C401 27 27 0 0 0
RET501 27 27 0 0 0
C402 27 27 0 0 0
PERF402 26 26 0 0 0
PIE804 26 26 0 0 0
PYI032 26 26 0 0 0
PYI041 23 23 0 0 0
LOG015 21 21 0 0 0
UP032 21 21 0 0 0
PLC0414 21 21 0 0 0
PLW1510 19 19 0 0 0
B015 19 19 0 0 0
UP012 19 19 0 0 0
UP030 19 19 0 0 0
PLW1508 18 18 0 0 0
PLR1711 18 18 0 0 0
PT031 18 18 0 0 0
C403 18 18 0 0 0
PT014 17 17 0 0 0
C405 17 17 0 0 0
PIE807 17 17 0 0 0
PYI055 17 17 0 0 0
C413 16 16 0 0 0
S102 16 16 0 0 0
PYI066 15 15 0 0 0
PLW0602 14 14 0 0 0
PYI019 14 14 0 0 0
F403 14 0 14 0 0
PYI016 13 13 0 0 0
UP004 13 13 0 0 0
E402 13 0 13 0 0
PIE808 11 11 0 0 0
PIE810 11 11 0 0 0
PERF102 11 11 0 0 0
C417 10 10 0 0 0
PLC0208 10 10 0 0 0
B017 10 10 0 0 0
UP009 10 10 0 0 0
SIM201 9 9 0 0 0
C414 9 9 0 0 0
PYI059 9 9 0 0 0
SIM101 8 8 0 0 0
UP034 8 8 0 0 0
D419 8 8 0 0 0
TRY401 8 8 0 0 0
TRY203 8 8 0 0 0
PLR0133 8 8 0 0 0
E712 8 0 8 0 0
B023 7 7 0 0 0
PLR1722 7 7 0 0 0
PLC0206 7 7 0 0 0
UP024 7 7 0 0 0
C400 7 7 0 0 0
B026 7 7 0 0 0
FURB177 7 7 0 0 0
G202 7 7 0 0 0
LOG014 7 7 0 0 0
PLW0127 6 6 0 0 0
PLR2044 6 6 0 0 0
PLW0120 6 6 0 0 0
RUF018 6 6 0 0 0
RUF023 6 6 0 0 0
FURB167 5 5 0 0 0
PERF403 5 5 0 0 0
EXE001 5 5 0 0 0
PIE800 5 5 0 0 0
UP036 5 5 0 0 0
PLR1733 5 5 0 0 0
TC004 5 5 0 0 0
PLR0124 5 5 0 0 0
SIM905 5 5 0 0 0
SIM210 4 4 0 0 0
DTZ007 4 4 0 0 0
FURB136 4 4 0 0 0
DTZ011 4 4 0 0 0
PTH124 4 4 0 0 0
PLE0704 3 3 0 0 0
LOG009 3 3 0 0 0
TC005 3 3 0 0 0
UP031 3 3 0 0 0
FURB188 3 3 0 0 0
B020 3 3 0 0 0
PYI030 3 3 0 0 0
B033 3 3 0 0 0
N999 3 3 0 0 0
PIE796 2 2 0 0 0
FURB129 2 2 0 0 0
EXE002 2 2 0 0 0
DTZ004 2 2 0 0 0
SIM113 2 2 0 0 0
PLE1205 2 2 0 0 0
PLR1730 2 2 0 0 0
PLW0128 2 2 0 0 0
PIE794 2 2 0 0 0
YTT103 2 2 0 0 0
UP010 2 2 0 0 0
SIM211 2 2 0 0 0
PLC0205 2 2 0 0 0
UP028 2 2 0 0 0
PLC0105 2 2 0 0 0
SIM223 2 2 0 0 0
PYI061 2 2 0 0 0
PYI025 2 2 0 0 0
UP018 2 2 0 0 0
FURB105 2 2 0 0 0
FURB157 1 1 0 0 0
PLW1509 1 1 0 0 0
PLW0133 1 1 0 0 0
PLW0211 1 1 0 0 0
UP014 1 1 0 0 0
RUF007 1 1 0 0 0
S112 1 1 0 0 0
B012 1 1 0 0 0
PLW0642 1 1 0 0 0
B013 1 1 0 0 0
YTT301 1 1 0 0 0
DTZ006 1 1 0 0 0
B039 1 1 0 0 0
UP011 1 1 0 0 0
DTZ901 1 1 0 0 0
FURB163 1 1 0 0 0
PLW0129 1 1 0 0 0
B030 1 1 0 0 0
RUF051 1 1 0 0 0
LOG001 1 1 0 0 0
DTZ002 1 1 0 0 0
RUF019 1 1 0 0 0
PLR1736 1 1 0 0 0
DTZ003 1 1 0 0 0
PYI044 1 1 0 0 0
FURB122 1 1 0 0 0
PYI006 1 1 0 0 0
PYI045 1 1 0 0 0

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

✅ ecosystem check detected no format changes.

Comment on lines +325 to +330
In [preview](https://docs.astral.sh/ruff/preview/), Ruff enables an expanded set of default rules
that includes rules from the `B`, `UP`, and `RUF` categories, as well as many more. If you give the
new defaults a try, feel free to leave feedback in the [GitHub
discussion](https://github.com/astral-sh/ruff/discussions/23203), where you can also find the new
rule set listed in full.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not totally sold on including this, especially in the README (but this is how the top of our Rules page is generated). I'd be happy just to put something like this in the release notes instead, but I'm curious what others think.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like including this, but should probably also say something along the lines of "and this will be the default set in a future minor release"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me that's kind of implied by referencing preview, but I don't feel too strongly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it here, and I agree with you that "in a future minor" is implicit. (In fact - if people don't like them in preview, then they won't be stabilized in a future minor!)

@ntBre
Copy link
Contributor Author

ntBre commented Feb 18, 2026

I think there are a few too many ecosystem results to verify them all manually, but I wanted to at least address a few of the top rules:

  • UP045: Optional[T] -> T | None and has an autofix

  • FA102: checks for annotations that are missing from __future__ import annotations

    This one is a bit surprising because the code shouldn't actually work as written if these are true positives. Some projects must have their Python versions misconfigured. I should probably look into this one more closely. It also had 0 hits in my previous ecosystem check, although I may have been running with --isolated.

  • UP006: typing.List -> list, seems preferable and has an autofix

  • C408, UP037, UP007, and UP035 all seem reasonable and have fixes

Those are a few high level thoughts. I'm working on a script to check a sample of the diagnostics directly just to rule out some obvious false positives. I think this is otherwise ready for review!

@ntBre ntBre marked this pull request as ready for review February 18, 2026 18:24
@ntBre ntBre changed the title [WIP] Add new default rules Expand the default rule set Feb 18, 2026
Copy link
Member

@amyreese amyreese left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also curious: how many of the rules that are currently default will get disabled by default in preview mode? Is that worth calling out somewhere?

@ntBre
Copy link
Contributor Author

ntBre commented Feb 18, 2026

I'm pretty happy with the ecosystem results. It looks like prefect only recently (PrefectHQ/prefect#19273) dropped Python 3.9 support, which explains why they have so many UP violations, despite their requires-python>=3.10.

Similarly, chameleon and qdrant-client do not declare versions, so we default them to an older Python version and thus report FA102. I guess we could consider defaulting to the latest Python version for this rule instead of the oldest since the semantics are reversed from most rules that use TargetVersion::linter_version.

C408 is one case where the results might be changing my mind. I think this came up in an earlier discussion, but I kind of like to use the dict(key=value) format when the keys are valid identifiers, and that's one of the common patterns in prefect (1288/1387 are from prefect). I think I might drop this rule for now.

RUF100 also seems somewhat troubling. The examples I checked for prefect were blanket noqas, so it's not even possible to configure around them with lint.external. I'd be curious to get other thoughts on this. I think when I added it to the set I was only picturing Ruff rules, where I think this would be a nice default to have, but it can potentially interact badly with other linters.

The prefect hits for B008 also look like false positives, but they're in functions decorated with decorators from typer and fastapi, so I think it's reasonable. And they can be fixed by configuring lint.flake8-bugbear.extend-immutable-calls.

It looks like PIE796 technically has 2 false positives of this form:

class Store(Enum):
    overwrite = object()
    write = object()
    skip = object()
    ^ PIE796: Enum contains duplicate value: `object()`

    def handle(self, getter: Callable[[], str], path: str) -> None:

but I guess it's probably better to use enum.auto() here anyway.

There's a strange false positive for B030 in setuptools:

        try:
            parsed = self.parsers.get(option_name, lambda x: x)(value)
        except (Exception,) * self.ignore_option_errors:
               ^ B030: `except` handlers should only be exception classes or tuples of exception classes
            return

I assume self.ignore_option_errors is either 1 or 0 and can bypass this exception handler. That seems unusual enough not to worry about.

A couple other rules looked more pedantic to me today, but I don't want to second guess too many of the rules. Hopefully we're about to get some feedback on them anyway! I'm leaning toward moving ahead with this set, with the possible exception of C408.

@ntBre
Copy link
Contributor Author

ntBre commented Feb 18, 2026

I'm also curious: how many of the rules that are currently default will get disabled by default in preview mode? Is that worth calling out somewhere?

I think only a handful of the E rules were removed, but I can double check and try to work that in somewhere!

@amyreese
Copy link
Member

RUF100 also seems somewhat troubling. The examples I checked for prefect were blanket noqas, so it's not even possible to configure around them with lint.external. I'd be curious to get other thoughts on this. I think when I added it to the set I was only picturing Ruff rules, where I think this would be a nice default to have, but it can potentially interact badly with other linters.

Rather than turn off RUF100 (I think that would be a huge miss) maybe we should narrow RUF100 to not fire on blanket noqa, and have a separate code for that? We could combine that with narrowing RUF100 to not overlap with RUF102?

@amyreese
Copy link
Member

Maybe C408 could similarly be narrowed to only fire on dict() with no arguments? Even the documentation for it implies that it only cares about empty dict() vs {}, etc.

@ntBre
Copy link
Contributor Author

ntBre commented Feb 18, 2026

I like those suggestions. It does make some sense to me to leave these rules in the default set and potentially update their behavior instead of removing them.

@dscorbett
Copy link

Maybe C408 could similarly be narrowed to only fire on dict() with no arguments? Even the documentation for it implies that it only cares about empty dict() vs {}, etc.

That could be effected by keeping the rule as is but defaulting allow-dict-calls-with-keyword-arguments to true.

@ntBre
Copy link
Contributor Author

ntBre commented Feb 18, 2026

I'm also curious: how many of the rules that are currently default will get disabled by default in preview mode? Is that worth calling out somewhere?

I think only a handful of the E rules were removed, but I can double check and try to work that in somewhere!

On second thought, I think this will be too verbose to work in smoothly. For reference, these are the removed rules:

E401
E402
E701
E702
E703
E711
E712
E713
E714
E721
E731
E741
E742
E743
F403
F405
F406
F722

so it is mostly E rules (only E902 and E722 are preserved) plus a couple of F rules.

@amyreese
Copy link
Member

On second thought, I think this will be too verbose to work in smoothly. For reference, these are the removed rules:

Could be worth documenting somewhere and reference it in the changelog at the same time. My main consideration would be the UX of users expecting those rules to be enabled, or unexpectedly getting RUF100 for any existing #noqa for those violations.

@ntBre
Copy link
Contributor Author

ntBre commented Feb 18, 2026

I'd be open to suggestions for where to document it! I just think it feels out of place in the README/top of the Rules page, but that's the main place where we discuss the defaults. And it's now documented here in the PR, which is linked from the release notes, at least. I could also add it to the body of the discussion and/or the changelog itself if nothing else.

Copy link
Collaborator

@dylwil3 dylwil3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing! Thanks for all the analysis and discussion that went into this

Comment on lines +325 to +330
In [preview](https://docs.astral.sh/ruff/preview/), Ruff enables an expanded set of default rules
that includes rules from the `B`, `UP`, and `RUF` categories, as well as many more. If you give the
new defaults a try, feel free to leave feedback in the [GitHub
discussion](https://github.com/astral-sh/ruff/discussions/23203), where you can also find the new
rule set listed in full.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it here, and I agree with you that "in a future minor" is implicit. (In fact - if people don't like them in preview, then they won't be stabilized in a future minor!)

@dylwil3
Copy link
Collaborator

dylwil3 commented Feb 19, 2026

I like those suggestions. It does make some sense to me to leave these rules in the default set and potentially update their behavior instead of removing them.

We also don't have to feel too bad about modifying this default set while it's still in preview.

@ntBre ntBre merged commit 222574a into main Feb 19, 2026
42 checks passed
@ntBre ntBre deleted the brent/default-rules branch February 19, 2026 19:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview Related to preview mode features rule-selection Related to enabling or disabling rules

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

Comments