Skip to content

[pylint] Implement swap-with-temporary-variable (PLR1712)#22205

Merged
ntBre merged 1 commit intoastral-sh:mainfrom
Bnyro:temporary-variable-swap
Feb 19, 2026
Merged

[pylint] Implement swap-with-temporary-variable (PLR1712)#22205
ntBre merged 1 commit intoastral-sh:mainfrom
Bnyro:temporary-variable-swap

Conversation

@Bnyro
Copy link
Copy Markdown
Contributor

@Bnyro Bnyro commented Dec 26, 2025

Summary

This PR implements the consider-swap-variables rule from pylint. Basically it tries to find code parts that swap two variables with each other using a temporary variable.

Example code:

temp = x
x = y
y = temp

can be simplified to

x, y = y, x

related:

Test Plan

I've added new snapshots tests.

PS: Since this is my first contribution here and I'm not too familiar with the codebase, suggestions are very welcome! The implementation might also not be 100% memory-optimized yet, since we use clone a few times.

@ntBre ntBre added rule Implementing or modifying a lint rule preview Related to preview mode features labels Dec 29, 2025
Copy link
Copy Markdown
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

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

Thank you! This looks good to me overall, just a few comments.

Comment thread crates/ruff_linter/src/rules/pylint/rules/consider_swap_variables.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/consider_swap_variables.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/consider_swap_variables.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/consider_swap_variables.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/consider_swap_variables.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/consider_swap_variables.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/consider_swap_variables.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/consider_swap_variables.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/consider_swap_variables.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/consider_swap_variables.rs Outdated
@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot Bot commented Dec 29, 2025

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+405 -32412 violations, +0 -0 fixes in 32 projects; 24 projects unchanged)

aiven/aiven-client (+0 -41 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 (+15 -74 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: panic: Panicked at crates/ruff_python_semantic/src/definition.rs:285:26 when checking `/home/runner/work/ruff/ruff/checkouts/PlasmaPy:PlasmaPy/docs/conf.py`: `index out of bounds: the len is 0 but the index is 0`
- docs/conf.py:39:8: PLC2701 Private name import `_author_list_from_cff`
- docs/conf.py:40:8: PLC2701 Private name import `_changelog_index`
- docs/conf.py:41:8: PLC2701 Private name import `_global_substitutions`
- docs/conf.py:59:5: LOG015 `info()` call on root logger
- docs/conf.py:63:5: LOG015 `warning()` call on root logger
+ docs/notebooks/formulary/braginskii.ipynb: panic: Panicked at crates/ruff_python_semantic/src/definition.rs:285:26 when checking `/home/runner/work/ruff/ruff/checkouts/PlasmaPy:PlasmaPy/docs/notebooks/formulary/braginskii.ipynb`: `index out of bounds: the len is 0 but the index is 0`
- src/plasmapy/__init__.py:13:11: RUF022 [*] `__all__` is not sorted
... 79 additional changes omitted for project

apache/airflow (+84 -6274 violations, +0 -0 fixes)

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

+ airflow-core/docs/conf.py: panic: Panicked at crates/ruff_python_semantic/src/definition.rs:285:26 when checking `/home/runner/work/ruff/ruff/checkouts/apache:airflow/airflow-core/docs/conf.py`: `index out of bounds: the len is 0 but the index is 0`
- airflow-core/docs/conf.py:107:6: COM812 [*] Trailing comma missing
- airflow-core/docs/conf.py:146:5: ANN201 Missing return type annotation for public function `add_airflow_core_exclude_patterns_to_sphinx`
- airflow-core/docs/conf.py:168:13: PERF401 Use `list.extend` to create a transformed list
- airflow-core/docs/conf.py:172:13: PERF401 Use `list.extend` to create a transformed list
- airflow-core/docs/conf.py:1:1: CPY001 Missing copyright notice at top of file
- airflow-core/docs/conf.py:1:1: INP001 File `airflow-core/docs/conf.py` is part of an implicit namespace package. Add an `__init__.py`.
- airflow-core/docs/conf.py:241:19: COM812 [*] Trailing comma missing
- airflow-core/docs/conf.py:247:3: FIX002 Line contains TODO, consider resolving the issue
- airflow-core/docs/conf.py:247:3: TD002 Missing author in TODO; try: `# TODO(): ...` or `# TODO @: ...`
... 6348 additional changes omitted for project

apache/superset (+14 -1147 violations, +0 -0 fixes)

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

+ docker/pythonpath_dev/superset_config.py: panic: Panicked at crates/ruff_python_semantic/src/definition.rs:285:26 when checking `/home/runner/work/ruff/ruff/checkouts/apache:superset/docker/pythonpath_dev/superset_config.py`: `index out of bounds: the len is 0 but the index is 0`
- docker/pythonpath_dev/superset_config.py:123:16: PTH120 `os.path.dirname()` should be replaced by `Path.parent`
- docker/pythonpath_dev/superset_config.py:124:21: PTH100 `os.path.abspath()` should be replaced by `Path.resolve()`
- docker/pythonpath_dev/superset_config.py:125:65: COM812 [*] Trailing comma missing
- docker/pythonpath_dev/superset_config.py:125:9: PTH118 `os.path.join()` should be replaced by `Path` with `/` operator
- docker/pythonpath_dev/superset_config.py:128:41: PGH004 Use specific rule codes when using `noqa`
- docker/pythonpath_dev/superset_config.py:141:84: COM812 [*] Trailing comma missing
- docker/pythonpath_dev/superset_config.py:1:1: CPY001 Missing copyright notice at top of file
- docker/pythonpath_dev/superset_config.py:1:1: D100 Missing docstring in public module
- docker/pythonpath_dev/superset_config.py:1:1: INP001 File `docker/pythonpath_dev/superset_config.py` is part of an implicit namespace package. Add an `__init__.py`.
... 1151 additional changes omitted for project

aws/aws-sam-cli (+4 -10 violations, +0 -0 fixes)

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

+ installer/pyinstaller/hook-samcli.py: panic: Panicked at crates/ruff_python_semantic/src/definition.rs:285:26 when checking `/home/runner/work/ruff/ruff/checkouts/aws:aws-sam-cli/installer/pyinstaller/hook-samcli.py`: `index out of bounds: the len is 0 but the index is 0`
- installer/pyinstaller/hook-samcli.py:1:1: I001 [*] Import block is un-sorted or un-formatted
+ samcli/commands/common/execution/history/options.py: panic: Panicked at crates/ruff_python_semantic/src/definition.rs:285:26 when checking `/home/runner/work/ruff/ruff/checkouts/aws:aws-sam-cli/samcli/commands/common/execution/history/options.py`: `index out of bounds: the len is 0 but the index is 0`
+ samcli/hook_packages/terraform/copy_terraform_built_artifacts.py: panic: Panicked at crates/ruff_python_semantic/src/definition.rs:285:26 when checking `/home/runner/work/ruff/ruff/checkouts/aws:aws-sam-cli/samcli/hook_packages/terraform/copy_terraform_built_artifacts.py`: `index out of bounds: the len is 0 but the index is 0`
+ tests/unit/commands/pipeline/bootstrap/test_guided_context.py: panic: Panicked at crates/ruff_python_semantic/src/definition.rs:285:26 when checking `/home/runner/work/ruff/ruff/checkouts/aws:aws-sam-cli/tests/unit/commands/pipeline/bootstrap/test_guided_context.py`: `index out of bounds: the len is 0 but the index is 0`
- tests/unit/commands/pipeline/bootstrap/test_guided_context.py:119:9: PLR6301 Method `test_guided_context_will_not_prompt_for_fields_that_are_already_provided_oidc_gitlab` could be a function, class method, or static method
- tests/unit/commands/pipeline/bootstrap/test_guided_context.py:1:1: I001 [*] Import block is un-sorted or un-formatted
- tests/unit/commands/pipeline/bootstrap/test_guided_context.py:37:9: PLR6301 Method `test_guided_context_will_not_prompt_for_fields_that_are_already_provided` could be a function, class method, or static method
- tests/unit/commands/pipeline/bootstrap/test_guided_context.py:412:9: PLR6301 Method `test_prompt_account_id_can_display_profiles_and_environment` could be a function, class method, or static method
- tests/unit/commands/pipeline/bootstrap/test_guided_context.py:431:9: PLR6301 Method `test_prompt_account_id_wont_show_environment_option_when_it_doesnt_exist` could be a function, class method, or static method
... 4 additional changes omitted for project

bokeh/bokeh (+28 -1382 violations, +0 -0 fixes)

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

+ docs/bokeh/source/docs/first_steps/examples/first_steps_6_row_layout.py: panic: Panicked at crates/ruff_python_semantic/src/definition.rs:285:26 when checking `/home/runner/work/ruff/ruff/checkouts/bokeh:bokeh/docs/bokeh/source/docs/first_steps/examples/first_steps_6_row_layout.py`: `index out of bounds: the len is 0 but the index is 0`
- docs/bokeh/source/docs/first_steps/examples/first_steps_6_row_layout.py:1:1: CPY001 Missing copyright notice at top of file
- docs/bokeh/source/docs/first_steps/examples/first_steps_6_row_layout.py:1:1: D100 Missing docstring in public module
- docs/bokeh/source/docs/first_steps/examples/first_steps_6_row_layout.py:1:1: INP001 File `docs/bokeh/source/docs/first_steps/examples/first_steps_6_row_layout.py` is part of an implicit namespace package. Add an `__init__.py`.
+ docs/bokeh/source/docs/first_steps/examples/first_steps_6_row_layout_responsive.py: panic: Panicked at crates/ruff_python_semantic/src/definition.rs:285:26 when checking `/home/runner/work/ruff/ruff/checkouts/bokeh:bokeh/docs/bokeh/source/docs/first_steps/examples/first_steps_6_row_layout_responsive.py`: `index out of bounds: the len is 0 but the index is 0`
- docs/bokeh/source/docs/first_steps/examples/first_steps_6_row_layout_responsive.py:1:1: CPY001 Missing copyright notice at top of file
- docs/bokeh/source/docs/first_steps/examples/first_steps_6_row_layout_responsive.py:1:1: D100 Missing docstring in public module
- docs/bokeh/source/docs/first_steps/examples/first_steps_6_row_layout_responsive.py:1:1: INP001 File `docs/bokeh/source/docs/first_steps/examples/first_steps_6_row_layout_responsive.py` is part of an implicit namespace package. Add an `__init__.py`.
+ examples/advanced/extensions/widget.py: panic: Panicked at crates/ruff_python_semantic/src/definition.rs:285:26 when checking `/home/runner/work/ruff/ruff/checkouts/bokeh:bokeh/examples/advanced/extensions/widget.py`: `index out of bounds: the len is 0 but the index is 0`
- examples/advanced/extensions/widget.py:15:22: RUF012 Mutable default value for class attribute
... 1400 additional changes omitted for project

docker/docker-py (+0 -209 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 (+0 -303 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

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

Changes by rule (409 rules affected)

code total + violation - violation + fix - fix
UP045 5895 0 5895 0 0
FA102 3016 0 3016 0 0
UP006 2226 0 2226 0 0
C408 1469 0 1469 0 0
ANN001 1374 0 1374 0 0
UP037 1130 0 1130 0 0
UP007 905 0 905 0 0
COM812 835 0 835 0 0
ANN201 815 0 815 0 0
UP035 814 0 814 0 0
RUF100 677 0 677 0 0
FA100 547 0 547 0 0
RET504 480 0 480 0 0
PLR6301 462 0 462 0 0
B008 446 0 446 0 0
BLE001 374 0 374 0 0
panic: 370 370 0 0 0
DOC201 370 0 370 0 0
D212 369 0 369 0 0
SIM117 314 0 314 0 0
ERA001 300 0 300 0 0
ANN202 256 0 256 0 0
RUF012 223 0 223 0 0
D102 209 0 209 0 0
TRY003 199 0 199 0 0
PLC0415 196 0 196 0 0
SIM102 191 0 191 0 0
E265 180 0 180 0 0
RUF059 179 0 179 0 0
D103 163 0 163 0 0
ARG002 161 0 161 0 0
Q000 161 0 161 0 0
CPY001 138 0 138 0 0
RUF067 137 0 137 0 0
ANN401 132 0 132 0 0
TRY002 129 0 129 0 0
TRY300 127 0 127 0 0
I001 125 0 125 0 0
PERF401 116 0 116 0 0
S101 112 0 112 0 0
DTZ001 110 0 110 0 0
PLR0917 107 0 107 0 0
D205 104 0 104 0 0
EM102 103 0 103 0 0
EM101 99 0 99 0 0
T201 99 0 99 0 0
AIR311 98 0 98 0 0
DOC501 97 0 97 0 0
E501 93 0 93 0 0
RUF022 89 0 89 0 0
E302 89 0 89 0 0
PLR0402 87 0 87 0 0
FBT001 86 0 86 0 0
E402 85 13 72 0 0
PTH118 82 0 82 0 0
C419 81 0 81 0 0
TRY004 74 0 74 0 0
SLF001 73 0 73 0 0
ANN204 73 0 73 0 0
PYI036 73 0 73 0 0
PLR6201 71 0 71 0 0
TRY201 70 0 70 0 0
D100 70 0 70 0 0
PLR0913 69 0 69 0 0
SIM118 69 0 69 0 0
FBT002 67 0 67 0 0
ANN003 66 0 66 0 0
FIX002 64 0 64 0 0
AIR001 64 0 64 0 0
RUF015 64 0 64 0 0
TD002 63 0 63 0 0
TD003 63 0 63 0 0
D101 62 0 62 0 0
D401 62 0 62 0 0
D107 61 0 61 0 0
FLY002 61 0 61 0 0
SIM115 60 0 60 0 0
D210 58 0 58 0 0
TID252 58 0 58 0 0
PYI063 56 0 56 0 0
ARG003 53 0 53 0 0
B018 53 0 53 0 0
LOG015 51 0 51 0 0
D415 49 0 49 0 0
B006 49 0 49 0 0
PLR2004 48 0 48 0 0
D400 48 0 48 0 0
PIE790 46 0 46 0 0
RUF010 45 0 45 0 0
ARG001 44 0 44 0 0
Q002 43 0 43 0 0
S110 43 0 43 0 0
C901 42 0 42 0 0
RET505 42 0 42 0 0
PYI034 42 0 42 0 0
D300 41 0 41 0 0
SIM103 40 0 40 0 0
PLR1714 38 0 38 0 0
TC003 36 0 36 0 0
D105 34 0 34 0 0
SIM114 34 0 34 0 0
B009 34 0 34 0 0
G201 34 0 34 0 0
B010 33 0 33 0 0
RUF013 33 0 33 0 0
RET501 32 0 32 0 0
D200 32 0 32 0 0
DTZ005 31 0 31 0 0
B904 30 0 30 0 0
PLR1704 30 0 30 0 0
INP001 29 0 29 0 0
PTH123 29 0 29 0 0
C401 28 0 28 0 0
PLR6104 28 0 28 0 0
PLC2701 27 0 27 0 0
RUF052 27 0 27 0 0
PYI032 27 0 27 0 0
C402 27 0 27 0 0
PLR0912 26 0 26 0 0
E261 26 0 26 0 0
PERF402 26 0 26 0 0
PIE804 26 0 26 0 0
RUF036 25 0 25 0 0
A002 24 0 24 0 0
PYI041 23 0 23 0 0
PLC0414 23 0 23 0 0
PTH120 22 0 22 0 0
PLW0603 22 0 22 0 0
S106 21 0 21 0 0
TC006 21 0 21 0 0
E305 21 0 21 0 0
UP032 21 0 21 0 0
PT007 20 0 20 0 0
PLR0904 19 0 19 0 0
PLW1508 19 0 19 0 0
PLW1510 19 0 19 0 0
B015 19 0 19 0 0
UP012 19 0 19 0 0
UP030 19 0 19 0 0
E241 18 0 18 0 0
E251 18 0 18 0 0
PLR1711 18 0 18 0 0
PT031 18 0 18 0 0
C403 18 0 18 0 0
D404 17 0 17 0 0
FURB118 17 0 17 0 0
PLW1514 17 0 17 0 0
D202 17 0 17 0 0
D419 17 0 17 0 0
PT014 17 0 17 0 0
C405 17 0 17 0 0
PIE807 17 0 17 0 0
PYI055 17 0 17 0 0
RUF031 16 0 16 0 0
SIM108 16 0 16 0 0
PGH003 16 0 16 0 0
N815 16 0 16 0 0
TC002 16 0 16 0 0
C413 16 0 16 0 0
S102 16 0 16 0 0
PLR0914 15 0 15 0 0
PTH100 15 0 15 0 0
ANN205 15 0 15 0 0
PYI016 15 0 15 0 0
PYI066 15 0 15 0 0
F403 14 14 0 0 0
FBT003 14 0 14 0 0
PLR0915 14 0 14 0 0
PLR1702 14 0 14 0 0
DOC402 14 0 14 0 0
TRY400 14 0 14 0 0
PLW0602 14 0 14 0 0
PYI019 14 0 14 0 0
ANN002 13 0 13 0 0
AIR321 13 0 13 0 0
D209 13 0 13 0 0
PIE808 13 0 13 0 0
PLW3201 13 0 13 0 0
UP004 13 0 13 0 0
ISC004 12 0 12 0 0
D204 12 0 12 0 0
C417 11 0 11 0 0
TD004 11 0 11 0 0
PLC1901 11 0 11 0 0
PYI059 11 0 11 0 0
PIE810 11 0 11 0 0
PERF102 11 0 11 0 0
TRY401 10 0 10 0 0
RSE102 10 0 10 0 0
SIM201 10 0 10 0 0
FURB101 10 0 10 0 0
UP031 10 0 10 0 0
PLC0208 10 0 10 0 0
B017 10 0 10 0 0
UP009 10 0 10 0 0
E266 10 0 10 0 0
PLR0911 9 0 9 0 0
RUF069 9 0 9 0 0
PTH110 9 0 9 0 0
UP034 9 0 9 0 0
SIM101 9 0 9 0 0
C414 9 0 9 0 0
E712 8 8 0 0 0
FIX004 8 0 8 0 0
TD006 8 0 8 0 0
N806 8 0 8 0 0
S108 8 0 8 0 0
PLC2801 8 0 8 0 0
PT019 8 0 8 0 0
RET503 8 0 8 0 0
PIE800 8 0 8 0 0
TRY203 8 0 8 0 0
PLR0133 8 0 8 0 0
PTH103 8 0 8 0 0
ANN206 7 0 7 0 0
C416 7 0 7 0 0
PTH119 7 0 7 0 0
TC001 7 0 7 0 0
RUF065 7 0 7 0 0
B023 7 0 7 0 0
PLR1722 7 0 7 0 0
PLC0206 7 0 7 0 0
UP024 7 0 7 0 0
C400 7 0 7 0 0
B026 7 0 7 0 0
FURB177 7 0 7 0 0
G202 7 0 7 0 0
LOG014 7 0 7 0 0
EXE001 6 0 6 0 0
S105 6 0 6 0 0
S404 6 0 6 0 0
ARG004 6 0 6 0 0
RUF043 6 0 6 0 0
FURB189 6 0 6 0 0
E221 6 0 6 0 0
E201 6 0 6 0 0
E202 6 0 6 0 0
A001 6 0 6 0 0
PLW0127 6 0 6 0 0
PLR2044 6 0 6 0 0
PLW0120 6 0 6 0 0
RUF018 6 0 6 0 0
RUF023 6 0 6 0 0
FURB167 5 0 5 0 0
PLR5501 5 0 5 0 0
FURB110 5 0 5 0 0
S403 5 0 5 0 0
ARG005 5 0 5 0 0
PGH004 5 0 5 0 0
E226 5 0 5 0 0
PERF403 5 0 5 0 0
UP036 5 0 5 0 0
PLR1733 5 0 5 0 0
TC004 5 0 5 0 0
PLR0124 5 0 5 0 0
SIM905 5 0 5 0 0
RUF001 5 0 5 0 0
FURB103 5 0 5 0 0
PLR0916 5 0 5 0 0
FURB140 4 0 4 0 0
PERF203 4 0 4 0 0
RUF027 4 0 4 0 0
PTH107 4 0 4 0 0
TD005 4 0 4 0 0
D413 4 0 4 0 0
DOC202 4 0 4 0 0
PYI030 4 0 4 0 0
D208 4 0 4 0 0
S603 4 0 4 0 0
FURB188 4 0 4 0 0
SIM113 4 0 4 0 0
SIM210 4 0 4 0 0
RUF029 4 0 4 0 0
RUF003 4 0 4 0 0
DTZ007 4 0 4 0 0
FURB136 4 0 4 0 0
DTZ011 4 0 4 0 0
PTH124 4 0 4 0 0
S607 4 0 4 0 0
PTH207 4 0 4 0 0
TRY301 3 0 3 0 0
PLR1730 3 0 3 0 0
PTH111 3 0 3 0 0
FURB154 3 0 3 0 0
FURB142 3 0 3 0 0
FURB113 3 0 3 0 0
B905 3 0 3 0 0
FURB129 3 0 3 0 0
S608 3 0 3 0 0
TD001 3 0 3 0 0
N802 3 0 3 0 0
S113 3 0 3 0 0
D418 3 0 3 0 0
PLW2901 3 0 3 0 0
D417 3 0 3 0 0
B007 3 0 3 0 0
RET506 3 0 3 0 0
PYI061 3 0 3 0 0
PLE0704 3 0 3 0 0
LOG009 3 0 3 0 0
TC005 3 0 3 0 0
B020 3 0 3 0 0
SIM211 3 0 3 0 0
B033 3 0 3 0 0
N999 3 0 3 0 0
PT018 3 0 3 0 0
PIE796 2 0 2 0 0
RUF021 2 0 2 0 0
PTH117 2 0 2 0 0
PYI024 2 0 2 0 0
DTZ901 2 0 2 0 0
PTH208 2 0 2 0 0
PLR1736 2 0 2 0 0
SIM910 2 0 2 0 0
D104 2 0 2 0 0
RUF002 2 0 2 0 0
SIM105 2 0 2 0 0
PD011 2 0 2 0 0
DOC102 2 0 2 0 0
D412 2 0 2 0 0
FIX003 2 0 2 0 0
S314 2 0 2 0 0
ICN001 2 0 2 0 0
N813 2 0 2 0 0
S405 2 0 2 0 0
EXE002 2 0 2 0 0
DTZ004 2 0 2 0 0
PLE1205 2 0 2 0 0
DOC502 2 0 2 0 0
F541 2 0 2 0 0
PLW0128 2 0 2 0 0
PIE794 2 0 2 0 0
YTT103 2 0 2 0 0
UP010 2 0 2 0 0
PLC0205 2 0 2 0 0
UP028 2 0 2 0 0
PLC0105 2 0 2 0 0
SIM223 2 0 2 0 0
PYI025 2 0 2 0 0
UP018 2 0 2 0 0
FURB105 2 0 2 0 0
LOG004 2 0 2 0 0
PTH108 2 0 2 0 0
PTH116 2 0 2 0 0
PTH113 2 0 2 0 0
FURB157 1 0 1 0 0
N817 1 0 1 0 0
N805 1 0 1 0 0
AIR002 1 0 1 0 0
N803 1 0 1 0 0
SIM401 1 0 1 0 0
FURB171 1 0 1 0 0
PTH106 1 0 1 0 0
FIX001 1 0 1 0 0
S301 1 0 1 0 0
SIM110 1 0 1 0 0
RUF005 1 0 1 0 0
S104 1 0 1 0 0
F401 1 0 1 0 0
NPY002 1 0 1 0 0
E225 1 0 1 0 0
Q001 1 0 1 0 0
N812 1 0 1 0 0
E303 1 0 1 0 0
RET508 1 0 1 0 0
E301 1 0 1 0 0
E227 1 0 1 0 0
PTH109 1 0 1 0 0
ISC003 1 0 1 0 0
E203 1 0 1 0 0
FURB148 1 0 1 0 0
PLW1509 1 0 1 0 0
PLW0133 1 0 1 0 0
PLW0211 1 0 1 0 0
PYI013 1 0 1 0 0
B903 1 0 1 0 0
UP014 1 0 1 0 0
RUF007 1 0 1 0 0
S112 1 0 1 0 0
B012 1 0 1 0 0
PLW0642 1 0 1 0 0
B013 1 0 1 0 0
YTT301 1 0 1 0 0
DTZ006 1 0 1 0 0
B039 1 0 1 0 0
UP011 1 0 1 0 0
FURB163 1 0 1 0 0
PLW0129 1 0 1 0 0
B030 1 0 1 0 0
RUF051 1 0 1 0 0
LOG001 1 0 1 0 0
DTZ002 1 0 1 0 0
RUF019 1 0 1 0 0
DTZ003 1 0 1 0 0
PYI044 1 0 1 0 0
FURB122 1 0 1 0 0
PYI006 1 0 1 0 0
S324 1 0 1 0 0
S606 1 0 1 0 0
PTH211 1 0 1 0 0
S311 1 0 1 0 0
PTH122 1 0 1 0 0
PTH202 1 0 1 0 0
D106 1 0 1 0 0
D403 1 0 1 0 0
RUF056 1 0 1 0 0
B901 1 0 1 0 0
PYI045 1 0 1 0 0
S402 1 0 1 0 0

@Bnyro Bnyro force-pushed the temporary-variable-swap branch from 9f0567d to 6edd032 Compare December 29, 2025 16:36
@Bnyro
Copy link
Copy Markdown
Contributor Author

Bnyro commented Dec 29, 2025

Thanks a lot for the review @ntBre!

I've applied all suggestions except for the 2 unresolved ones above where I'm not quite sure yet, I'd appreciate your thoughts on that :)

@Bnyro Bnyro force-pushed the temporary-variable-swap branch 2 times, most recently from 0519f49 to 7e79155 Compare December 29, 2025 17:47
Copy link
Copy Markdown
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

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

Thank you again, this is looking great!

I had a couple more nits and one larger concern about removing the temporary variable, but even that shouldn't be too much trouble, I hope.

Comment thread crates/ruff_linter/src/rules/pylint/rules/swap_with_temporary_variable.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/swap_with_temporary_variable.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/swap_with_temporary_variable.rs Outdated
@ntBre ntBre changed the title [pylint]: Implement consider-swap-variables (PLR1712) [pylint] Implement swap-with-temporary-variable (PLR1712) Jan 2, 2026
@Bnyro Bnyro force-pushed the temporary-variable-swap branch from 7e79155 to df1c532 Compare January 3, 2026 12:50
@MichaReiser MichaReiser requested a review from ntBre January 16, 2026 08:59
@Bnyro Bnyro force-pushed the temporary-variable-swap branch 2 times, most recently from 8971c6d to 51bbda1 Compare January 16, 2026 17:45
@Bnyro Bnyro force-pushed the temporary-variable-swap branch from 6a165cc to 33f02d2 Compare February 7, 2026 14:35
Comment thread crates/ruff_linter/src/rules/pylint/rules/swap_with_temporary_variable.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/swap_with_temporary_variable.rs Outdated
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Feb 12, 2026

Merging this PR will not alter performance

✅ 30 untouched benchmarks
⏩ 24 skipped benchmarks1


Comparing Bnyro:temporary-variable-swap (c08dea5) with main (97acaae)

Open in CodSpeed

Footnotes

  1. 24 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@Bnyro Bnyro force-pushed the temporary-variable-swap branch from 33f02d2 to 839b712 Compare February 18, 2026 19:50
Copy link
Copy Markdown
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

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

Thank you, this is looking great! I managed to come up with a few more nits and another contrived test case that we should double check the behavior of, but otherwise I think this is good to go.

Comment thread crates/ruff_linter/src/rules/pylint/rules/swap_with_temporary_variable.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/swap_with_temporary_variable.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/swap_with_temporary_variable.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/swap_with_temporary_variable.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/swap_with_temporary_variable.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/swap_with_temporary_variable.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/swap_with_temporary_variable.rs Outdated
Comment thread crates/ruff_linter/src/rules/pylint/rules/swap_with_temporary_variable.rs Outdated
@Bnyro Bnyro force-pushed the temporary-variable-swap branch from 839b712 to d022c96 Compare February 19, 2026 16:55
@Bnyro
Copy link
Copy Markdown
Contributor Author

Bnyro commented Feb 19, 2026

Thank you, this is looking great! I managed to come up with a few more nits and another contrived test case that we should double check the behavior of, but otherwise I think this is good to go.

Thanks for the review, I've addressed all the suggestions.

Your reviews helped me to learn more about the Python AST structure used in Ruff, so although the PR process took some time, it was definitely worth it. Thanks for all your help :)

@Bnyro Bnyro force-pushed the temporary-variable-swap branch from d022c96 to c08dea5 Compare February 19, 2026 16:59
Copy link
Copy Markdown
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

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

Thank you for all of your work here, this looks great to me!

I'll land this once the release is finished for today.

@ntBre ntBre merged commit 333ad91 into astral-sh:main Feb 19, 2026
42 checks passed
@Bnyro Bnyro deleted the temporary-variable-swap branch February 19, 2026 23:35
knutwannheden pushed a commit to openrewrite/ruff that referenced this pull request Feb 20, 2026
…al-sh#22205)

## Summary
This PR implements the `consider-swap-variables` rule from pylint.
Basically it tries to find code parts that swap two variables with each
other using a temporary variable.

Example code:
```py
temp = x
x = y
y = temp
```

can be simplified to

```py
x, y = y, x
```

related:
-
https://pylint.readthedocs.io/en/latest/user_guide/messages/refactor/consider-swap-variables.html
- astral-sh#970 

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan
I've added new snapshots tests.

PS: Since this is my first contribution here and I'm not too familiar
with the codebase, suggestions are very welcome! The implementation
might also not be 100% memory-optimized yet, since we use `clone` a few
times.
ntBre added a commit that referenced this pull request Feb 26, 2026
… (`PLR1712`) (#22205)"

This is an alternative to #23588, checking the ecosystem results in comparison.
ntBre added a commit that referenced this pull request Feb 26, 2026
Summary
--

The huge number of changes in
#22205 (comment) should have
obviously been a red flag, but I think it would be nice if CI failed when new
ecosystem panics were introduced. This PR adds a check for diagnostic lines that
start with `panic: Panicked at crates/`, raises a `ToolError` if any are found
in the results from the comparison executable, and then also exits non-zero if
any errors are returned.

If exiting non-zero is going too far, we could also just raise the `ToolError`,
as that will at least trigger this message, which was not the case on the
PLR1712 PR:

https://github.com/astral-sh/ruff/blob/f14edd8661e2803254f89265548c7487f47a09f6/python/ruff-ecosystem/ruff_ecosystem/check.py#L103-L106

Another option would be not to exit zero if Ruff panics, even if `--exit-zero`
is used, but I saw that ty has the same behavior and assumed that that was
intentional.

Test Plan
--

Local testing on the 0.15.3 tag showing that ruff-ecosystem exited non-zero. I
can also introduce a panic into a lint rule to test this in CI
ntBre added a commit that referenced this pull request Mar 4, 2026
Summary
--

The huge number of changes in
#22205 (comment)
should have
obviously been a red flag, but I think it would be nice if CI failed
when new
ecosystem panics were introduced. This PR adds a check for diagnostic
lines that
start with `panic: Panicked at crates/`, raises a `ToolError` if any are
found
in the results from the comparison executable, and then ~~also exits
non-zero if
any errors are returned~~ fails the CI run if the corresponding error
message
was printed.

After trying this out in CI, I opted not to change the script's exit
code itself
because that suppressed the ecosystem comment. It feels a little hackier
this way but preserves the behavior I wanted of both failing CI and
still getting
the ecosystem comment to help with debugging.

Test Plan
--

Local testing on the 0.15.3 tag showing that ruff-ecosystem exited
non-zero and
some manual testing in CI, as you can see below.
@BLKSerene BLKSerene mentioned this pull request Mar 21, 2026
nicopauss pushed a commit to Intersec/lib-common that referenced this pull request Apr 1, 2026
Released on 2026-03-19.

- Display output severity in preview ([#23845](astral-sh/ruff#23845))
- Don't show `noqa` hover for non-Python documents ([#24040](astral-sh/ruff#24040))

- \[`pycodestyle`] Recognize `pyrefly:` as a pragma comment (`E501`) ([#24019](astral-sh/ruff#24019))

- Don't return code actions for non-Python documents ([#23905](astral-sh/ruff#23905))

- Add company AI policy to contributing guide ([#24021](astral-sh/ruff#24021))
- Document editor features for Markdown code formatting ([#23924](astral-sh/ruff#23924))
- \[`pylint`] Improve phrasing (`PLC0208`) ([#24033](astral-sh/ruff#24033))

- Use PEP 639 license information ([#19661](astral-sh/ruff#19661))

- [@tmimmanuel](https://github.com/tmimmanuel)
- [@DimitriPapadopoulos](https://github.com/DimitriPapadopoulos)
- [@amyreese](https://github.com/amyreese)
- [@statxc](https://github.com/statxc)
- [@dylwil3](https://github.com/dylwil3)
- [@hunterhogan](https://github.com/hunterhogan)
- [@renovate](https://github.com/renovate)

Released on 2026-03-12.

- Add support for `lazy` import parsing ([#23755](astral-sh/ruff#23755))
- Add support for star-unpacking of comprehensions (PEP 798) ([#23788](astral-sh/ruff#23788))
- Reject semantic syntax errors for lazy imports ([#23757](astral-sh/ruff#23757))
- Drop a few rules from the preview default set ([#23879](astral-sh/ruff#23879))
- \[`airflow`] Flag `Variable.get()` calls outside of task execution context (`AIR003`) ([#23584](astral-sh/ruff#23584))
- \[`airflow`] Flag runtime-varying values in DAG/task constructor arguments (`AIR304`) ([#23631](astral-sh/ruff#23631))
- \[`flake8-bugbear`] Implement `delattr-with-constant` (`B043`) ([#23737](astral-sh/ruff#23737))
- \[`flake8-tidy-imports`] Add `TID254` to enforce lazy imports ([#23777](astral-sh/ruff#23777))
- \[`flake8-tidy-imports`] Allow users to ban lazy imports with `TID254` ([#23847](astral-sh/ruff#23847))
- \[`isort`] Retain `lazy` keyword when sorting imports ([#23762](astral-sh/ruff#23762))
- \[`pyupgrade`] Add `from __future__ import annotations` automatically (`UP006`) ([#23260](astral-sh/ruff#23260))
- \[`refurb`] Support `newline` parameter in `FURB101` for Python 3.13+ ([#23754](astral-sh/ruff#23754))
- \[`ruff`] Add `os-path-commonprefix` (`RUF071`) ([#23814](astral-sh/ruff#23814))
- \[`ruff`] Add unsafe fix for os-path-commonprefix (`RUF071`) ([#23852](astral-sh/ruff#23852))
- \[`ruff`] Limit `RUF036` to typing contexts; make it unsafe for non-typing-only ([#23765](astral-sh/ruff#23765))
- \[`ruff`] Use starred unpacking for `RUF017` in Python 3.15+ ([#23789](astral-sh/ruff#23789))

- Fix `--add-noqa` creating unwanted leading whitespace ([#23773](astral-sh/ruff#23773))
- Fix `--add-noqa` breaking shebangs ([#23577](astral-sh/ruff#23577))
- \[formatter] Fix lambda body formatting for multiline calls and subscripts ([#23866](astral-sh/ruff#23866))
- \[formatter] Preserve required annotation parentheses in annotated assignments ([#23865](astral-sh/ruff#23865))
- \[formatter] Preserve type-expression parentheses in the formatter ([#23867](astral-sh/ruff#23867))
- \[`flake8-annotations`] Fix stack overflow in `ANN401` on quoted annotations with escape sequences ([#23912](astral-sh/ruff#23912))
- \[`pep8-naming`] Check naming conventions in `match` pattern bindings (`N806`, `N815`, `N816`) ([#23899](astral-sh/ruff#23899))
- \[`perflint`] Fix comment duplication in fixes (`PERF401`, `PERF403`) ([#23729](astral-sh/ruff#23729))
- \[`pyupgrade`] Properly trigger `super` change in nested class (`UP008`) ([#22677](astral-sh/ruff#22677))
- \[`ruff`] Avoid syntax errors in `RUF036` fixes ([#23764](astral-sh/ruff#23764))

- \[`flake8-bandit`] Flag `S501` with `requests.request` ([#23873](astral-sh/ruff#23873))
- \[`flake8-executable`] Fix WSL detection in non-Docker containers ([#22879](astral-sh/ruff#22879))
- \[`flake8-print`] Ignore `pprint` calls with `stream=` ([#23787](astral-sh/ruff#23787))

- Update docs for Markdown code block formatting ([#23871](astral-sh/ruff#23871))
- \[`flake8-bugbear`] Fix misleading description for `B904` ([#23731](astral-sh/ruff#23731))

- [@zsol](https://github.com/zsol)
- [@carljm](https://github.com/carljm)
- [@ntBre](https://github.com/ntBre)
- [@Bortlesboat](https://github.com/Bortlesboat)
- [@sososonia-cyber](https://github.com/sososonia-cyber)
- [@chirizxc](https://github.com/chirizxc)
- [@leandrobbraga](https://github.com/leandrobbraga)
- [@11happy](https://github.com/11happy)
- [@Acelogic](https://github.com/Acelogic)
- [@anishgirianish](https://github.com/anishgirianish)
- [@amyreese](https://github.com/amyreese)
- [@xvchris](https://github.com/xvchris)
- [@charliermarsh](https://github.com/charliermarsh)
- [@getehen](https://github.com/getehen)
- [@Dev-iL](https://github.com/Dev-iL)

Released on 2026-03-05.

- Discover Markdown files by default in preview mode ([#23434](astral-sh/ruff#23434))
- \[`perflint`] Extend `PERF102` to comprehensions and generators ([#23473](astral-sh/ruff#23473))
- \[`refurb`] Fix `FURB101` and `FURB103` false positives when I/O variable is used later ([#23542](astral-sh/ruff#23542))
- \[`ruff`] Add fix for `none-not-at-end-of-union` (`RUF036`) ([#22829](astral-sh/ruff#22829))
- \[`ruff`] Fix false positive for `re.split` with empty string pattern (`RUF055`) ([#23634](astral-sh/ruff#23634))

- \[`fastapi`] Handle callable class dependencies with `__call__` method (`FAST003`) ([#23553](astral-sh/ruff#23553))
- \[`pydocstyle`] Fix numpy section ordering (`D420`) ([#23685](astral-sh/ruff#23685))
- \[`pyflakes`] Fix false positive for names shadowing re-exports (`F811`) ([#23356](astral-sh/ruff#23356))
- \[`pyupgrade`] Avoid inserting redundant `None` elements in `UP045` ([#23459](astral-sh/ruff#23459))

- Document extension mapping for Markdown code formatting ([#23574](astral-sh/ruff#23574))
- Update default Python version examples ([#23605](astral-sh/ruff#23605))

- Publish releases to Astral mirror ([#23616](astral-sh/ruff#23616))

- [@amyreese](https://github.com/amyreese)
- [@stakeswky](https://github.com/stakeswky)
- [@chirizxc](https://github.com/chirizxc)
- [@anishgirianish](https://github.com/anishgirianish)
- [@bxff](https://github.com/bxff)
- [@zsol](https://github.com/zsol)
- [@charliermarsh](https://github.com/charliermarsh)
- [@ntBre](https://github.com/ntBre)
- [@kar-ganap](https://github.com/kar-ganap)

Released on 2026-02-26.

This is a follow-up release to 0.15.3 that resolves a panic when the new rule `PLR1712` was enabled with any rule that analyzes definitions, such as many of the `ANN` or `D` rules.

- Fix panic on access to definitions after analyzing definitions ([#23588](astral-sh/ruff#23588))
- \[`pyflakes`] Suppress false positive in `F821` for names used before `del` in stub files ([#23550](astral-sh/ruff#23550))

- Clarify first-party import detection in Ruff ([#23591](astral-sh/ruff#23591))
- Fix incorrect `import-heading` example ([#23568](astral-sh/ruff#23568))

- [@stakeswky](https://github.com/stakeswky)
- [@ntBre](https://github.com/ntBre)
- [@thejcannon](https://github.com/thejcannon)
- [@GeObts](https://github.com/GeObts)

Released on 2026-02-26.

- Drop explicit support for `.qmd` file extension ([#23572](astral-sh/ruff#23572))

  This can now be enabled instead by setting the [`extension`](https://docs.astral.sh/ruff/settings/#extension) option:

  ```toml
  # ruff.toml
  extension = { qmd = "markdown" }

  # pyproject.toml
  [tool.ruff]
  extension = { qmd = "markdown" }
  ```

- Include configured extensions in file discovery ([#23400](astral-sh/ruff#23400))

- \[`flake8-bandit`] Allow suspicious imports in `TYPE_CHECKING` blocks (`S401`-`S415`) ([#23441](astral-sh/ruff#23441))

- \[`flake8-bugbear`] Allow `B901` in pytest hook wrappers ([#21931](astral-sh/ruff#21931))

- \[`flake8-import-conventions`] Add missing conventions from upstream (`ICN001`, `ICN002`) ([#21373](astral-sh/ruff#21373))

- \[`pydocstyle`] Add rule to enforce docstring section ordering (`D420`) ([#23537](astral-sh/ruff#23537))

- \[`pylint`] Implement `swap-with-temporary-variable` (`PLR1712`) ([#22205](astral-sh/ruff#22205))

- \[`ruff`] Add `unnecessary-assign-before-yield` (`RUF070`) ([#23300](astral-sh/ruff#23300))

- \[`ruff`] Support file-level noqa in `RUF102` ([#23535](astral-sh/ruff#23535))

- \[`ruff`] Suppress diagnostic for invalid f-strings before Python 3.12 (`RUF027`) ([#23480](astral-sh/ruff#23480))

- \[`flake8-bandit`] Don't flag `BaseLoader`/`CBaseLoader` as unsafe (`S506`) ([#23510](astral-sh/ruff#23510))

- Avoid infinite loop between `I002` and `PYI025` ([#23352](astral-sh/ruff#23352))
- \[`pyflakes`] Fix false positive for `@overload` from `lint.typing-modules` (`F811`) ([#23357](astral-sh/ruff#23357))
- \[`pyupgrade`] Fix false positive for `TypeVar` default before Python 3.12 (`UP046`) ([#23540](astral-sh/ruff#23540))
- \[`pyupgrade`] Fix handling of `\N` in raw strings (`UP032`) ([#22149](astral-sh/ruff#22149))

- Render sub-diagnostics in the GitHub output format ([#23455](astral-sh/ruff#23455))

- \[`flake8-bugbear`] Tag certain `B007` diagnostics as unnecessary ([#23453](astral-sh/ruff#23453))

- \[`ruff`] Ignore unknown rule codes in `RUF100` ([#23531](astral-sh/ruff#23531))

  These are now flagged by [`RUF102`](https://docs.astral.sh/ruff/rules/invalid-rule-code/) instead.

- Fix missing settings links for several linters ([#23519](astral-sh/ruff#23519))
- Update isort action comments heading ([#23515](astral-sh/ruff#23515))
- \[`pydocstyle`] Fix double comma in description of `D404` ([#23440](astral-sh/ruff#23440))

- Update the Python module (notably `find_ruff_bin`) for parity with uv ([#23406](astral-sh/ruff#23406))

- [@zanieb](https://github.com/zanieb)
- [@o1x3](https://github.com/o1x3)
- [@assadyousuf](https://github.com/assadyousuf)
- [@kar-ganap](https://github.com/kar-ganap)
- [@denyszhak](https://github.com/denyszhak)
- [@amyreese](https://github.com/amyreese)
- [@carljm](https://github.com/carljm)
- [@anishgirianish](https://github.com/anishgirianish)
- [@Bnyro](https://github.com/Bnyro)
- [@danparizher](https://github.com/danparizher)
- [@ntBre](https://github.com/ntBre)
- [@gcomneno](https://github.com/gcomneno)
- [@jaap3](https://github.com/jaap3)
- [@stakeswky](https://github.com/stakeswky)

Released on 2026-02-19.

- Expand the default rule set ([#23385](astral-sh/ruff#23385))

  In preview, Ruff now enables a significantly expanded default rule set of 412
  rules, up from the stable default set of 59 rules. The new rules are mostly a
  superset of the stable defaults, with the exception of these rules, which are
  removed from the preview defaults:

  - [`multiple-imports-on-one-line`](https://docs.astral.sh/ruff/rules/multiple-imports-on-one-line) (`E401`)
  - [`module-import-not-at-top-of-file`](https://docs.astral.sh/ruff/rules/module-import-not-at-top-of-file) (`E402`)
  - [`module-import-not-at-top-of-file`](https://docs.astral.sh/ruff/rules/module-import-not-at-top-of-file) (`E701`)
  - [`multiple-statements-on-one-line-semicolon`](https://docs.astral.sh/ruff/rules/multiple-statements-on-one-line-semicolon) (`E702`)
  - [`useless-semicolon`](https://docs.astral.sh/ruff/rules/useless-semicolon) (`E703`)
  - [`none-comparison`](https://docs.astral.sh/ruff/rules/none-comparison) (`E711`)
  - [`true-false-comparison`](https://docs.astral.sh/ruff/rules/true-false-comparison) (`E712`)
  - [`not-in-test`](https://docs.astral.sh/ruff/rules/not-in-test) (`E713`)
  - [`not-is-test`](https://docs.astral.sh/ruff/rules/not-is-test) (`E714`)
  - [`type-comparison`](https://docs.astral.sh/ruff/rules/type-comparison) (`E721`)
  - [`lambda-assignment`](https://docs.astral.sh/ruff/rules/lambda-assignment) (`E731`)
  - [`ambiguous-variable-name`](https://docs.astral.sh/ruff/rules/ambiguous-variable-name) (`E741`)
  - [`ambiguous-class-name`](https://docs.astral.sh/ruff/rules/ambiguous-class-name) (`E742`)
  - [`ambiguous-function-name`](https://docs.astral.sh/ruff/rules/ambiguous-function-name) (`E743`)
  - [`undefined-local-with-import-star`](https://docs.astral.sh/ruff/rules/undefined-local-with-import-star) (`F403`)
  - [`undefined-local-with-import-star-usage`](https://docs.astral.sh/ruff/rules/undefined-local-with-import-star-usage) (`F405`)
  - [`undefined-local-with-nested-import-star-usage`](https://docs.astral.sh/ruff/rules/undefined-local-with-nested-import-star-usage) (`F406`)
  - [`forward-annotation-syntax-error`](https://docs.astral.sh/ruff/rules/forward-annotation-syntax-error) (`F722`)

  If you use preview and prefer the old defaults, you can restore them with
  configuration like:

  ```toml

  # ruff.toml

  [lint]
  select = ["E4", "E7", "E9", "F"]

  # pyproject.toml

  [tool.ruff.lint]
  select = ["E4", "E7", "E9", "F"]
  ```

  If you do give them a try, feel free to share your feedback in the [GitHub
  discussion](astral-sh/ruff#23203)!

- \[`flake8-pyi`] Also check string annotations (`PYI041`) ([#19023](astral-sh/ruff#19023))

- \[`flake8-async`] Fix `in_async_context` logic ([#23426](astral-sh/ruff#23426))
- \[`ruff`] Fix for `RUF102` should delete entire comment ([#23380](astral-sh/ruff#23380))
- \[`ruff`] Suppress diagnostic for strings with backslashes in interpolations before Python 3.12 (`RUF027`) ([#21069](astral-sh/ruff#21069))
- \[`flake8-bugbear`] Fix `B023` false positive for immediately-invoked lambdas ([#23294](astral-sh/ruff#23294))
- \[parser] Fix false syntax error for match-like annotated assignments ([#23297](astral-sh/ruff#23297))
- \[parser] Fix indentation tracking after line continuations ([#23417](astral-sh/ruff#23417))

- \[`flake8-executable`] Allow global flags in uv shebangs (`EXE003`) ([#22582](astral-sh/ruff#22582))
- \[`pyupgrade`] Fix handling of `typing.{io,re}` (`UP035`) ([#23131](astral-sh/ruff#23131))
- \[`ruff`] Detect `PLC0207` on chained `str.split()` calls ([#23275](astral-sh/ruff#23275))

- Remove invalid inline `noqa` warning ([#23270](astral-sh/ruff#23270))

- Add extension mapping to configuration file options ([#23384](astral-sh/ruff#23384))

- Add `Q004` to the list of conflicting rules ([#23340](astral-sh/ruff#23340))
- \[`ruff`] Expand `lint.external` docs and add sub-diagnostic (`RUF100`, `RUF102`) ([#23268](astral-sh/ruff#23268))

- [@dylwil3](https://github.com/dylwil3)
- [@Jkhall81](https://github.com/Jkhall81)
- [@danparizher](https://github.com/danparizher)
- [@dhruvmanila](https://github.com/dhruvmanila)
- [@harupy](https://github.com/harupy)
- [@ngnpope](https://github.com/ngnpope)
- [@amyreese](https://github.com/amyreese)
- [@kar-ganap](https://github.com/kar-ganap)
- [@robsdedude](https://github.com/robsdedude)
- [@shaanmajid](https://github.com/shaanmajid)
- [@ntBre](https://github.com/ntBre)
- [@toslunar](https://github.com/toslunar)

Released on 2026-02-12.

- \[`airflow`] Add ruff rules to catch deprecated Airflow imports for Airflow 3.1 (`AIR321`) ([#22376](astral-sh/ruff#22376))
- \[`airflow`] Third positional parameter not named `ti_key` should be flagged for `BaseOperatorLink.get_link` (`AIR303`) ([#22828](astral-sh/ruff#22828))
- \[`flake8-gettext`] Fix false negatives for plural argument of `ngettext` (`INT001`, `INT002`, `INT003`) ([#21078](astral-sh/ruff#21078))
- \[`pyflakes`] Fix infinite loop in preview fix for `unused-import` (`F401`) ([#23038](astral-sh/ruff#23038))
- \[`pygrep-hooks`] Detect non-existent mock methods in standalone expressions (`PGH005`) ([#22830](astral-sh/ruff#22830))
- \[`pylint`] Allow dunder submodules and improve diagnostic range (`PLC2701`) ([#22804](astral-sh/ruff#22804))
- \[`pyupgrade`] Improve diagnostic range for tuples (`UP024`) ([#23013](astral-sh/ruff#23013))
- \[`refurb`] Check subscripts in tuple do not use lambda parameters in `reimplemented-operator` (`FURB118`) ([#23079](astral-sh/ruff#23079))
- \[`ruff`] Detect mutable defaults in `field` calls (`RUF008`) ([#23046](astral-sh/ruff#23046))
- \[`ruff`] Ignore std `cmath.inf` (`RUF069`) ([#23120](astral-sh/ruff#23120))
- \[`ruff`] New rule `float-equality-comparison` (`RUF069`) ([#20585](astral-sh/ruff#20585))
- Don't format unlabeled Markdown code blocks ([#23106](astral-sh/ruff#23106))
- Markdown formatting support in LSP ([#23063](astral-sh/ruff#23063))
- Support Quarto Markdown language markers ([#22947](astral-sh/ruff#22947))
- Support formatting `pycon` Markdown code blocks ([#23112](astral-sh/ruff#23112))
- Use extension mapping to select Markdown code block language ([#22934](astral-sh/ruff#22934))

- Avoid false positive for undefined variables in `FAST001` ([#23224](astral-sh/ruff#23224))
- Avoid introducing syntax errors for `FAST003` autofix ([#23227](astral-sh/ruff#23227))
- Avoid suggesting `InitVar` for `__post_init__` that references PEP 695 type parameters ([#23226](astral-sh/ruff#23226))
- Deduplicate type variables in generic functions ([#23225](astral-sh/ruff#23225))
- Fix exception handler parenthesis removal for Python 3.14+ ([#23126](astral-sh/ruff#23126))
- Fix f-string middle panic when parsing t-strings ([#23232](astral-sh/ruff#23232))
- Wrap `RUF020` target for multiline fixes ([#23210](astral-sh/ruff#23210))
- Wrap `UP007` target for multiline fixes ([#23208](astral-sh/ruff#23208))
- Fix missing diagnostics for last range suppression in file ([#23242](astral-sh/ruff#23242))
- \[`pyupgrade`] Fix syntax error on string with newline escape and comment (`UP037`) ([#22968](astral-sh/ruff#22968))

- Use `ruff` instead of `Ruff` as the program name in GitHub output format ([#23240](astral-sh/ruff#23240))
- \[`PT006`] Fix syntax error when unpacking nested tuples in `parametrize` fixes ([#22441](astral-sh/ruff#22441)) ([#22464](astral-sh/ruff#22464))
- \[`airflow`] Catch deprecated attribute access from context key for Airflow 3.0 (`AIR301`) ([#22850](astral-sh/ruff#22850))
- \[`airflow`] Capture deprecated arguments and a decorator (`AIR301`) ([#23170](astral-sh/ruff#23170))
- \[`flake8-boolean-trap`] Add `multiprocessing.Value` to excluded functions for `FBT003` ([#23010](astral-sh/ruff#23010))
- \[`flake8-bugbear`] Add a secondary annotation showing the previous occurrence (`B033`) ([#22634](astral-sh/ruff#22634))
- \[`flake8-type-checking`] Add sub-diagnostic showing the runtime use of an annotation (`TC004`) ([#23091](astral-sh/ruff#23091))
- \[`isort`] Support configurable import section heading comments ([#23151](astral-sh/ruff#23151))
- \[`ruff`] Improve the diagnostic for `RUF012` ([#23202](astral-sh/ruff#23202))

- Suppress diagnostic output for `format --check --silent` ([#17736](astral-sh/ruff#17736))

- Add tabbed shell completion documentation ([#23169](astral-sh/ruff#23169))
- Explain how to enable Markdown formatting for pre-commit hook ([#23077](astral-sh/ruff#23077))
- Fixed import in `runtime-evaluated-decorators` example ([#23187](astral-sh/ruff#23187))
- Update ruff server contributing guide ([#23060](astral-sh/ruff#23060))

- Exclude WASM artifacts from GitHub releases ([#23221](astral-sh/ruff#23221))

- [@mkniewallner](https://github.com/mkniewallner)
- [@bxff](https://github.com/bxff)
- [@dylwil3](https://github.com/dylwil3)
- [@Avasam](https://github.com/Avasam)
- [@amyreese](https://github.com/amyreese)
- [@charliermarsh](https://github.com/charliermarsh)
- [@Alex-ley-scrub](https://github.com/Alex-ley-scrub)
- [@Kalmaegi](https://github.com/Kalmaegi)
- [@danparizher](https://github.com/danparizher)
- [@AiyionPrime](https://github.com/AiyionPrime)
- [@eureka928](https://github.com/eureka928)
- [@11happy](https://github.com/11happy)
- [@Jkhall81](https://github.com/Jkhall81)
- [@chirizxc](https://github.com/chirizxc)
- [@leandrobbraga](https://github.com/leandrobbraga)
- [@tvatter](https://github.com/tvatter)
- [@anishgirianish](https://github.com/anishgirianish)
- [@shaanmajid](https://github.com/shaanmajid)
- [@ntBre](https://github.com/ntBre)
- [@sjyangkevin](https://github.com/sjyangkevin)

Released on 2026-02-03.

Check out the [blog post](https://astral.sh/blog/ruff-v0.15.0) for a migration
guide and overview of the changes!

- Ruff now formats your code according to the 2026 style guide. See the formatter section below or in the blog post for a detailed list of changes.

- The linter now supports block suppression comments. For example, to suppress `N803` for all parameters in this function:

  ```python
  # ruff: disable[N803]
  def foo(
      legacyArg1,
      legacyArg2,
      legacyArg3,
      legacyArg4,
  ): ...
  # ruff: enable[N803]
  ```

  See the [documentation](https://docs.astral.sh/ruff/linter/#block-level) for more details.

- The `ruff:alpine` Docker image is now based on Alpine 3.23 (up from 3.21).

- The `ruff:debian` and `ruff:debian-slim` Docker images are now based on Debian 13 "Trixie" instead of Debian 12 "Bookworm."

- Binaries for the `ppc64` (64-bit big-endian PowerPC) architecture are no longer included in our releases. It should still be possible to build Ruff manually for this platform, if needed.

- Ruff now resolves all `extend`ed configuration files before falling back on a default Python version.

The following rules have been stabilized and are no longer in preview:

- [`blocking-http-call-httpx-in-async-function`](https://docs.astral.sh/ruff/rules/blocking-http-call-httpx-in-async-function)
  (`ASYNC212`)
- [`blocking-path-method-in-async-function`](https://docs.astral.sh/ruff/rules/blocking-path-method-in-async-function)
  (`ASYNC240`)
- [`blocking-input-in-async-function`](https://docs.astral.sh/ruff/rules/blocking-input-in-async-function)
  (`ASYNC250`)
- [`map-without-explicit-strict`](https://docs.astral.sh/ruff/rules/map-without-explicit-strict)
  (`B912`)
- [`if-exp-instead-of-or-operator`](https://docs.astral.sh/ruff/rules/if-exp-instead-of-or-operator)
  (`FURB110`)
- [`single-item-membership-test`](https://docs.astral.sh/ruff/rules/single-item-membership-test)
  (`FURB171`)
- [`missing-maxsplit-arg`](https://docs.astral.sh/ruff/rules/missing-maxsplit-arg) (`PLC0207`)
- [`unnecessary-lambda`](https://docs.astral.sh/ruff/rules/unnecessary-lambda) (`PLW0108`)
- [`unnecessary-empty-iterable-within-deque-call`](https://docs.astral.sh/ruff/rules/unnecessary-empty-iterable-within-deque-call)
  (`RUF037`)
- [`in-empty-collection`](https://docs.astral.sh/ruff/rules/in-empty-collection) (`RUF060`)
- [`legacy-form-pytest-raises`](https://docs.astral.sh/ruff/rules/legacy-form-pytest-raises)
  (`RUF061`)
- [`non-octal-permissions`](https://docs.astral.sh/ruff/rules/non-octal-permissions) (`RUF064`)
- [`invalid-rule-code`](https://docs.astral.sh/ruff/rules/invalid-rule-code) (`RUF102`)
- [`invalid-suppression-comment`](https://docs.astral.sh/ruff/rules/invalid-suppression-comment)
  (`RUF103`)
- [`unmatched-suppression-comment`](https://docs.astral.sh/ruff/rules/unmatched-suppression-comment)
  (`RUF104`)
- [`replace-str-enum`](https://docs.astral.sh/ruff/rules/replace-str-enum) (`UP042`)

The following behaviors have been stabilized:

- The `--output-format` flag is now respected when running Ruff in `--watch` mode, and the `full` output format is now used by default, matching the regular CLI output.
- [`builtin-attribute-shadowing`](https://docs.astral.sh/ruff/rules/builtin-attribute-shadowing/) (`A003`) now detects the use of shadowed built-in names in additional contexts like decorators, default arguments, and other attribute definitions.
- [`duplicate-union-member`](https://docs.astral.sh/ruff/rules/duplicate-union-member/) (`PYI016`) now considers `typing.Optional` when searching for duplicate union members.
- [`split-static-string`](https://docs.astral.sh/ruff/rules/split-static-string/) (`SIM905`) now offers an autofix when the `maxsplit` argument is provided, even without a `sep` argument.
- [`dict-get-with-none-default`](https://docs.astral.sh/ruff/rules/dict-get-with-none-default/) (`SIM910`) now applies to more types of key expressions.
- [`super-call-with-parameters`](https://docs.astral.sh/ruff/rules/super-call-with-parameters/) (`UP008`) now has a safe fix when it will not delete comments.
- [`unnecessary-default-type-args`](https://docs.astral.sh/ruff/rules/unnecessary-default-type-args/) (`UP043`) now applies to stub (`.pyi`) files on Python versions before 3.13.

This release introduces the new 2026 style guide, with the following changes:

- Lambda parameters are now kept on the same line and lambda bodies will be parenthesized to let
  them break across multiple lines ([#21385](astral-sh/ruff#21385))
- Parentheses around tuples of exceptions in `except` clauses will now be removed on Python 3.14 and
  later ([#20768](astral-sh/ruff#20768))
- A single empty line is now permitted at the beginning of function bodies ([#21110](astral-sh/ruff#21110))
- Parentheses are avoided for long `as` captures in `match` statements ([#21176](astral-sh/ruff#21176))
- Extra spaces between escaped quotes and ending triple quotes can now be omitted ([#17216](astral-sh/ruff#17216))
- Blank lines are now enforced before classes with decorators in stub files ([#18888](astral-sh/ruff#18888))

- Apply formatting to Markdown code blocks ([#22470](astral-sh/ruff#22470), [#22990](astral-sh/ruff#22990), [#22996](astral-sh/ruff#22996))

  See the [documentation](https://docs.astral.sh/ruff/formatter/#markdown-code-formatting) for more details.

- Fix suppression indentation matching ([#22903](astral-sh/ruff#22903))

- Customize where the `fix_title` sub-diagnostic appears ([#23044](astral-sh/ruff#23044))
- \[`FastAPI`] Add sub-diagnostic explaining why a fix was unavailable (`FAST002`) ([#22565](astral-sh/ruff#22565))
- \[`flake8-annotations`] Don't suggest `NoReturn` for functions raising `NotImplementedError` (`ANN201`, `ANN202`, `ANN205`, `ANN206`) ([#21311](astral-sh/ruff#21311))
- \[`pyupgrade`] Make fix unsafe if it deletes comments (`UP017`) ([#22873](astral-sh/ruff#22873))
- \[`pyupgrade`] Make fix unsafe if it deletes comments (`UP020`) ([#22872](astral-sh/ruff#22872))
- \[`pyupgrade`] Make fix unsafe if it deletes comments (`UP033`) ([#22871](astral-sh/ruff#22871))
- \[`refurb`] Do not add `abc.ABC` if already present (`FURB180`) ([#22234](astral-sh/ruff#22234))
- \[`refurb`] Make fix unsafe if it deletes comments (`FURB110`) ([#22768](astral-sh/ruff#22768))
- \[`ruff`] Add sub-diagnostics with permissions (`RUF064`) ([#22972](astral-sh/ruff#22972))

- Identify notebooks by LSP `didOpen` instead of `.ipynb` file extension ([#22810](astral-sh/ruff#22810))

- Add `--color` CLI option to force colored output ([#22806](astral-sh/ruff#22806))

- Document `-` stdin convention in CLI help text ([#22817](astral-sh/ruff#22817))
- \[`refurb`] Change example to `re.search` with `^` anchor (`FURB167`) ([#22984](astral-sh/ruff#22984))
- Fix link to Sphinx code block directives ([#23041](astral-sh/ruff#23041))
- \[`pydocstyle`] Clarify which quote styles are allowed (`D300`) ([#22825](astral-sh/ruff#22825))
- \[`flake8-bugbear`] Improve docs for `no-explicit-stacklevel` (`B028`) ([#22538](astral-sh/ruff#22538))

- Update MSRV to 1.91 ([#22874](astral-sh/ruff#22874))

- [@danparizher](https://github.com/danparizher)
- [@chirizxc](https://github.com/chirizxc)
- [@amyreese](https://github.com/amyreese)
- [@Jkhall81](https://github.com/Jkhall81)
- [@cwkang1998](https://github.com/cwkang1998)
- [@manzt](https://github.com/manzt)
- [@11happy](https://github.com/11happy)
- [@hugovk](https://github.com/hugovk)
- [@caiquejjx](https://github.com/caiquejjx)
- [@ntBre](https://github.com/ntBre)
- [@akawd](https://github.com/akawd)
- [@konstin](https://github.com/konstin)

Renovate-Branch: renovate/2024.6-ruff-0.x
Change-Id: I8f8e865435fde1fc736fe2528261a604acb46215
Priv-Id: f7e1d99008e3617149c4b639a9a2bbc06212d064
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 Implementing or modifying a lint rule

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants