Compare commits
98 Commits
david/fix-
...
0.13.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1fdd66f10 | ||
|
|
8770b95509 | ||
|
|
65982a1e14 | ||
|
|
57d1f7132d | ||
|
|
7a75702237 | ||
|
|
9ca632c84f | ||
|
|
64fe7d30a3 | ||
|
|
beeeb8d5c5 | ||
|
|
b6fca52855 | ||
|
|
ac7f882c78 | ||
|
|
aef0a107a8 | ||
|
|
512395f4e6 | ||
|
|
5dec37fbaf | ||
|
|
4bda9dad68 | ||
|
|
9d972d0583 | ||
|
|
1bbb553d6f | ||
|
|
bb4c51afb2 | ||
|
|
3dbdd2b883 | ||
|
|
d8e43bf9f7 | ||
|
|
ee448eab2d | ||
|
|
307b7df027 | ||
|
|
e139104aba | ||
|
|
9bb9b54168 | ||
|
|
262f2767ca | ||
|
|
1de9dac9d5 | ||
|
|
1cf6c2439f | ||
|
|
2b51ec6531 | ||
|
|
b85c995927 | ||
|
|
fd7eb1e22f | ||
|
|
4de7d653bd | ||
|
|
9cb37db510 | ||
|
|
ed06fb5ce2 | ||
|
|
54df73c9f7 | ||
|
|
d7524ea6d4 | ||
|
|
bf66178959 | ||
|
|
9cdac2d6fb | ||
|
|
79706a2e26 | ||
|
|
25853e2377 | ||
|
|
61f906d8e7 | ||
|
|
08a561fc05 | ||
|
|
aa5d665d52 | ||
|
|
d55edb3d74 | ||
|
|
ab86ae1760 | ||
|
|
916968d0ff | ||
|
|
deb3d3d150 | ||
|
|
982a0a2a7c | ||
|
|
f893b19930 | ||
|
|
c96ebe3936 | ||
|
|
22ca5dd890 | ||
|
|
f7995f4aef | ||
|
|
480fb278d0 | ||
|
|
b2f364d9cb | ||
|
|
aa82137d9f | ||
|
|
adfe2438e9 | ||
|
|
3247991429 | ||
|
|
08fcf7e106 | ||
|
|
2467c4352e | ||
|
|
a27c64811e | ||
|
|
5d52902e18 | ||
|
|
eb6154f792 | ||
|
|
fdfb51b595 | ||
|
|
7ee863b6d7 | ||
|
|
8ade6c4eaf | ||
|
|
9e45bfa9fd | ||
|
|
7509d376eb | ||
|
|
a24a4b55ee | ||
|
|
888a22e849 | ||
|
|
08c1d3660c | ||
|
|
670fffef37 | ||
|
|
de63f408b9 | ||
|
|
555b9f78d6 | ||
|
|
c6516e9b60 | ||
|
|
1aaa0847ab | ||
|
|
b49aa35074 | ||
|
|
1e34f3f20a | ||
|
|
77b2cee223 | ||
|
|
200349c6e8 | ||
|
|
0d4f7dde99 | ||
|
|
cb1ba0d4c2 | ||
|
|
cda376afe0 | ||
|
|
b14fc96141 | ||
|
|
c452a2cb79 | ||
|
|
4c3e1930f6 | ||
|
|
5d7c17c20a | ||
|
|
c402bf8ae2 | ||
|
|
6bc33a041f | ||
|
|
0a0eaf5a9b | ||
|
|
8e52027a88 | ||
|
|
78db56e362 | ||
|
|
046893c186 | ||
|
|
9cea752934 | ||
|
|
3b913ce652 | ||
|
|
aee9350df1 | ||
|
|
4e97b97a76 | ||
|
|
00214fc60c | ||
|
|
ec5584219e | ||
|
|
d5e48a0f80 | ||
|
|
f40a0b3800 |
30
.github/workflows/ci.yaml
vendored
30
.github/workflows/ci.yaml
vendored
@@ -259,6 +259,10 @@ jobs:
|
||||
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: ty mdtests (GitHub annotations)
|
||||
if: ${{ needs.determine_changes.outputs.ty == 'true' }}
|
||||
env:
|
||||
@@ -317,6 +321,10 @@ jobs:
|
||||
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
|
||||
with:
|
||||
tool: cargo-insta
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: "Run tests"
|
||||
shell: bash
|
||||
env:
|
||||
@@ -340,6 +348,10 @@ jobs:
|
||||
uses: taiki-e/install-action@6064345e6658255e90e9500fdf9a06ab77e6909c # v2.57.6
|
||||
with:
|
||||
tool: cargo-nextest
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
with:
|
||||
enable-cache: "true"
|
||||
- name: "Run tests"
|
||||
shell: bash
|
||||
env:
|
||||
@@ -441,9 +453,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo-binstall"
|
||||
uses: cargo-bins/cargo-binstall@2bb61346d075e720d4c3da92f23b6d612d5a7543 # v1.15.3
|
||||
with:
|
||||
tool: cargo-fuzz@0.11.2
|
||||
uses: cargo-bins/cargo-binstall@837578dfb436769f1e6669b2e23ffea9d9d2da8f # v1.15.4
|
||||
- name: "Install cargo-fuzz"
|
||||
# Download the latest version from quick install and not the github releases because github releases only has MUSL targets.
|
||||
run: cargo binstall cargo-fuzz --force --disable-strategies crate-meta-data --no-confirm
|
||||
@@ -463,7 +473,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
name: Download Ruff binary to test
|
||||
id: download-cached-binary
|
||||
@@ -664,7 +674,7 @@ jobs:
|
||||
branch: ${{ github.event.pull_request.base.ref }}
|
||||
workflow: "ci.yaml"
|
||||
check_artifacts: true
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- name: Fuzz
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
@@ -694,7 +704,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: cargo-bins/cargo-binstall@2bb61346d075e720d4c3da92f23b6d612d5a7543 # v1.15.3
|
||||
- uses: cargo-bins/cargo-binstall@837578dfb436769f1e6669b2e23ffea9d9d2da8f # v1.15.4
|
||||
- run: cargo binstall --no-confirm cargo-shear
|
||||
- run: cargo shear
|
||||
|
||||
@@ -734,7 +744,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
@@ -777,7 +787,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: uv pip install -r docs/requirements-insiders.txt --system
|
||||
@@ -909,7 +919,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
@@ -942,7 +952,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
|
||||
2
.github/workflows/daily_fuzz.yaml
vendored
2
.github/workflows/daily_fuzz.yaml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install mold"
|
||||
|
||||
4
.github/workflows/mypy_primer.yaml
vendored
4
.github/workflows/mypy_primer.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
|
||||
2
.github/workflows/publish-pypi.yml
vendored
2
.github/workflows/publish-pypi.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
pattern: wheels-*
|
||||
|
||||
6
.github/workflows/sync_typeshed.yaml
vendored
6
.github/workflows/sync_typeshed.yaml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
git config --global user.email '<>'
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- name: Sync typeshed stubs
|
||||
run: |
|
||||
rm -rf "ruff/${VENDORED_TYPESHED}"
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: true
|
||||
ref: ${{ env.UPSTREAM_BRANCH}}
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
|
||||
5
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
5
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
@@ -64,11 +64,12 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@27dd66d9e397d986ef9c631119ee09556eab8af9"
|
||||
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@1f560d07d672effae250e3d271da53d96c5260ff"
|
||||
|
||||
ecosystem-analyzer \
|
||||
--repository ruff \
|
||||
diff \
|
||||
--profile=release \
|
||||
--projects-old ruff/projects_old.txt \
|
||||
--projects-new ruff/projects_new.txt \
|
||||
--old old_commit \
|
||||
|
||||
2
.github/workflows/ty-ecosystem-report.yaml
vendored
2
.github/workflows/ty-ecosystem-report.yaml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
uses: astral-sh/setup-uv@557e51de59eb14aaaba2ed9621916900a91d50c6 # v6.6.1
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
|
||||
@@ -1,5 +1,44 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.13.0
|
||||
|
||||
- **Several rules can now add `from __future__ import annotations` automatically**
|
||||
|
||||
`TC001`, `TC002`, `TC003`, `RUF013`, and `UP037` now add `from __future__ import annotations` as part of their fixes when the
|
||||
`lint.future-annotations` setting is enabled. This allows the rules to move
|
||||
more imports into `TYPE_CHECKING` blocks (`TC001`, `TC002`, and `TC003`),
|
||||
use PEP 604 union syntax on Python versions before 3.10 (`RUF013`), and
|
||||
unquote more annotations (`UP037`).
|
||||
|
||||
- **Full module paths are now used to verify first-party modules**
|
||||
|
||||
Ruff now checks that the full path to a module exists on disk before
|
||||
categorizing it as a first-party import. This change makes first-party
|
||||
import detection more accurate, helping to avoid false positives on local
|
||||
directories with the same name as a third-party dependency, for example. See
|
||||
the [FAQ
|
||||
section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) on import categorization for more details.
|
||||
|
||||
- **Deprecated rules must now be selected by exact rule code**
|
||||
|
||||
Ruff will no longer activate deprecated rules selected by their group name
|
||||
or prefix. As noted below, the two remaining deprecated rules were also
|
||||
removed in this release, so this won't affect any current rules, but it will
|
||||
still affect any deprecations in the future.
|
||||
|
||||
- **The deprecated macOS configuration directory fallback has been removed**
|
||||
|
||||
Ruff will no longer look for a user-level configuration file at
|
||||
`~/Library/Application Support/ruff/ruff.toml` on macOS. This feature was
|
||||
deprecated in v0.5 in favor of using the [XDG
|
||||
specification](https://specifications.freedesktop.org/basedir-spec/latest/)
|
||||
(usually resolving to `~/.config/ruff/ruff.toml`), like on Linux. The
|
||||
fallback and accompanying deprecation warning have now been removed.
|
||||
|
||||
- **[`pandas-df-variable-name`](https://docs.astral.sh/ruff/rules/pandas-df-variable-name) (`PD901`) has been removed**
|
||||
|
||||
- **[`non-pep604-isinstance`](https://docs.astral.sh/ruff/rules/non-pep604-isinstance) (`UP038`) has been removed**
|
||||
|
||||
## 0.12.0
|
||||
|
||||
- **Detection of more syntax errors**
|
||||
|
||||
561
CHANGELOG.md
561
CHANGELOG.md
@@ -1,517 +1,119 @@
|
||||
# Changelog
|
||||
|
||||
## 0.12.11
|
||||
## 0.13.0
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`airflow`\] Extend `AIR311` and `AIR312` rules ([#20082](https://github.com/astral-sh/ruff/pull/20082))
|
||||
- \[`airflow`\] Replace wrong path `airflow.io.storage` with `airflow.io.store` (`AIR311`) ([#20081](https://github.com/astral-sh/ruff/pull/20081))
|
||||
- \[`flake8-async`\] Implement `blocking-http-call-httpx-in-async-function` (`ASYNC212`) ([#20091](https://github.com/astral-sh/ruff/pull/20091))
|
||||
- \[`flake8-logging-format`\] Add auto-fix for f-string logging calls (`G004`) ([#19303](https://github.com/astral-sh/ruff/pull/19303))
|
||||
- \[`flake8-use-pathlib`\] Add autofix for `PTH211` ([#20009](https://github.com/astral-sh/ruff/pull/20009))
|
||||
- \[`flake8-use-pathlib`\] Make `PTH100` fix unsafe because it can change behavior ([#20100](https://github.com/astral-sh/ruff/pull/20100))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`pyflakes`, `pylint`\] Fix false positives caused by `__class__` cell handling (`F841`, `PLE0117`) ([#20048](https://github.com/astral-sh/ruff/pull/20048))
|
||||
- \[`pyflakes`\] Fix `allowed-unused-imports` matching for top-level modules (`F401`) ([#20115](https://github.com/astral-sh/ruff/pull/20115))
|
||||
- \[`ruff`\] Fix false positive for t-strings in `default-factory-kwarg` (`RUF026`) ([#20032](https://github.com/astral-sh/ruff/pull/20032))
|
||||
- \[`ruff`\] Preserve relative whitespace in multi-line expressions (`RUF033`) ([#19647](https://github.com/astral-sh/ruff/pull/19647))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`ruff`\] Handle empty t-strings in `unnecessary-empty-iterable-within-deque-call` (`RUF037`) ([#20045](https://github.com/astral-sh/ruff/pull/20045))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Fix incorrect `D413` links in docstrings convention FAQ ([#20089](https://github.com/astral-sh/ruff/pull/20089))
|
||||
- \[`flake8-use-pathlib`\] Update links to the table showing the correspondence between `os` and `pathlib` ([#20103](https://github.com/astral-sh/ruff/pull/20103))
|
||||
|
||||
## 0.12.10
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-simplify`\] Implement fix for `maxsplit` without separator (`SIM905`) ([#19851](https://github.com/astral-sh/ruff/pull/19851))
|
||||
- \[`flake8-use-pathlib`\] Add fixes for `PTH102` and `PTH103` ([#19514](https://github.com/astral-sh/ruff/pull/19514))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`isort`\] Handle multiple continuation lines after module docstring (`I002`) ([#19818](https://github.com/astral-sh/ruff/pull/19818))
|
||||
- \[`pyupgrade`\] Avoid reporting `__future__` features as unnecessary when they are used (`UP010`) ([#19769](https://github.com/astral-sh/ruff/pull/19769))
|
||||
- \[`pyupgrade`\] Handle nested `Optional`s (`UP045`) ([#19770](https://github.com/astral-sh/ruff/pull/19770))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`pycodestyle`\] Make `E731` fix unsafe instead of display-only for class assignments ([#19700](https://github.com/astral-sh/ruff/pull/19700))
|
||||
- \[`pyflakes`\] Add secondary annotation showing previous definition (`F811`) ([#19900](https://github.com/astral-sh/ruff/pull/19900))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Fix description of global config file discovery strategy ([#19188](https://github.com/astral-sh/ruff/pull/19188))
|
||||
- Update outdated links to <https://typing.python.org/en/latest/source/stubs.html> ([#19992](https://github.com/astral-sh/ruff/pull/19992))
|
||||
- \[`flake8-annotations`\] Remove unused import in example (`ANN401`) ([#20000](https://github.com/astral-sh/ruff/pull/20000))
|
||||
|
||||
## 0.12.9
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`airflow`\] Add check for `airflow.secrets.cache.SecretCache` (`AIR301`) ([#17707](https://github.com/astral-sh/ruff/pull/17707))
|
||||
- \[`ruff`\] Offer a safe fix for multi-digit zeros (`RUF064`) ([#19847](https://github.com/astral-sh/ruff/pull/19847))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`flake8-blind-except`\] Fix `BLE001` false-positive on `raise ... from None` ([#19755](https://github.com/astral-sh/ruff/pull/19755))
|
||||
- \[`flake8-comprehensions`\] Fix false positive for `C420` with attribute, subscript, or slice assignment targets ([#19513](https://github.com/astral-sh/ruff/pull/19513))
|
||||
- \[`flake8-simplify`\] Fix handling of U+001C..U+001F whitespace (`SIM905`) ([#19849](https://github.com/astral-sh/ruff/pull/19849))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`pylint`\] Use lowercase hex characters to match the formatter (`PLE2513`) ([#19808](https://github.com/astral-sh/ruff/pull/19808))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Fix `lint.future-annotations` link ([#19876](https://github.com/astral-sh/ruff/pull/19876))
|
||||
|
||||
### Other changes
|
||||
|
||||
- Build `riscv64` binaries for release ([#19819](https://github.com/astral-sh/ruff/pull/19819))
|
||||
|
||||
- Add rule code to error description in GitLab output ([#19896](https://github.com/astral-sh/ruff/pull/19896))
|
||||
|
||||
- Improve rendering of the `full` output format ([#19415](https://github.com/astral-sh/ruff/pull/19415))
|
||||
|
||||
Below is an example diff for [`F401`](https://docs.astral.sh/ruff/rules/unused-import/):
|
||||
|
||||
```diff
|
||||
-unused.py:8:19: F401 [*] `pathlib` imported but unused
|
||||
+F401 [*] `pathlib` imported but unused
|
||||
+ --> unused.py:8:19
|
||||
|
|
||||
7 | # Unused, _not_ marked as required (due to the alias).
|
||||
8 | import pathlib as non_alias
|
||||
- | ^^^^^^^^^ F401
|
||||
+ | ^^^^^^^^^
|
||||
9 |
|
||||
10 | # Unused, marked as required.
|
||||
|
|
||||
- = help: Remove unused import: `pathlib`
|
||||
+help: Remove unused import: `pathlib`
|
||||
```
|
||||
|
||||
For now, the primary difference is the movement of the filename, line number, and column information to a second line in the header. This new representation will allow us to make further additions to Ruff's diagnostics, such as adding sub-diagnostics and multiple annotations to the same snippet.
|
||||
|
||||
## 0.12.8
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-use-pathlib`\] Expand `PTH201` to check all `PurePath` subclasses ([#19440](https://github.com/astral-sh/ruff/pull/19440))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`flake8-blind-except`\] Change `BLE001` to correctly parse exception tuples ([#19747](https://github.com/astral-sh/ruff/pull/19747))
|
||||
- \[`flake8-errmsg`\] Exclude `typing.cast` from `EM101` ([#19656](https://github.com/astral-sh/ruff/pull/19656))
|
||||
- \[`flake8-simplify`\] Fix raw string handling in `SIM905` for embedded quotes ([#19591](https://github.com/astral-sh/ruff/pull/19591))
|
||||
- \[`flake8-import-conventions`\] Avoid false positives for NFKC-normalized `__debug__` import aliases in `ICN001` ([#19411](https://github.com/astral-sh/ruff/pull/19411))
|
||||
- \[`isort`\] Fix syntax error after docstring ending with backslash (`I002`) ([#19505](https://github.com/astral-sh/ruff/pull/19505))
|
||||
- \[`pylint`\] Mark `PLC0207` fixes as unsafe when `*args` unpacking is present ([#19679](https://github.com/astral-sh/ruff/pull/19679))
|
||||
- \[`pyupgrade`\] Prevent infinite loop with `I002` (`UP010`, `UP035`) ([#19413](https://github.com/astral-sh/ruff/pull/19413))
|
||||
- \[`ruff`\] Parenthesize generator expressions in f-strings (`RUF010`) ([#19434](https://github.com/astral-sh/ruff/pull/19434))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`eradicate`\] Don't flag `pyrefly` pragmas as unused code (`ERA001`) ([#19731](https://github.com/astral-sh/ruff/pull/19731))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Replace "associative" with "commutative" in docs for `RUF036` ([#19706](https://github.com/astral-sh/ruff/pull/19706))
|
||||
- Fix copy and line separator colors in dark mode ([#19630](https://github.com/astral-sh/ruff/pull/19630))
|
||||
- Fix link to `typing` documentation ([#19648](https://github.com/astral-sh/ruff/pull/19648))
|
||||
- \[`refurb`\] Make more examples error out-of-the-box ([#19695](https://github.com/astral-sh/ruff/pull/19695),[#19673](https://github.com/astral-sh/ruff/pull/19673),[#19672](https://github.com/astral-sh/ruff/pull/19672))
|
||||
|
||||
### Other changes
|
||||
|
||||
- Include column numbers in GitLab output format ([#19708](https://github.com/astral-sh/ruff/pull/19708))
|
||||
- Always expand tabs to four spaces in diagnostics ([#19618](https://github.com/astral-sh/ruff/pull/19618))
|
||||
- Update pre-commit's `ruff` id ([#19654](https://github.com/astral-sh/ruff/pull/19654))
|
||||
|
||||
## 0.12.7
|
||||
|
||||
This is a follow-up release to 0.12.6. Because of an issue in the package metadata, 0.12.6 failed to publish fully to PyPI and has been yanked. Similarly, there is no GitHub release or Git tag for 0.12.6. The contents of the 0.12.7 release are identical to 0.12.6, except for the updated metadata.
|
||||
|
||||
## 0.12.6
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-commas`\] Add support for trailing comma checks in type parameter lists (`COM812`, `COM819`) ([#19390](https://github.com/astral-sh/ruff/pull/19390))
|
||||
- \[`pylint`\] Implement auto-fix for `missing-maxsplit-arg` (`PLC0207`) ([#19387](https://github.com/astral-sh/ruff/pull/19387))
|
||||
- \[`ruff`\] Offer fixes for `RUF039` in more cases ([#19065](https://github.com/astral-sh/ruff/pull/19065))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Support `.pyi` files in ruff analyze graph ([#19611](https://github.com/astral-sh/ruff/pull/19611))
|
||||
- \[`flake8-pyi`\] Preserve inline comment in ellipsis removal (`PYI013`) ([#19399](https://github.com/astral-sh/ruff/pull/19399))
|
||||
- \[`perflint`\] Ignore rule if target is `global` or `nonlocal` (`PERF401`) ([#19539](https://github.com/astral-sh/ruff/pull/19539))
|
||||
- \[`pyupgrade`\] Fix `UP030` to avoid modifying double curly braces in format strings ([#19378](https://github.com/astral-sh/ruff/pull/19378))
|
||||
- \[`refurb`\] Ignore decorated functions for `FURB118` ([#19339](https://github.com/astral-sh/ruff/pull/19339))
|
||||
- \[`refurb`\] Mark `int` and `bool` cases for `Decimal.from_float` as safe fixes (`FURB164`) ([#19468](https://github.com/astral-sh/ruff/pull/19468))
|
||||
- \[`ruff`\] Fix `RUF033` for named default expressions ([#19115](https://github.com/astral-sh/ruff/pull/19115))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-blind-except`\] Change `BLE001` to permit `logging.critical(..., exc_info=True)` ([#19520](https://github.com/astral-sh/ruff/pull/19520))
|
||||
|
||||
### Performance
|
||||
|
||||
- Add support for specifying minimum dots in detected string imports ([#19538](https://github.com/astral-sh/ruff/pull/19538))
|
||||
|
||||
## 0.12.5
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-use-pathlib`\] Add autofix for `PTH101`, `PTH104`, `PTH105`, `PTH121` ([#19404](https://github.com/astral-sh/ruff/pull/19404))
|
||||
- \[`ruff`\] Support byte strings (`RUF055`) ([#18926](https://github.com/astral-sh/ruff/pull/18926))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fix `unreachable` panic in parser ([#19183](https://github.com/astral-sh/ruff/pull/19183))
|
||||
- \[`flake8-pyi`\] Skip fix if all `Union` members are `None` (`PYI016`) ([#19416](https://github.com/astral-sh/ruff/pull/19416))
|
||||
- \[`perflint`\] Parenthesize generator expressions (`PERF401`) ([#19325](https://github.com/astral-sh/ruff/pull/19325))
|
||||
- \[`pylint`\] Handle empty comments after line continuation (`PLR2044`) ([#19405](https://github.com/astral-sh/ruff/pull/19405))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`pep8-naming`\] Fix `N802` false positives for `CGIHTTPRequestHandler` and `SimpleHTTPRequestHandler` ([#19432](https://github.com/astral-sh/ruff/pull/19432))
|
||||
|
||||
## 0.12.4
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-type-checking`, `pyupgrade`, `ruff`\] Add `from __future__ import annotations` when it would allow new fixes (`TC001`, `TC002`, `TC003`, `UP037`, `RUF013`) ([#19100](https://github.com/astral-sh/ruff/pull/19100))
|
||||
- \[`flake8-use-pathlib`\] Add autofix for `PTH109` ([#19245](https://github.com/astral-sh/ruff/pull/19245))
|
||||
- \[`pylint`\] Detect indirect `pathlib.Path` usages for `unspecified-encoding` (`PLW1514`) ([#19304](https://github.com/astral-sh/ruff/pull/19304))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`flake8-bugbear`\] Fix `B017` false negatives for keyword exception arguments ([#19217](https://github.com/astral-sh/ruff/pull/19217))
|
||||
- \[`flake8-use-pathlib`\] Fix false negative on direct `Path()` instantiation (`PTH210`) ([#19388](https://github.com/astral-sh/ruff/pull/19388))
|
||||
- \[`flake8-django`\] Fix `DJ008` false positive for abstract models with type-annotated `abstract` field ([#19221](https://github.com/astral-sh/ruff/pull/19221))
|
||||
- \[`isort`\] Fix `I002` import insertion after docstring with multiple string statements ([#19222](https://github.com/astral-sh/ruff/pull/19222))
|
||||
- \[`isort`\] Treat form feed as valid whitespace before a semicolon ([#19343](https://github.com/astral-sh/ruff/pull/19343))
|
||||
- \[`pydoclint`\] Fix `SyntaxError` from fixes with line continuations (`D201`, `D202`) ([#19246](https://github.com/astral-sh/ruff/pull/19246))
|
||||
- \[`refurb`\] `FURB164` fix should validate arguments and should usually be marked unsafe ([#19136](https://github.com/astral-sh/ruff/pull/19136))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-use-pathlib`\] Skip single dots for `invalid-pathlib-with-suffix` (`PTH210`) on versions >= 3.14 ([#19331](https://github.com/astral-sh/ruff/pull/19331))
|
||||
- \[`pep8_naming`\] Avoid false positives on standard library functions with uppercase names (`N802`) ([#18907](https://github.com/astral-sh/ruff/pull/18907))
|
||||
- \[`pycodestyle`\] Handle brace escapes for t-strings in logical lines ([#19358](https://github.com/astral-sh/ruff/pull/19358))
|
||||
- \[`pylint`\] Extend invalid string character rules to include t-strings ([#19355](https://github.com/astral-sh/ruff/pull/19355))
|
||||
- \[`ruff`\] Allow `strict` kwarg when checking for `starmap-zip` (`RUF058`) in Python 3.14+ ([#19333](https://github.com/astral-sh/ruff/pull/19333))
|
||||
|
||||
### Documentation
|
||||
|
||||
- \[`flake8-type-checking`\] Make `TC010` docs example more realistic ([#19356](https://github.com/astral-sh/ruff/pull/19356))
|
||||
- Make more documentation examples error out-of-the-box ([#19288](https://github.com/astral-sh/ruff/pull/19288),[#19272](https://github.com/astral-sh/ruff/pull/19272),[#19291](https://github.com/astral-sh/ruff/pull/19291),[#19296](https://github.com/astral-sh/ruff/pull/19296),[#19292](https://github.com/astral-sh/ruff/pull/19292),[#19295](https://github.com/astral-sh/ruff/pull/19295),[#19297](https://github.com/astral-sh/ruff/pull/19297),[#19309](https://github.com/astral-sh/ruff/pull/19309))
|
||||
|
||||
## 0.12.3
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-bugbear`\] Support non-context-manager calls in `B017` ([#19063](https://github.com/astral-sh/ruff/pull/19063))
|
||||
- \[`flake8-use-pathlib`\] Add autofixes for `PTH100`, `PTH106`, `PTH107`, `PTH108`, `PTH110`, `PTH111`, `PTH112`, `PTH113`, `PTH114`, `PTH115`, `PTH117`, `PTH119`, `PTH120` ([#19213](https://github.com/astral-sh/ruff/pull/19213))
|
||||
- \[`flake8-use-pathlib`\] Add autofixes for `PTH203`, `PTH204`, `PTH205` ([#18922](https://github.com/astral-sh/ruff/pull/18922))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`flake8-return`\] Fix false-positive for variables used inside nested functions in `RET504` ([#18433](https://github.com/astral-sh/ruff/pull/18433))
|
||||
- Treat form feed as valid whitespace before a line continuation ([#19220](https://github.com/astral-sh/ruff/pull/19220))
|
||||
- \[`flake8-type-checking`\] Fix syntax error introduced by fix (`TC008`) ([#19150](https://github.com/astral-sh/ruff/pull/19150))
|
||||
- \[`pyupgrade`\] Keyword arguments in `super` should suppress the `UP008` fix ([#19131](https://github.com/astral-sh/ruff/pull/19131))
|
||||
|
||||
### Documentation
|
||||
|
||||
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI007`, `PYI008`) ([#19103](https://github.com/astral-sh/ruff/pull/19103))
|
||||
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM116`) ([#19111](https://github.com/astral-sh/ruff/pull/19111))
|
||||
- \[`flake8-type-checking`\] Make example error out-of-the-box (`TC001`) ([#19151](https://github.com/astral-sh/ruff/pull/19151))
|
||||
- \[`flake8-use-pathlib`\] Make example error out-of-the-box (`PTH210`) ([#19189](https://github.com/astral-sh/ruff/pull/19189))
|
||||
- \[`pycodestyle`\] Make example error out-of-the-box (`E272`) ([#19191](https://github.com/astral-sh/ruff/pull/19191))
|
||||
- \[`pycodestyle`\] Make example not raise unnecessary `SyntaxError` (`E114`) ([#19190](https://github.com/astral-sh/ruff/pull/19190))
|
||||
- \[`pydoclint`\] Make example error out-of-the-box (`DOC501`) ([#19218](https://github.com/astral-sh/ruff/pull/19218))
|
||||
- \[`pylint`, `pyupgrade`\] Fix syntax errors in examples (`PLW1501`, `UP028`) ([#19127](https://github.com/astral-sh/ruff/pull/19127))
|
||||
- \[`pylint`\] Update `missing-maxsplit-arg` docs and error to suggest proper usage (`PLC0207`) ([#18949](https://github.com/astral-sh/ruff/pull/18949))
|
||||
- \[`flake8-bandit`\] Make example error out-of-the-box (`S412`) ([#19241](https://github.com/astral-sh/ruff/pull/19241))
|
||||
|
||||
## 0.12.2
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-pyi`\] Expand `Optional[A]` to `A | None` (`PYI016`) ([#18572](https://github.com/astral-sh/ruff/pull/18572))
|
||||
- \[`pyupgrade`\] Mark `UP008` fix safe if no comments are in range ([#18683](https://github.com/astral-sh/ruff/pull/18683))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`flake8-comprehensions`\] Fix `C420` to prepend whitespace when needed ([#18616](https://github.com/astral-sh/ruff/pull/18616))
|
||||
- \[`perflint`\] Fix `PERF403` panic on attribute or subscription loop variable ([#19042](https://github.com/astral-sh/ruff/pull/19042))
|
||||
- \[`pydocstyle`\] Fix `D413` infinite loop for parenthesized docstring ([#18930](https://github.com/astral-sh/ruff/pull/18930))
|
||||
- \[`pylint`\] Fix `PLW0108` autofix introducing a syntax error when the lambda's body contains an assignment expression ([#18678](https://github.com/astral-sh/ruff/pull/18678))
|
||||
- \[`refurb`\] Fix false positive on empty tuples (`FURB168`) ([#19058](https://github.com/astral-sh/ruff/pull/19058))
|
||||
- \[`ruff`\] Allow more `field` calls from `attrs` (`RUF009`) ([#19021](https://github.com/astral-sh/ruff/pull/19021))
|
||||
- \[`ruff`\] Fix syntax error introduced for an empty string followed by a u-prefixed string (`UP025`) ([#18899](https://github.com/astral-sh/ruff/pull/18899))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-executable`\] Allow `uvx` in shebang line (`EXE003`) ([#18967](https://github.com/astral-sh/ruff/pull/18967))
|
||||
- \[`pandas`\] Avoid flagging `PD002` if `pandas` is not imported ([#18963](https://github.com/astral-sh/ruff/pull/18963))
|
||||
- \[`pyupgrade`\] Avoid PEP-604 unions with `typing.NamedTuple` (`UP007`, `UP045`) ([#18682](https://github.com/astral-sh/ruff/pull/18682))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Document link between `import-outside-top-level (PLC0415)` and `lint.flake8-tidy-imports.banned-module-level-imports` ([#18733](https://github.com/astral-sh/ruff/pull/18733))
|
||||
- Fix description of the `format.skip-magic-trailing-comma` example ([#19095](https://github.com/astral-sh/ruff/pull/19095))
|
||||
- \[`airflow`\] Make `AIR302` example error out-of-the-box ([#18988](https://github.com/astral-sh/ruff/pull/18988))
|
||||
- \[`airflow`\] Make `AIR312` example error out-of-the-box ([#18989](https://github.com/astral-sh/ruff/pull/18989))
|
||||
- \[`flake8-annotations`\] Make `ANN401` example error out-of-the-box ([#18974](https://github.com/astral-sh/ruff/pull/18974))
|
||||
- \[`flake8-async`\] Make `ASYNC100` example error out-of-the-box ([#18993](https://github.com/astral-sh/ruff/pull/18993))
|
||||
- \[`flake8-async`\] Make `ASYNC105` example error out-of-the-box ([#19002](https://github.com/astral-sh/ruff/pull/19002))
|
||||
- \[`flake8-async`\] Make `ASYNC110` example error out-of-the-box ([#18975](https://github.com/astral-sh/ruff/pull/18975))
|
||||
- \[`flake8-async`\] Make `ASYNC210` example error out-of-the-box ([#18977](https://github.com/astral-sh/ruff/pull/18977))
|
||||
- \[`flake8-async`\] Make `ASYNC220`, `ASYNC221`, and `ASYNC222` examples error out-of-the-box ([#18978](https://github.com/astral-sh/ruff/pull/18978))
|
||||
- \[`flake8-async`\] Make `ASYNC251` example error out-of-the-box ([#18990](https://github.com/astral-sh/ruff/pull/18990))
|
||||
- \[`flake8-bandit`\] Make `S201` example error out-of-the-box ([#19017](https://github.com/astral-sh/ruff/pull/19017))
|
||||
- \[`flake8-bandit`\] Make `S604` and `S609` examples error out-of-the-box ([#19049](https://github.com/astral-sh/ruff/pull/19049))
|
||||
- \[`flake8-bugbear`\] Make `B028` example error out-of-the-box ([#19054](https://github.com/astral-sh/ruff/pull/19054))
|
||||
- \[`flake8-bugbear`\] Make `B911` example error out-of-the-box ([#19051](https://github.com/astral-sh/ruff/pull/19051))
|
||||
- \[`flake8-datetimez`\] Make `DTZ011` example error out-of-the-box ([#19055](https://github.com/astral-sh/ruff/pull/19055))
|
||||
- \[`flake8-datetimez`\] Make `DTZ901` example error out-of-the-box ([#19056](https://github.com/astral-sh/ruff/pull/19056))
|
||||
- \[`flake8-pyi`\] Make `PYI032` example error out-of-the-box ([#19061](https://github.com/astral-sh/ruff/pull/19061))
|
||||
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI014`, `PYI015`) ([#19097](https://github.com/astral-sh/ruff/pull/19097))
|
||||
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI042`) ([#19101](https://github.com/astral-sh/ruff/pull/19101))
|
||||
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI059`) ([#19080](https://github.com/astral-sh/ruff/pull/19080))
|
||||
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI062`) ([#19079](https://github.com/astral-sh/ruff/pull/19079))
|
||||
- \[`flake8-pytest-style`\] Make example error out-of-the-box (`PT023`) ([#19104](https://github.com/astral-sh/ruff/pull/19104))
|
||||
- \[`flake8-pytest-style`\] Make example error out-of-the-box (`PT030`) ([#19105](https://github.com/astral-sh/ruff/pull/19105))
|
||||
- \[`flake8-quotes`\] Make example error out-of-the-box (`Q003`) ([#19106](https://github.com/astral-sh/ruff/pull/19106))
|
||||
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM110`) ([#19113](https://github.com/astral-sh/ruff/pull/19113))
|
||||
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM113`) ([#19109](https://github.com/astral-sh/ruff/pull/19109))
|
||||
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM401`) ([#19110](https://github.com/astral-sh/ruff/pull/19110))
|
||||
- \[`pyflakes`\] Fix backslash in docs (`F621`) ([#19098](https://github.com/astral-sh/ruff/pull/19098))
|
||||
- \[`pylint`\] Fix `PLC0415` example ([#18970](https://github.com/astral-sh/ruff/pull/18970))
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-errmsg`\] Extend `EM101` to support byte strings ([#18867](https://github.com/astral-sh/ruff/pull/18867))
|
||||
- \[`flake8-use-pathlib`\] Add autofix for `PTH202` ([#18763](https://github.com/astral-sh/ruff/pull/18763))
|
||||
- \[`pygrep-hooks`\] Add `AsyncMock` methods to `invalid-mock-access` (`PGH005`) ([#18547](https://github.com/astral-sh/ruff/pull/18547))
|
||||
- \[`pylint`\] Ignore `__init__.py` files in (`PLC0414`) ([#18400](https://github.com/astral-sh/ruff/pull/18400))
|
||||
- \[`ruff`\] Trigger `RUF037` for empty string and byte strings ([#18862](https://github.com/astral-sh/ruff/pull/18862))
|
||||
- [formatter] Fix missing blank lines before decorated classes in `.pyi` files ([#18888](https://github.com/astral-sh/ruff/pull/18888))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Avoid generating diagnostics with per-file ignores ([#18801](https://github.com/astral-sh/ruff/pull/18801))
|
||||
- Handle parenthesized arguments in `remove_argument` ([#18805](https://github.com/astral-sh/ruff/pull/18805))
|
||||
- \[`flake8-logging`\] Avoid false positive for `exc_info=True` outside `logger.exception` (`LOG014`) ([#18737](https://github.com/astral-sh/ruff/pull/18737))
|
||||
- \[`flake8-pytest-style`\] Enforce `pytest` import for decorators ([#18779](https://github.com/astral-sh/ruff/pull/18779))
|
||||
- \[`flake8-pytest-style`\] Mark autofix for `PT001` and `PT023` as unsafe if there's comments in the decorator ([#18792](https://github.com/astral-sh/ruff/pull/18792))
|
||||
- \[`flake8-pytest-style`\] `PT001`/`PT023` fix makes syntax error on parenthesized decorator ([#18782](https://github.com/astral-sh/ruff/pull/18782))
|
||||
- \[`flake8-raise`\] Make fix unsafe if it deletes comments (`RSE102`) ([#18788](https://github.com/astral-sh/ruff/pull/18788))
|
||||
- \[`flake8-simplify`\] Fix `SIM911` autofix creating a syntax error ([#18793](https://github.com/astral-sh/ruff/pull/18793))
|
||||
- \[`flake8-simplify`\] Fix false negatives for shadowed bindings (`SIM910`, `SIM911`) ([#18794](https://github.com/astral-sh/ruff/pull/18794))
|
||||
- \[`flake8-simplify`\] Preserve original behavior for `except ()` and bare `except` (`SIM105`) ([#18213](https://github.com/astral-sh/ruff/pull/18213))
|
||||
- \[`flake8-pyi`\] Fix `PYI041`'s fix causing `TypeError` with `None | None | ...` ([#18637](https://github.com/astral-sh/ruff/pull/18637))
|
||||
- \[`perflint`\] Fix `PERF101` autofix creating a syntax error and mark autofix as unsafe if there are comments in the `list` call expr ([#18803](https://github.com/astral-sh/ruff/pull/18803))
|
||||
- \[`perflint`\] Fix false negative in `PERF401` ([#18866](https://github.com/astral-sh/ruff/pull/18866))
|
||||
- \[`pylint`\] Avoid flattening nested `min`/`max` when outer call has single argument (`PLW3301`) ([#16885](https://github.com/astral-sh/ruff/pull/16885))
|
||||
- \[`pylint`\] Fix `PLC2801` autofix creating a syntax error ([#18857](https://github.com/astral-sh/ruff/pull/18857))
|
||||
- \[`pylint`\] Mark `PLE0241` autofix as unsafe if there's comments in the base classes ([#18832](https://github.com/astral-sh/ruff/pull/18832))
|
||||
- \[`pylint`\] Suppress `PLE2510`/`PLE2512`/`PLE2513`/`PLE2514`/`PLE2515` autofix if the text contains an odd number of backslashes ([#18856](https://github.com/astral-sh/ruff/pull/18856))
|
||||
- \[`refurb`\] Detect more exotic float literals in `FURB164` ([#18925](https://github.com/astral-sh/ruff/pull/18925))
|
||||
- \[`refurb`\] Fix `FURB163` autofix creating a syntax error for `yield` expressions ([#18756](https://github.com/astral-sh/ruff/pull/18756))
|
||||
- \[`refurb`\] Mark `FURB129` autofix as unsafe if there's comments in the `readlines` call ([#18858](https://github.com/astral-sh/ruff/pull/18858))
|
||||
- \[`ruff`\] Fix false positives and negatives in `RUF010` ([#18690](https://github.com/astral-sh/ruff/pull/18690))
|
||||
- Fix casing of `analyze.direction` variant names ([#18892](https://github.com/astral-sh/ruff/pull/18892))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- Fix f-string interpolation escaping in generated fixes ([#18882](https://github.com/astral-sh/ruff/pull/18882))
|
||||
- \[`flake8-return`\] Mark `RET501` fix unsafe if comments are inside ([#18780](https://github.com/astral-sh/ruff/pull/18780))
|
||||
- \[`flake8-async`\] Fix detection for large integer sleep durations in `ASYNC116` rule ([#18767](https://github.com/astral-sh/ruff/pull/18767))
|
||||
- \[`flake8-async`\] Mark autofix for `ASYNC115` as unsafe if the call expression contains comments ([#18753](https://github.com/astral-sh/ruff/pull/18753))
|
||||
- \[`flake8-bugbear`\] Mark autofix for `B004` as unsafe if the `hasattr` call expr contains comments ([#18755](https://github.com/astral-sh/ruff/pull/18755))
|
||||
- \[`flake8-comprehension`\] Mark autofix for `C420` as unsafe if there's comments inside the dict comprehension ([#18768](https://github.com/astral-sh/ruff/pull/18768))
|
||||
- \[`flake8-comprehensions`\] Handle template strings for comprehension fixes ([#18710](https://github.com/astral-sh/ruff/pull/18710))
|
||||
- \[`flake8-future-annotations`\] Add autofix (`FA100`) ([#18903](https://github.com/astral-sh/ruff/pull/18903))
|
||||
- \[`pyflakes`\] Mark `F504`/`F522`/`F523` autofix as unsafe if there's a call with side effect ([#18839](https://github.com/astral-sh/ruff/pull/18839))
|
||||
- \[`pylint`\] Allow fix with comments and document performance implications (`PLW3301`) ([#18936](https://github.com/astral-sh/ruff/pull/18936))
|
||||
- \[`pylint`\] Detect more exotic `NaN` literals in `PLW0177` ([#18630](https://github.com/astral-sh/ruff/pull/18630))
|
||||
- \[`pylint`\] Fix `PLC1802` autofix creating a syntax error and mark autofix as unsafe if there's comments in the `len` call ([#18836](https://github.com/astral-sh/ruff/pull/18836))
|
||||
- \[`pyupgrade`\] Extend version detection to include `sys.version_info.major` (`UP036`) ([#18633](https://github.com/astral-sh/ruff/pull/18633))
|
||||
- \[`ruff`\] Add lint rule `RUF064` for calling `chmod` with non-octal integers ([#18541](https://github.com/astral-sh/ruff/pull/18541))
|
||||
- \[`ruff`\] Added `cls.__dict__.get('__annotations__')` check (`RUF063`) ([#18233](https://github.com/astral-sh/ruff/pull/18233))
|
||||
- \[`ruff`\] Frozen `dataclass` default should be valid (`RUF009`) ([#18735](https://github.com/astral-sh/ruff/pull/18735))
|
||||
|
||||
### Server
|
||||
|
||||
- Consider virtual path for various server actions ([#18910](https://github.com/astral-sh/ruff/pull/18910))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add fix safety sections ([#18940](https://github.com/astral-sh/ruff/pull/18940),[#18841](https://github.com/astral-sh/ruff/pull/18841),[#18802](https://github.com/astral-sh/ruff/pull/18802),[#18837](https://github.com/astral-sh/ruff/pull/18837),[#18800](https://github.com/astral-sh/ruff/pull/18800),[#18415](https://github.com/astral-sh/ruff/pull/18415),[#18853](https://github.com/astral-sh/ruff/pull/18853),[#18842](https://github.com/astral-sh/ruff/pull/18842))
|
||||
- Use updated pre-commit id ([#18718](https://github.com/astral-sh/ruff/pull/18718))
|
||||
- \[`perflint`\] Small docs improvement to `PERF401` ([#18786](https://github.com/astral-sh/ruff/pull/18786))
|
||||
- \[`pyupgrade`\]: Use `super()`, not `__super__` in error messages (`UP008`) ([#18743](https://github.com/astral-sh/ruff/pull/18743))
|
||||
- \[`flake8-pie`\] Small docs fix to `PIE794` ([#18829](https://github.com/astral-sh/ruff/pull/18829))
|
||||
- \[`flake8-pyi`\] Correct `collections-named-tuple` example to use PascalCase assignment ([#16884](https://github.com/astral-sh/ruff/pull/16884))
|
||||
- \[`flake8-pie`\] Add note on type checking benefits to `unnecessary-dict-kwargs` (`PIE804`) ([#18666](https://github.com/astral-sh/ruff/pull/18666))
|
||||
- \[`pycodestyle`\] Clarify PEP 8 relationship to `whitespace-around-operator` rules ([#18870](https://github.com/astral-sh/ruff/pull/18870))
|
||||
|
||||
### Other changes
|
||||
|
||||
- Disallow newlines in format specifiers of single quoted f- or t-strings ([#18708](https://github.com/astral-sh/ruff/pull/18708))
|
||||
- \[`flake8-logging`\] Add fix safety section to `LOG002` ([#18840](https://github.com/astral-sh/ruff/pull/18840))
|
||||
- \[`pyupgrade`\] Add fix safety section to `UP010` ([#18838](https://github.com/astral-sh/ruff/pull/18838))
|
||||
|
||||
## 0.12.0
|
||||
|
||||
Check out the [blog post](https://astral.sh/blog/ruff-v0.12.0) for a migration
|
||||
Check out the [blog post](https://astral.sh/blog/ruff-v0.13.0) for a migration
|
||||
guide and overview of the changes!
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- **Detection of more syntax errors**
|
||||
- **Several rules can now add `from __future__ import annotations` automatically**
|
||||
|
||||
Ruff now detects version-related syntax errors, such as the use of the `match`
|
||||
statement on Python versions before 3.10, and syntax errors emitted by
|
||||
CPython's compiler, such as irrefutable `match` patterns before the final
|
||||
`case` arm.
|
||||
`TC001`, `TC002`, `TC003`, `RUF013`, and `UP037` now add `from __future__ import annotations` as part of their fixes when the
|
||||
`lint.future-annotations` setting is enabled. This allows the rules to move
|
||||
more imports into `TYPE_CHECKING` blocks (`TC001`, `TC002`, and `TC003`),
|
||||
use PEP 604 union syntax on Python versions before 3.10 (`RUF013`), and
|
||||
unquote more annotations (`UP037`).
|
||||
|
||||
- **New default Python version handling for syntax errors**
|
||||
- **Full module paths are now used to verify first-party modules**
|
||||
|
||||
Ruff will default to the *latest* supported Python version (3.13) when
|
||||
checking for the version-related syntax errors mentioned above to prevent
|
||||
false positives in projects without a Python version configured. The default
|
||||
in all other cases, like applying lint rules, is unchanged and remains at the
|
||||
minimum supported Python version (3.9).
|
||||
Ruff now checks that the full path to a module exists on disk before
|
||||
categorizing it as a first-party import. This change makes first-party
|
||||
import detection more accurate, helping to avoid false positives on local
|
||||
directories with the same name as a third-party dependency, for example. See
|
||||
the [FAQ
|
||||
section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) on import categorization for more details.
|
||||
|
||||
- **Updated f-string formatting**
|
||||
- **Deprecated rules must now be selected by exact rule code**
|
||||
|
||||
Ruff now formats multi-line f-strings with format specifiers to avoid adding a
|
||||
line break after the format specifier. This addresses a change to the Python
|
||||
grammar in version 3.13.4 that made such a line break a syntax error.
|
||||
Ruff will no longer activate deprecated rules selected by their group name
|
||||
or prefix. As noted below, the two remaining deprecated rules were also
|
||||
removed in this release, so this won't affect any current rules, but it will
|
||||
still affect any deprecations in the future.
|
||||
|
||||
- **`rust-toolchain.toml` is no longer included in source distributions**
|
||||
- **The deprecated macOS configuration directory fallback has been removed**
|
||||
|
||||
The `rust-toolchain.toml` is used to specify a higher Rust version than Ruff's
|
||||
minimum supported Rust version (MSRV) for development and building release
|
||||
artifacts. However, when present in source distributions, it would also cause
|
||||
downstream package maintainers to pull in the same Rust toolchain, even if
|
||||
their available toolchain was MSRV-compatible.
|
||||
Ruff will no longer look for a user-level configuration file at
|
||||
`~/Library/Application Support/ruff/ruff.toml` on macOS. This feature was
|
||||
deprecated in v0.5 in favor of using the [XDG
|
||||
specification](https://specifications.freedesktop.org/basedir-spec/latest/)
|
||||
(usually resolving to `~/.config/ruff/ruff.toml`), like on Linux. The
|
||||
fallback and accompanying deprecation warning have now been removed.
|
||||
|
||||
### Removed Rules
|
||||
|
||||
The following rules have been removed:
|
||||
|
||||
- [`suspicious-xmle-tree-usage`](https://docs.astral.sh/ruff/rules/suspicious-xmle-tree-usage/)
|
||||
(`S320`)
|
||||
|
||||
### Deprecated Rules
|
||||
|
||||
The following rules have been deprecated:
|
||||
|
||||
- [`pandas-df-variable-name`](https://docs.astral.sh/ruff/rules/pandas-df-variable-name/)
|
||||
- [`pandas-df-variable-name`](https://docs.astral.sh/ruff/rules/pandas-df-variable-name) (`PD901`)
|
||||
- [`non-pep604-isinstance`](https://docs.astral.sh/ruff/rules/non-pep604-isinstance) (`UP038`)
|
||||
|
||||
### Stabilization
|
||||
|
||||
The following rules have been stabilized and are no longer in preview:
|
||||
|
||||
- [`for-loop-writes`](https://docs.astral.sh/ruff/rules/for-loop-writes) (`FURB122`)
|
||||
- [`check-and-remove-from-set`](https://docs.astral.sh/ruff/rules/check-and-remove-from-set) (`FURB132`)
|
||||
- [`verbose-decimal-constructor`](https://docs.astral.sh/ruff/rules/verbose-decimal-constructor) (`FURB157`)
|
||||
- [`fromisoformat-replace-z`](https://docs.astral.sh/ruff/rules/fromisoformat-replace-z) (`FURB162`)
|
||||
- [`int-on-sliced-str`](https://docs.astral.sh/ruff/rules/int-on-sliced-str) (`FURB166`)
|
||||
- [`exc-info-outside-except-handler`](https://docs.astral.sh/ruff/rules/exc-info-outside-except-handler) (`LOG014`)
|
||||
- [`import-outside-top-level`](https://docs.astral.sh/ruff/rules/import-outside-top-level) (`PLC0415`)
|
||||
- [`unnecessary-dict-index-lookup`](https://docs.astral.sh/ruff/rules/unnecessary-dict-index-lookup) (`PLR1733`)
|
||||
- [`nan-comparison`](https://docs.astral.sh/ruff/rules/nan-comparison) (`PLW0177`)
|
||||
- [`eq-without-hash`](https://docs.astral.sh/ruff/rules/eq-without-hash) (`PLW1641`)
|
||||
- [`pytest-parameter-with-default-argument`](https://docs.astral.sh/ruff/rules/pytest-parameter-with-default-argument) (`PT028`)
|
||||
- [`pytest-warns-too-broad`](https://docs.astral.sh/ruff/rules/pytest-warns-too-broad) (`PT030`)
|
||||
- [`pytest-warns-with-multiple-statements`](https://docs.astral.sh/ruff/rules/pytest-warns-with-multiple-statements) (`PT031`)
|
||||
- [`invalid-formatter-suppression-comment`](https://docs.astral.sh/ruff/rules/invalid-formatter-suppression-comment) (`RUF028`)
|
||||
- [`dataclass-enum`](https://docs.astral.sh/ruff/rules/dataclass-enum) (`RUF049`)
|
||||
- [`class-with-mixed-type-vars`](https://docs.astral.sh/ruff/rules/class-with-mixed-type-vars) (`RUF053`)
|
||||
- [`unnecessary-round`](https://docs.astral.sh/ruff/rules/unnecessary-round) (`RUF057`)
|
||||
- [`starmap-zip`](https://docs.astral.sh/ruff/rules/starmap-zip) (`RUF058`)
|
||||
- [`non-pep604-annotation-optional`] (`UP045`)
|
||||
- [`non-pep695-generic-class`](https://docs.astral.sh/ruff/rules/non-pep695-generic-class) (`UP046`)
|
||||
- [`non-pep695-generic-function`](https://docs.astral.sh/ruff/rules/non-pep695-generic-function) (`UP047`)
|
||||
- [`private-type-parameter`](https://docs.astral.sh/ruff/rules/private-type-parameter) (`UP049`)
|
||||
- [`airflow-dag-no-schedule-argument`](https://docs.astral.sh/ruff/rules/airflow-dag-no-schedule-argument)
|
||||
(`AIR002`)
|
||||
- [`airflow3-removal`](https://docs.astral.sh/ruff/rules/airflow3-removal) (`AIR301`)
|
||||
- [`airflow3-moved-to-provider`](https://docs.astral.sh/ruff/rules/airflow3-moved-to-provider)
|
||||
(`AIR302`)
|
||||
- [`airflow3-suggested-update`](https://docs.astral.sh/ruff/rules/airflow3-suggested-update)
|
||||
(`AIR311`)
|
||||
- [`airflow3-suggested-to-move-to-provider`](https://docs.astral.sh/ruff/rules/airflow3-suggested-to-move-to-provider)
|
||||
(`AIR312`)
|
||||
- [`long-sleep-not-forever`](https://docs.astral.sh/ruff/rules/long-sleep-not-forever) (`ASYNC116`)
|
||||
- [`f-string-number-format`](https://docs.astral.sh/ruff/rules/f-string-number-format) (`FURB116`)
|
||||
- [`os-symlink`](https://docs.astral.sh/ruff/rules/os-symlink) (`PTH211`)
|
||||
- [`generic-not-last-base-class`](https://docs.astral.sh/ruff/rules/generic-not-last-base-class)
|
||||
(`PYI059`)
|
||||
- [`redundant-none-literal`](https://docs.astral.sh/ruff/rules/redundant-none-literal) (`PYI061`)
|
||||
- [`pytest-raises-ambiguous-pattern`](https://docs.astral.sh/ruff/rules/pytest-raises-ambiguous-pattern)
|
||||
(`RUF043`)
|
||||
- [`unused-unpacked-variable`](https://docs.astral.sh/ruff/rules/unused-unpacked-variable)
|
||||
(`RUF059`)
|
||||
- [`useless-class-metaclass-type`](https://docs.astral.sh/ruff/rules/useless-class-metaclass-type)
|
||||
(`UP050`)
|
||||
|
||||
The following behaviors have been stabilized:
|
||||
|
||||
- [`collection-literal-concatenation`] (`RUF005`) now recognizes slices, in
|
||||
addition to list literals and variables.
|
||||
- The fix for [`readlines-in-for`] (`FURB129`) is now marked as always safe.
|
||||
- [`if-else-block-instead-of-if-exp`] (`SIM108`) will now further simplify
|
||||
expressions to use `or` instead of an `if` expression, where possible.
|
||||
- [`unused-noqa`] (`RUF100`) now checks for file-level `noqa` comments as well
|
||||
as inline comments.
|
||||
- [`subprocess-without-shell-equals-true`] (`S603`) now accepts literal strings,
|
||||
as well as lists and tuples of literal strings, as trusted input.
|
||||
- [`boolean-type-hint-positional-argument`] (`FBT001`) now applies to types that
|
||||
include `bool`, like `bool | int` or `typing.Optional[bool]`, in addition to
|
||||
plain `bool` annotations.
|
||||
- [`non-pep604-annotation-union`] (`UP007`) has now been split into two rules.
|
||||
`UP007` now applies only to `typing.Union`, while
|
||||
[`non-pep604-annotation-optional`] (`UP045`) checks for use of
|
||||
`typing.Optional`. `UP045` has also been stabilized in this release, but you
|
||||
may need to update existing `include`, `ignore`, or `noqa` settings to
|
||||
accommodate this change.
|
||||
- [`assert-raises-exception`](https://docs.astral.sh/ruff/rules/assert-raises-exception) (`B017`)
|
||||
now checks for direct calls to `unittest.TestCase.assert_raises` and `pytest.raises` instead of
|
||||
only the context manager forms.
|
||||
- [`missing-trailing-comma`](https://docs.astral.sh/ruff/rules/missing-trailing-comma) (`COM812`)
|
||||
and [`prohibited-trailing-comma`](https://docs.astral.sh/ruff/rules/prohibited-trailing-comma)
|
||||
(`COM819`) now check for trailing commas in PEP 695 type parameter lists.
|
||||
- [`raw-string-in-exception`](https://docs.astral.sh/ruff/rules/raw-string-in-exception) (`EM101`)
|
||||
now also checks for byte strings in exception messages.
|
||||
- [`invalid-mock-access`](https://docs.astral.sh/ruff/rules/invalid-mock-access) (`PGH005`) now
|
||||
checks for `AsyncMock` methods like `not_awaited` in addition to the synchronous variants.
|
||||
- [`useless-import-alias`](https://docs.astral.sh/ruff/rules/useless-import-alias) (`PLC0414`) no
|
||||
longer applies to `__init__.py` files, where it conflicted with one of the suggested fixes for
|
||||
[`unused-import`](https://docs.astral.sh/ruff/rules/unused-import) (`F401`).
|
||||
- [`bidirectional-unicode`](https://docs.astral.sh/ruff/rules/bidirectional-unicode) (`PLE2502`) now
|
||||
also checks for U+061C (Arabic Letter Mark).
|
||||
- The fix for
|
||||
[`multiple-with-statements`](https://docs.astral.sh/ruff/rules/multiple-with-statements)
|
||||
(`SIM117`) is now marked as always safe.
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`ruff`\] Check for non-context-manager use of `pytest.raises`, `pytest.warns`, and `pytest.deprecated_call` (`RUF061`) ([#17368](https://github.com/astral-sh/ruff/pull/17368))
|
||||
- [syntax-errors] Raise unsupported syntax error for template strings prior to Python 3.14 ([#18664](https://github.com/astral-sh/ruff/pull/18664))
|
||||
- \[`pyupgrade`\] Enable `UP043` in stub files ([#20027](https://github.com/astral-sh/ruff/pull/20027))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Add syntax error when conversion flag does not immediately follow exclamation mark ([#18706](https://github.com/astral-sh/ruff/pull/18706))
|
||||
- Add trailing space around `readlines` ([#18542](https://github.com/astral-sh/ruff/pull/18542))
|
||||
- Fix `\r` and `\r\n` handling in t- and f-string debug texts ([#18673](https://github.com/astral-sh/ruff/pull/18673))
|
||||
- Hug closing `}` when f-string expression has a format specifier ([#18704](https://github.com/astral-sh/ruff/pull/18704))
|
||||
- \[`flake8-pyi`\] Avoid syntax error in the case of starred and keyword arguments (`PYI059`) ([#18611](https://github.com/astral-sh/ruff/pull/18611))
|
||||
- \[`flake8-return`\] Fix `RET504` autofix generating a syntax error ([#18428](https://github.com/astral-sh/ruff/pull/18428))
|
||||
- \[`pep8-naming`\] Suppress fix for `N804` and `N805` if the recommended name is already used ([#18472](https://github.com/astral-sh/ruff/pull/18472))
|
||||
- \[`pycodestyle`\] Avoid causing a syntax error in expressions spanning multiple lines (`E731`) ([#18479](https://github.com/astral-sh/ruff/pull/18479))
|
||||
- \[`pyupgrade`\] Suppress `UP008` if `super` is shadowed ([#18688](https://github.com/astral-sh/ruff/pull/18688))
|
||||
- \[`refurb`\] Parenthesize lambda and ternary expressions (`FURB122`, `FURB142`) ([#18592](https://github.com/astral-sh/ruff/pull/18592))
|
||||
- \[`ruff`\] Handle extra arguments to `deque` (`RUF037`) ([#18614](https://github.com/astral-sh/ruff/pull/18614))
|
||||
- \[`ruff`\] Preserve parentheses around `deque` in fix for `unnecessary-empty-iterable-within-deque-call` (`RUF037`) ([#18598](https://github.com/astral-sh/ruff/pull/18598))
|
||||
- \[`ruff`\] Validate arguments before offering a fix (`RUF056`) ([#18631](https://github.com/astral-sh/ruff/pull/18631))
|
||||
- \[`ruff`\] Skip fix for `RUF059` if dummy name is already bound ([#18509](https://github.com/astral-sh/ruff/pull/18509))
|
||||
- \[`pylint`\] Fix `PLW0128` to check assignment targets in square brackets and after asterisks ([#18665](https://github.com/astral-sh/ruff/pull/18665))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- Fix false positive on mutations in `return` statements (`B909`) ([#18408](https://github.com/astral-sh/ruff/pull/18408))
|
||||
- Treat `ty:` comments as pragma comments ([#18532](https://github.com/astral-sh/ruff/pull/18532))
|
||||
- \[`flake8-pyi`\] Apply `custom-typevar-for-self` to string annotations (`PYI019`) ([#18311](https://github.com/astral-sh/ruff/pull/18311))
|
||||
- \[`pyupgrade`\] Don't offer a fix for `Optional[None]` (`UP007`, `UP045)` ([#18545](https://github.com/astral-sh/ruff/pull/18545))
|
||||
- \[`pyupgrade`\] Fix `super(__class__, self)` detection (`UP008`) ([#18478](https://github.com/astral-sh/ruff/pull/18478))
|
||||
- \[`refurb`\] Make the fix for `FURB163` unsafe for `log2`, `log10`, `*args`, and deleted comments ([#18645](https://github.com/astral-sh/ruff/pull/18645))
|
||||
- \[`pyupgrade`\] Apply `UP008` only when the `__class__` cell exists ([#19424](https://github.com/astral-sh/ruff/pull/19424))
|
||||
- \[`ruff`\] Fix empty f-string detection in `in-empty-collection` (`RUF060`) ([#20249](https://github.com/astral-sh/ruff/pull/20249))
|
||||
|
||||
### Server
|
||||
|
||||
- Support cancellation requests ([#18627](https://github.com/astral-sh/ruff/pull/18627))
|
||||
- Add support for using uv as an alternative formatter backend ([#19665](https://github.com/astral-sh/ruff/pull/19665))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Drop confusing second `*` from glob pattern example for `per-file-target-version` ([#18709](https://github.com/astral-sh/ruff/pull/18709))
|
||||
- Update Neovim configuration examples ([#18491](https://github.com/astral-sh/ruff/pull/18491))
|
||||
- \[`pylint`\] De-emphasize `__hash__ = Parent.__hash__` (`PLW1641`) ([#18613](https://github.com/astral-sh/ruff/pull/18613))
|
||||
- \[`refurb`\] Add a note about float literal handling (`FURB157`) ([#18615](https://github.com/astral-sh/ruff/pull/18615))
|
||||
- \[`pep8-naming`\] Fix formatting of `__all__` (`N816`) ([#20301](https://github.com/astral-sh/ruff/pull/20301))
|
||||
|
||||
## 0.12.x
|
||||
|
||||
See [changelogs/0.12.x](./changelogs/0.12.x.md)
|
||||
|
||||
## 0.11.x
|
||||
|
||||
@@ -556,12 +158,3 @@ See [changelogs/0.2.x](./changelogs/0.2.x.md)
|
||||
## 0.1.x
|
||||
|
||||
See [changelogs/0.1.x](./changelogs/0.1.x.md)
|
||||
|
||||
[`boolean-type-hint-positional-argument`]: https://docs.astral.sh/ruff/rules/boolean-type-hint-positional-argument
|
||||
[`collection-literal-concatenation`]: https://docs.astral.sh/ruff/rules/collection-literal-concatenation
|
||||
[`if-else-block-instead-of-if-exp`]: https://docs.astral.sh/ruff/rules/if-else-block-instead-of-if-exp
|
||||
[`non-pep604-annotation-optional`]: https://docs.astral.sh/ruff/rules/non-pep604-annotation-optional
|
||||
[`non-pep604-annotation-union`]: https://docs.astral.sh/ruff/rules/non-pep604-annotation-union
|
||||
[`readlines-in-for`]: https://docs.astral.sh/ruff/rules/readlines-in-for
|
||||
[`subprocess-without-shell-equals-true`]: https://docs.astral.sh/ruff/rules/subprocess-without-shell-equals-true
|
||||
[`unused-noqa`]: https://docs.astral.sh/ruff/rules/unused-noqa
|
||||
|
||||
223
Cargo.lock
generated
223
Cargo.lock
generated
@@ -257,9 +257,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.3"
|
||||
version = "2.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
|
||||
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
@@ -295,7 +295,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata 0.4.10",
|
||||
"regex-automata",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -408,9 +408,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.46"
|
||||
version = "4.5.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57"
|
||||
checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -418,9 +418,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.46"
|
||||
version = "4.5.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41"
|
||||
checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -461,9 +461,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.45"
|
||||
version = "4.5.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6"
|
||||
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -603,7 +603,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -612,7 +612,7 @@ version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1035,7 +1035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1231,8 +1231,8 @@ dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"log",
|
||||
"regex-automata 0.4.10",
|
||||
"regex-syntax 0.8.5",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1241,7 +1241,7 @@ version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"ignore",
|
||||
"walkdir",
|
||||
]
|
||||
@@ -1459,7 +1459,7 @@ dependencies = [
|
||||
"globset",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex-automata 0.4.10",
|
||||
"regex-automata",
|
||||
"same-file",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
@@ -1521,7 +1521,7 @@ version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
@@ -1537,9 +1537,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.43.1"
|
||||
version = "1.43.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "154934ea70c58054b556dd430b99a98c2a7ff5309ac9891597e339b5c28f4371"
|
||||
checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0"
|
||||
dependencies = [
|
||||
"console 0.15.11",
|
||||
"globset",
|
||||
@@ -1617,7 +1617,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1681,7 +1681,7 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1728,9 +1728,9 @@ checksum = "a037eddb7d28de1d0fc42411f501b53b75838d313908078d6698d064f3029b24"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
version = "0.3.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
@@ -1770,9 +1770,9 @@ checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
version = "1.8.2"
|
||||
version = "1.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae28ddc5b90c3e3146a21d051ca095cbc8d932ad8714cf65ddf71a9abb35684c"
|
||||
checksum = "052ef5d9fc958a51aeebdf3713573b36c6fd6eed0bf0e60e204d2c0f8cf19b9f"
|
||||
dependencies = [
|
||||
"annotate-snippets",
|
||||
"libcst_derive",
|
||||
@@ -1785,9 +1785,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libcst_derive"
|
||||
version = "1.8.2"
|
||||
version = "1.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc2de5c2f62bcf8a4f7290b1854388b262c4b68f1db1a3ee3ef6d4c1319b00a3"
|
||||
checksum = "a91a751afee92cbdd59d4bc6754c7672712eec2d30a308f23de4e3287b2929cb"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -1809,7 +1809,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
]
|
||||
@@ -1850,9 +1850,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
|
||||
[[package]]
|
||||
name = "lsp-server"
|
||||
@@ -1913,11 +1913,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||
dependencies = [
|
||||
"regex-automata 0.1.10",
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2014,7 +2014,7 @@ version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
@@ -2026,7 +2026,7 @@ version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
@@ -2054,7 +2054,7 @@ version = "8.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
@@ -2074,12 +2074,11 @@ checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
version = "0.50.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2154,12 +2153,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.4"
|
||||
@@ -2666,7 +2659,7 @@ version = "0.5.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2688,17 +2681,8 @@ checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.4.10",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
dependencies = [
|
||||
"regex-syntax 0.6.29",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2709,7 +2693,7 @@ checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.8.5",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2718,12 +2702,6 @@ version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
@@ -2743,13 +2721,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.12.11"
|
||||
version = "0.13.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
"assert_fs",
|
||||
"bincode 2.0.1",
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"cachedir",
|
||||
"clap",
|
||||
"clap_complete_command",
|
||||
@@ -2999,11 +2977,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.12.11"
|
||||
version = "0.13.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"clap",
|
||||
"colored 3.0.0",
|
||||
"fern",
|
||||
@@ -3108,7 +3086,7 @@ name = "ruff_python_ast"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"compact_str",
|
||||
"get-size2",
|
||||
"is-macro",
|
||||
@@ -3196,7 +3174,7 @@ dependencies = [
|
||||
name = "ruff_python_literal"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"itertools 0.14.0",
|
||||
"ruff_python_ast",
|
||||
"unic-ucd-category",
|
||||
@@ -3207,7 +3185,7 @@ name = "ruff_python_parser"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"bstr",
|
||||
"compact_str",
|
||||
"get-size2",
|
||||
@@ -3232,7 +3210,7 @@ dependencies = [
|
||||
name = "ruff_python_semantic"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"ruff_cache",
|
||||
@@ -3253,7 +3231,7 @@ dependencies = [
|
||||
name = "ruff_python_stdlib"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
@@ -3337,7 +3315,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.12.11"
|
||||
version = "0.13.0"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -3430,11 +3408,11 @@ version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3827,7 +3805,7 @@ dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4161,15 +4139,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"regex-automata",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
@@ -4240,7 +4218,8 @@ dependencies = [
|
||||
name = "ty_ide"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"camino",
|
||||
"get-size2",
|
||||
"insta",
|
||||
"itertools 0.14.0",
|
||||
@@ -4278,7 +4257,7 @@ dependencies = [
|
||||
"pep440_rs",
|
||||
"rayon",
|
||||
"regex",
|
||||
"regex-automata 0.4.10",
|
||||
"regex-automata",
|
||||
"ruff_cache",
|
||||
"ruff_db",
|
||||
"ruff_macros",
|
||||
@@ -4304,7 +4283,7 @@ name = "ty_python_semantic"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"bitvec",
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
@@ -4357,7 +4336,7 @@ name = "ty_server"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"crossbeam",
|
||||
"dunce",
|
||||
"insta",
|
||||
@@ -4400,7 +4379,7 @@ name = "ty_test"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
"insta",
|
||||
@@ -4759,21 +4738,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
@@ -4785,9 +4765,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.50"
|
||||
version = "0.4.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
|
||||
checksum = "0ca85039a9b469b38336411d6d6ced91f3fc87109a2a27b0c197663f5144dffe"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@@ -4798,9 +4778,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -4808,9 +4788,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4821,18 +4801,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
version = "0.2.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test"
|
||||
version = "0.3.50"
|
||||
version = "0.3.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3"
|
||||
checksum = "80cc7f8a4114fdaa0c58383caf973fc126cf004eba25c9dc639bccd3880d55ad"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"minicov",
|
||||
@@ -4843,9 +4823,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-test-macro"
|
||||
version = "0.3.50"
|
||||
version = "0.3.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b"
|
||||
checksum = "c5ada2ab788d46d4bda04c9d567702a79c8ced14f51f221646a16ed39d0e6a5d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4854,9 +4834,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.77"
|
||||
version = "0.3.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
|
||||
checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -4892,37 +4872,15 @@ dependencies = [
|
||||
"glob",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.2"
|
||||
@@ -4982,6 +4940,15 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
@@ -5150,7 +5117,7 @@ version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.3",
|
||||
"bitflags 2.9.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
10
Cargo.toml
10
Cargo.toml
@@ -115,7 +115,7 @@ jiff = { version = "0.2.0" }
|
||||
js-sys = { version = "0.3.69" }
|
||||
jod-thread = { version = "1.0.0" }
|
||||
libc = { version = "0.2.153" }
|
||||
libcst = { version = "1.1.0", default-features = false }
|
||||
libcst = { version = "1.8.4", default-features = false }
|
||||
log = { version = "0.4.17" }
|
||||
lsp-server = { version = "0.7.6" }
|
||||
lsp-types = { git = "https://github.com/astral-sh/lsp-types.git", rev = "3512a9f", features = [
|
||||
@@ -251,6 +251,14 @@ rest_pat_in_fully_bound_structs = "warn"
|
||||
redundant_clone = "warn"
|
||||
debug_assert_with_mut_call = "warn"
|
||||
unused_peekable = "warn"
|
||||
# This lint sometimes flags code whose `if` and `else`
|
||||
# bodies could be flipped when a `!` operator is removed.
|
||||
# While perhaps sometimes a good idea, it is also often
|
||||
# not a good idea due to other factors impacting
|
||||
# readability. For example, if flipping the bodies results
|
||||
# in the `if` being an order of magnitude bigger than the
|
||||
# `else`, then some might consider that harder to read.
|
||||
if_not_else = "allow"
|
||||
|
||||
# Diagnostics are not actionable: Enable once https://github.com/rust-lang/rust-clippy/issues/13774 is resolved.
|
||||
large_stack_arrays = "allow"
|
||||
|
||||
@@ -148,8 +148,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.12.11/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.12.11/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.13.0/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.13.0/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -182,7 +182,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.12.11
|
||||
rev: v0.13.0
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
||||
551
changelogs/0.12.x.md
Normal file
551
changelogs/0.12.x.md
Normal file
@@ -0,0 +1,551 @@
|
||||
# Changelog 0.12.x
|
||||
|
||||
## 0.12.0
|
||||
|
||||
Check out the [blog post](https://astral.sh/blog/ruff-v0.12.0) for a migration
|
||||
guide and overview of the changes!
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- **Detection of more syntax errors**
|
||||
|
||||
Ruff now detects version-related syntax errors, such as the use of the `match`
|
||||
statement on Python versions before 3.10, and syntax errors emitted by
|
||||
CPython's compiler, such as irrefutable `match` patterns before the final
|
||||
`case` arm.
|
||||
|
||||
- **New default Python version handling for syntax errors**
|
||||
|
||||
Ruff will default to the *latest* supported Python version (3.13) when
|
||||
checking for the version-related syntax errors mentioned above to prevent
|
||||
false positives in projects without a Python version configured. The default
|
||||
in all other cases, like applying lint rules, is unchanged and remains at the
|
||||
minimum supported Python version (3.9).
|
||||
|
||||
- **Updated f-string formatting**
|
||||
|
||||
Ruff now formats multi-line f-strings with format specifiers to avoid adding a
|
||||
line break after the format specifier. This addresses a change to the Python
|
||||
grammar in version 3.13.4 that made such a line break a syntax error.
|
||||
|
||||
- **`rust-toolchain.toml` is no longer included in source distributions**
|
||||
|
||||
The `rust-toolchain.toml` is used to specify a higher Rust version than Ruff's
|
||||
minimum supported Rust version (MSRV) for development and building release
|
||||
artifacts. However, when present in source distributions, it would also cause
|
||||
downstream package maintainers to pull in the same Rust toolchain, even if
|
||||
their available toolchain was MSRV-compatible.
|
||||
|
||||
### Removed Rules
|
||||
|
||||
The following rules have been removed:
|
||||
|
||||
- [`suspicious-xmle-tree-usage`](https://docs.astral.sh/ruff/rules/suspicious-xmle-tree-usage/)
|
||||
(`S320`)
|
||||
|
||||
### Deprecated Rules
|
||||
|
||||
The following rules have been deprecated:
|
||||
|
||||
- [`pandas-df-variable-name`](https://docs.astral.sh/ruff/rules/pandas-df-variable-name/)
|
||||
|
||||
### Stabilization
|
||||
|
||||
The following rules have been stabilized and are no longer in preview:
|
||||
|
||||
- [`for-loop-writes`](https://docs.astral.sh/ruff/rules/for-loop-writes) (`FURB122`)
|
||||
- [`check-and-remove-from-set`](https://docs.astral.sh/ruff/rules/check-and-remove-from-set) (`FURB132`)
|
||||
- [`verbose-decimal-constructor`](https://docs.astral.sh/ruff/rules/verbose-decimal-constructor) (`FURB157`)
|
||||
- [`fromisoformat-replace-z`](https://docs.astral.sh/ruff/rules/fromisoformat-replace-z) (`FURB162`)
|
||||
- [`int-on-sliced-str`](https://docs.astral.sh/ruff/rules/int-on-sliced-str) (`FURB166`)
|
||||
- [`exc-info-outside-except-handler`](https://docs.astral.sh/ruff/rules/exc-info-outside-except-handler) (`LOG014`)
|
||||
- [`import-outside-top-level`](https://docs.astral.sh/ruff/rules/import-outside-top-level) (`PLC0415`)
|
||||
- [`unnecessary-dict-index-lookup`](https://docs.astral.sh/ruff/rules/unnecessary-dict-index-lookup) (`PLR1733`)
|
||||
- [`nan-comparison`](https://docs.astral.sh/ruff/rules/nan-comparison) (`PLW0177`)
|
||||
- [`eq-without-hash`](https://docs.astral.sh/ruff/rules/eq-without-hash) (`PLW1641`)
|
||||
- [`pytest-parameter-with-default-argument`](https://docs.astral.sh/ruff/rules/pytest-parameter-with-default-argument) (`PT028`)
|
||||
- [`pytest-warns-too-broad`](https://docs.astral.sh/ruff/rules/pytest-warns-too-broad) (`PT030`)
|
||||
- [`pytest-warns-with-multiple-statements`](https://docs.astral.sh/ruff/rules/pytest-warns-with-multiple-statements) (`PT031`)
|
||||
- [`invalid-formatter-suppression-comment`](https://docs.astral.sh/ruff/rules/invalid-formatter-suppression-comment) (`RUF028`)
|
||||
- [`dataclass-enum`](https://docs.astral.sh/ruff/rules/dataclass-enum) (`RUF049`)
|
||||
- [`class-with-mixed-type-vars`](https://docs.astral.sh/ruff/rules/class-with-mixed-type-vars) (`RUF053`)
|
||||
- [`unnecessary-round`](https://docs.astral.sh/ruff/rules/unnecessary-round) (`RUF057`)
|
||||
- [`starmap-zip`](https://docs.astral.sh/ruff/rules/starmap-zip) (`RUF058`)
|
||||
- [`non-pep604-annotation-optional`] (`UP045`)
|
||||
- [`non-pep695-generic-class`](https://docs.astral.sh/ruff/rules/non-pep695-generic-class) (`UP046`)
|
||||
- [`non-pep695-generic-function`](https://docs.astral.sh/ruff/rules/non-pep695-generic-function) (`UP047`)
|
||||
- [`private-type-parameter`](https://docs.astral.sh/ruff/rules/private-type-parameter) (`UP049`)
|
||||
|
||||
The following behaviors have been stabilized:
|
||||
|
||||
- [`collection-literal-concatenation`] (`RUF005`) now recognizes slices, in
|
||||
addition to list literals and variables.
|
||||
- The fix for [`readlines-in-for`] (`FURB129`) is now marked as always safe.
|
||||
- [`if-else-block-instead-of-if-exp`] (`SIM108`) will now further simplify
|
||||
expressions to use `or` instead of an `if` expression, where possible.
|
||||
- [`unused-noqa`] (`RUF100`) now checks for file-level `noqa` comments as well
|
||||
as inline comments.
|
||||
- [`subprocess-without-shell-equals-true`] (`S603`) now accepts literal strings,
|
||||
as well as lists and tuples of literal strings, as trusted input.
|
||||
- [`boolean-type-hint-positional-argument`] (`FBT001`) now applies to types that
|
||||
include `bool`, like `bool | int` or `typing.Optional[bool]`, in addition to
|
||||
plain `bool` annotations.
|
||||
- [`non-pep604-annotation-union`] (`UP007`) has now been split into two rules.
|
||||
`UP007` now applies only to `typing.Union`, while
|
||||
[`non-pep604-annotation-optional`] (`UP045`) checks for use of
|
||||
`typing.Optional`. `UP045` has also been stabilized in this release, but you
|
||||
may need to update existing `include`, `ignore`, or `noqa` settings to
|
||||
accommodate this change.
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`ruff`\] Check for non-context-manager use of `pytest.raises`, `pytest.warns`, and `pytest.deprecated_call` (`RUF061`) ([#17368](https://github.com/astral-sh/ruff/pull/17368))
|
||||
- [syntax-errors] Raise unsupported syntax error for template strings prior to Python 3.14 ([#18664](https://github.com/astral-sh/ruff/pull/18664))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Add syntax error when conversion flag does not immediately follow exclamation mark ([#18706](https://github.com/astral-sh/ruff/pull/18706))
|
||||
- Add trailing space around `readlines` ([#18542](https://github.com/astral-sh/ruff/pull/18542))
|
||||
- Fix `\r` and `\r\n` handling in t- and f-string debug texts ([#18673](https://github.com/astral-sh/ruff/pull/18673))
|
||||
- Hug closing `}` when f-string expression has a format specifier ([#18704](https://github.com/astral-sh/ruff/pull/18704))
|
||||
- \[`flake8-pyi`\] Avoid syntax error in the case of starred and keyword arguments (`PYI059`) ([#18611](https://github.com/astral-sh/ruff/pull/18611))
|
||||
- \[`flake8-return`\] Fix `RET504` autofix generating a syntax error ([#18428](https://github.com/astral-sh/ruff/pull/18428))
|
||||
- \[`pep8-naming`\] Suppress fix for `N804` and `N805` if the recommended name is already used ([#18472](https://github.com/astral-sh/ruff/pull/18472))
|
||||
- \[`pycodestyle`\] Avoid causing a syntax error in expressions spanning multiple lines (`E731`) ([#18479](https://github.com/astral-sh/ruff/pull/18479))
|
||||
- \[`pyupgrade`\] Suppress `UP008` if `super` is shadowed ([#18688](https://github.com/astral-sh/ruff/pull/18688))
|
||||
- \[`refurb`\] Parenthesize lambda and ternary expressions (`FURB122`, `FURB142`) ([#18592](https://github.com/astral-sh/ruff/pull/18592))
|
||||
- \[`ruff`\] Handle extra arguments to `deque` (`RUF037`) ([#18614](https://github.com/astral-sh/ruff/pull/18614))
|
||||
- \[`ruff`\] Preserve parentheses around `deque` in fix for `unnecessary-empty-iterable-within-deque-call` (`RUF037`) ([#18598](https://github.com/astral-sh/ruff/pull/18598))
|
||||
- \[`ruff`\] Validate arguments before offering a fix (`RUF056`) ([#18631](https://github.com/astral-sh/ruff/pull/18631))
|
||||
- \[`ruff`\] Skip fix for `RUF059` if dummy name is already bound ([#18509](https://github.com/astral-sh/ruff/pull/18509))
|
||||
- \[`pylint`\] Fix `PLW0128` to check assignment targets in square brackets and after asterisks ([#18665](https://github.com/astral-sh/ruff/pull/18665))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- Fix false positive on mutations in `return` statements (`B909`) ([#18408](https://github.com/astral-sh/ruff/pull/18408))
|
||||
- Treat `ty:` comments as pragma comments ([#18532](https://github.com/astral-sh/ruff/pull/18532))
|
||||
- \[`flake8-pyi`\] Apply `custom-typevar-for-self` to string annotations (`PYI019`) ([#18311](https://github.com/astral-sh/ruff/pull/18311))
|
||||
- \[`pyupgrade`\] Don't offer a fix for `Optional[None]` (`UP007`, `UP045)` ([#18545](https://github.com/astral-sh/ruff/pull/18545))
|
||||
- \[`pyupgrade`\] Fix `super(__class__, self)` detection (`UP008`) ([#18478](https://github.com/astral-sh/ruff/pull/18478))
|
||||
- \[`refurb`\] Make the fix for `FURB163` unsafe for `log2`, `log10`, `*args`, and deleted comments ([#18645](https://github.com/astral-sh/ruff/pull/18645))
|
||||
|
||||
### Server
|
||||
|
||||
- Support cancellation requests ([#18627](https://github.com/astral-sh/ruff/pull/18627))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Drop confusing second `*` from glob pattern example for `per-file-target-version` ([#18709](https://github.com/astral-sh/ruff/pull/18709))
|
||||
- Update Neovim configuration examples ([#18491](https://github.com/astral-sh/ruff/pull/18491))
|
||||
- \[`pylint`\] De-emphasize `__hash__ = Parent.__hash__` (`PLW1641`) ([#18613](https://github.com/astral-sh/ruff/pull/18613))
|
||||
- \[`refurb`\] Add a note about float literal handling (`FURB157`) ([#18615](https://github.com/astral-sh/ruff/pull/18615))
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-errmsg`\] Extend `EM101` to support byte strings ([#18867](https://github.com/astral-sh/ruff/pull/18867))
|
||||
- \[`flake8-use-pathlib`\] Add autofix for `PTH202` ([#18763](https://github.com/astral-sh/ruff/pull/18763))
|
||||
- \[`pygrep-hooks`\] Add `AsyncMock` methods to `invalid-mock-access` (`PGH005`) ([#18547](https://github.com/astral-sh/ruff/pull/18547))
|
||||
- \[`pylint`\] Ignore `__init__.py` files in (`PLC0414`) ([#18400](https://github.com/astral-sh/ruff/pull/18400))
|
||||
- \[`ruff`\] Trigger `RUF037` for empty string and byte strings ([#18862](https://github.com/astral-sh/ruff/pull/18862))
|
||||
- [formatter] Fix missing blank lines before decorated classes in `.pyi` files ([#18888](https://github.com/astral-sh/ruff/pull/18888))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Avoid generating diagnostics with per-file ignores ([#18801](https://github.com/astral-sh/ruff/pull/18801))
|
||||
- Handle parenthesized arguments in `remove_argument` ([#18805](https://github.com/astral-sh/ruff/pull/18805))
|
||||
- \[`flake8-logging`\] Avoid false positive for `exc_info=True` outside `logger.exception` (`LOG014`) ([#18737](https://github.com/astral-sh/ruff/pull/18737))
|
||||
- \[`flake8-pytest-style`\] Enforce `pytest` import for decorators ([#18779](https://github.com/astral-sh/ruff/pull/18779))
|
||||
- \[`flake8-pytest-style`\] Mark autofix for `PT001` and `PT023` as unsafe if there's comments in the decorator ([#18792](https://github.com/astral-sh/ruff/pull/18792))
|
||||
- \[`flake8-pytest-style`\] `PT001`/`PT023` fix makes syntax error on parenthesized decorator ([#18782](https://github.com/astral-sh/ruff/pull/18782))
|
||||
- \[`flake8-raise`\] Make fix unsafe if it deletes comments (`RSE102`) ([#18788](https://github.com/astral-sh/ruff/pull/18788))
|
||||
- \[`flake8-simplify`\] Fix `SIM911` autofix creating a syntax error ([#18793](https://github.com/astral-sh/ruff/pull/18793))
|
||||
- \[`flake8-simplify`\] Fix false negatives for shadowed bindings (`SIM910`, `SIM911`) ([#18794](https://github.com/astral-sh/ruff/pull/18794))
|
||||
- \[`flake8-simplify`\] Preserve original behavior for `except ()` and bare `except` (`SIM105`) ([#18213](https://github.com/astral-sh/ruff/pull/18213))
|
||||
- \[`flake8-pyi`\] Fix `PYI041`'s fix causing `TypeError` with `None | None | ...` ([#18637](https://github.com/astral-sh/ruff/pull/18637))
|
||||
- \[`perflint`\] Fix `PERF101` autofix creating a syntax error and mark autofix as unsafe if there are comments in the `list` call expr ([#18803](https://github.com/astral-sh/ruff/pull/18803))
|
||||
- \[`perflint`\] Fix false negative in `PERF401` ([#18866](https://github.com/astral-sh/ruff/pull/18866))
|
||||
- \[`pylint`\] Avoid flattening nested `min`/`max` when outer call has single argument (`PLW3301`) ([#16885](https://github.com/astral-sh/ruff/pull/16885))
|
||||
- \[`pylint`\] Fix `PLC2801` autofix creating a syntax error ([#18857](https://github.com/astral-sh/ruff/pull/18857))
|
||||
- \[`pylint`\] Mark `PLE0241` autofix as unsafe if there's comments in the base classes ([#18832](https://github.com/astral-sh/ruff/pull/18832))
|
||||
- \[`pylint`\] Suppress `PLE2510`/`PLE2512`/`PLE2513`/`PLE2514`/`PLE2515` autofix if the text contains an odd number of backslashes ([#18856](https://github.com/astral-sh/ruff/pull/18856))
|
||||
- \[`refurb`\] Detect more exotic float literals in `FURB164` ([#18925](https://github.com/astral-sh/ruff/pull/18925))
|
||||
- \[`refurb`\] Fix `FURB163` autofix creating a syntax error for `yield` expressions ([#18756](https://github.com/astral-sh/ruff/pull/18756))
|
||||
- \[`refurb`\] Mark `FURB129` autofix as unsafe if there's comments in the `readlines` call ([#18858](https://github.com/astral-sh/ruff/pull/18858))
|
||||
- \[`ruff`\] Fix false positives and negatives in `RUF010` ([#18690](https://github.com/astral-sh/ruff/pull/18690))
|
||||
- Fix casing of `analyze.direction` variant names ([#18892](https://github.com/astral-sh/ruff/pull/18892))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- Fix f-string interpolation escaping in generated fixes ([#18882](https://github.com/astral-sh/ruff/pull/18882))
|
||||
- \[`flake8-return`\] Mark `RET501` fix unsafe if comments are inside ([#18780](https://github.com/astral-sh/ruff/pull/18780))
|
||||
- \[`flake8-async`\] Fix detection for large integer sleep durations in `ASYNC116` rule ([#18767](https://github.com/astral-sh/ruff/pull/18767))
|
||||
- \[`flake8-async`\] Mark autofix for `ASYNC115` as unsafe if the call expression contains comments ([#18753](https://github.com/astral-sh/ruff/pull/18753))
|
||||
- \[`flake8-bugbear`\] Mark autofix for `B004` as unsafe if the `hasattr` call expr contains comments ([#18755](https://github.com/astral-sh/ruff/pull/18755))
|
||||
- \[`flake8-comprehension`\] Mark autofix for `C420` as unsafe if there's comments inside the dict comprehension ([#18768](https://github.com/astral-sh/ruff/pull/18768))
|
||||
- \[`flake8-comprehensions`\] Handle template strings for comprehension fixes ([#18710](https://github.com/astral-sh/ruff/pull/18710))
|
||||
- \[`flake8-future-annotations`\] Add autofix (`FA100`) ([#18903](https://github.com/astral-sh/ruff/pull/18903))
|
||||
- \[`pyflakes`\] Mark `F504`/`F522`/`F523` autofix as unsafe if there's a call with side effect ([#18839](https://github.com/astral-sh/ruff/pull/18839))
|
||||
- \[`pylint`\] Allow fix with comments and document performance implications (`PLW3301`) ([#18936](https://github.com/astral-sh/ruff/pull/18936))
|
||||
- \[`pylint`\] Detect more exotic `NaN` literals in `PLW0177` ([#18630](https://github.com/astral-sh/ruff/pull/18630))
|
||||
- \[`pylint`\] Fix `PLC1802` autofix creating a syntax error and mark autofix as unsafe if there's comments in the `len` call ([#18836](https://github.com/astral-sh/ruff/pull/18836))
|
||||
- \[`pyupgrade`\] Extend version detection to include `sys.version_info.major` (`UP036`) ([#18633](https://github.com/astral-sh/ruff/pull/18633))
|
||||
- \[`ruff`\] Add lint rule `RUF064` for calling `chmod` with non-octal integers ([#18541](https://github.com/astral-sh/ruff/pull/18541))
|
||||
- \[`ruff`\] Added `cls.__dict__.get('__annotations__')` check (`RUF063`) ([#18233](https://github.com/astral-sh/ruff/pull/18233))
|
||||
- \[`ruff`\] Frozen `dataclass` default should be valid (`RUF009`) ([#18735](https://github.com/astral-sh/ruff/pull/18735))
|
||||
|
||||
### Server
|
||||
|
||||
- Consider virtual path for various server actions ([#18910](https://github.com/astral-sh/ruff/pull/18910))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add fix safety sections ([#18940](https://github.com/astral-sh/ruff/pull/18940),[#18841](https://github.com/astral-sh/ruff/pull/18841),[#18802](https://github.com/astral-sh/ruff/pull/18802),[#18837](https://github.com/astral-sh/ruff/pull/18837),[#18800](https://github.com/astral-sh/ruff/pull/18800),[#18415](https://github.com/astral-sh/ruff/pull/18415),[#18853](https://github.com/astral-sh/ruff/pull/18853),[#18842](https://github.com/astral-sh/ruff/pull/18842))
|
||||
- Use updated pre-commit id ([#18718](https://github.com/astral-sh/ruff/pull/18718))
|
||||
- \[`perflint`\] Small docs improvement to `PERF401` ([#18786](https://github.com/astral-sh/ruff/pull/18786))
|
||||
- \[`pyupgrade`\]: Use `super()`, not `__super__` in error messages (`UP008`) ([#18743](https://github.com/astral-sh/ruff/pull/18743))
|
||||
- \[`flake8-pie`\] Small docs fix to `PIE794` ([#18829](https://github.com/astral-sh/ruff/pull/18829))
|
||||
- \[`flake8-pyi`\] Correct `collections-named-tuple` example to use PascalCase assignment ([#16884](https://github.com/astral-sh/ruff/pull/16884))
|
||||
- \[`flake8-pie`\] Add note on type checking benefits to `unnecessary-dict-kwargs` (`PIE804`) ([#18666](https://github.com/astral-sh/ruff/pull/18666))
|
||||
- \[`pycodestyle`\] Clarify PEP 8 relationship to `whitespace-around-operator` rules ([#18870](https://github.com/astral-sh/ruff/pull/18870))
|
||||
|
||||
### Other changes
|
||||
|
||||
- Disallow newlines in format specifiers of single quoted f- or t-strings ([#18708](https://github.com/astral-sh/ruff/pull/18708))
|
||||
- \[`flake8-logging`\] Add fix safety section to `LOG002` ([#18840](https://github.com/astral-sh/ruff/pull/18840))
|
||||
- \[`pyupgrade`\] Add fix safety section to `UP010` ([#18838](https://github.com/astral-sh/ruff/pull/18838))
|
||||
|
||||
## 0.12.2
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-pyi`\] Expand `Optional[A]` to `A | None` (`PYI016`) ([#18572](https://github.com/astral-sh/ruff/pull/18572))
|
||||
- \[`pyupgrade`\] Mark `UP008` fix safe if no comments are in range ([#18683](https://github.com/astral-sh/ruff/pull/18683))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`flake8-comprehensions`\] Fix `C420` to prepend whitespace when needed ([#18616](https://github.com/astral-sh/ruff/pull/18616))
|
||||
- \[`perflint`\] Fix `PERF403` panic on attribute or subscription loop variable ([#19042](https://github.com/astral-sh/ruff/pull/19042))
|
||||
- \[`pydocstyle`\] Fix `D413` infinite loop for parenthesized docstring ([#18930](https://github.com/astral-sh/ruff/pull/18930))
|
||||
- \[`pylint`\] Fix `PLW0108` autofix introducing a syntax error when the lambda's body contains an assignment expression ([#18678](https://github.com/astral-sh/ruff/pull/18678))
|
||||
- \[`refurb`\] Fix false positive on empty tuples (`FURB168`) ([#19058](https://github.com/astral-sh/ruff/pull/19058))
|
||||
- \[`ruff`\] Allow more `field` calls from `attrs` (`RUF009`) ([#19021](https://github.com/astral-sh/ruff/pull/19021))
|
||||
- \[`ruff`\] Fix syntax error introduced for an empty string followed by a u-prefixed string (`UP025`) ([#18899](https://github.com/astral-sh/ruff/pull/18899))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-executable`\] Allow `uvx` in shebang line (`EXE003`) ([#18967](https://github.com/astral-sh/ruff/pull/18967))
|
||||
- \[`pandas`\] Avoid flagging `PD002` if `pandas` is not imported ([#18963](https://github.com/astral-sh/ruff/pull/18963))
|
||||
- \[`pyupgrade`\] Avoid PEP-604 unions with `typing.NamedTuple` (`UP007`, `UP045`) ([#18682](https://github.com/astral-sh/ruff/pull/18682))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Document link between `import-outside-top-level (PLC0415)` and `lint.flake8-tidy-imports.banned-module-level-imports` ([#18733](https://github.com/astral-sh/ruff/pull/18733))
|
||||
- Fix description of the `format.skip-magic-trailing-comma` example ([#19095](https://github.com/astral-sh/ruff/pull/19095))
|
||||
- \[`airflow`\] Make `AIR302` example error out-of-the-box ([#18988](https://github.com/astral-sh/ruff/pull/18988))
|
||||
- \[`airflow`\] Make `AIR312` example error out-of-the-box ([#18989](https://github.com/astral-sh/ruff/pull/18989))
|
||||
- \[`flake8-annotations`\] Make `ANN401` example error out-of-the-box ([#18974](https://github.com/astral-sh/ruff/pull/18974))
|
||||
- \[`flake8-async`\] Make `ASYNC100` example error out-of-the-box ([#18993](https://github.com/astral-sh/ruff/pull/18993))
|
||||
- \[`flake8-async`\] Make `ASYNC105` example error out-of-the-box ([#19002](https://github.com/astral-sh/ruff/pull/19002))
|
||||
- \[`flake8-async`\] Make `ASYNC110` example error out-of-the-box ([#18975](https://github.com/astral-sh/ruff/pull/18975))
|
||||
- \[`flake8-async`\] Make `ASYNC210` example error out-of-the-box ([#18977](https://github.com/astral-sh/ruff/pull/18977))
|
||||
- \[`flake8-async`\] Make `ASYNC220`, `ASYNC221`, and `ASYNC222` examples error out-of-the-box ([#18978](https://github.com/astral-sh/ruff/pull/18978))
|
||||
- \[`flake8-async`\] Make `ASYNC251` example error out-of-the-box ([#18990](https://github.com/astral-sh/ruff/pull/18990))
|
||||
- \[`flake8-bandit`\] Make `S201` example error out-of-the-box ([#19017](https://github.com/astral-sh/ruff/pull/19017))
|
||||
- \[`flake8-bandit`\] Make `S604` and `S609` examples error out-of-the-box ([#19049](https://github.com/astral-sh/ruff/pull/19049))
|
||||
- \[`flake8-bugbear`\] Make `B028` example error out-of-the-box ([#19054](https://github.com/astral-sh/ruff/pull/19054))
|
||||
- \[`flake8-bugbear`\] Make `B911` example error out-of-the-box ([#19051](https://github.com/astral-sh/ruff/pull/19051))
|
||||
- \[`flake8-datetimez`\] Make `DTZ011` example error out-of-the-box ([#19055](https://github.com/astral-sh/ruff/pull/19055))
|
||||
- \[`flake8-datetimez`\] Make `DTZ901` example error out-of-the-box ([#19056](https://github.com/astral-sh/ruff/pull/19056))
|
||||
- \[`flake8-pyi`\] Make `PYI032` example error out-of-the-box ([#19061](https://github.com/astral-sh/ruff/pull/19061))
|
||||
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI014`, `PYI015`) ([#19097](https://github.com/astral-sh/ruff/pull/19097))
|
||||
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI042`) ([#19101](https://github.com/astral-sh/ruff/pull/19101))
|
||||
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI059`) ([#19080](https://github.com/astral-sh/ruff/pull/19080))
|
||||
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI062`) ([#19079](https://github.com/astral-sh/ruff/pull/19079))
|
||||
- \[`flake8-pytest-style`\] Make example error out-of-the-box (`PT023`) ([#19104](https://github.com/astral-sh/ruff/pull/19104))
|
||||
- \[`flake8-pytest-style`\] Make example error out-of-the-box (`PT030`) ([#19105](https://github.com/astral-sh/ruff/pull/19105))
|
||||
- \[`flake8-quotes`\] Make example error out-of-the-box (`Q003`) ([#19106](https://github.com/astral-sh/ruff/pull/19106))
|
||||
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM110`) ([#19113](https://github.com/astral-sh/ruff/pull/19113))
|
||||
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM113`) ([#19109](https://github.com/astral-sh/ruff/pull/19109))
|
||||
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM401`) ([#19110](https://github.com/astral-sh/ruff/pull/19110))
|
||||
- \[`pyflakes`\] Fix backslash in docs (`F621`) ([#19098](https://github.com/astral-sh/ruff/pull/19098))
|
||||
- \[`pylint`\] Fix `PLC0415` example ([#18970](https://github.com/astral-sh/ruff/pull/18970))
|
||||
|
||||
## 0.12.3
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-bugbear`\] Support non-context-manager calls in `B017` ([#19063](https://github.com/astral-sh/ruff/pull/19063))
|
||||
- \[`flake8-use-pathlib`\] Add autofixes for `PTH100`, `PTH106`, `PTH107`, `PTH108`, `PTH110`, `PTH111`, `PTH112`, `PTH113`, `PTH114`, `PTH115`, `PTH117`, `PTH119`, `PTH120` ([#19213](https://github.com/astral-sh/ruff/pull/19213))
|
||||
- \[`flake8-use-pathlib`\] Add autofixes for `PTH203`, `PTH204`, `PTH205` ([#18922](https://github.com/astral-sh/ruff/pull/18922))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`flake8-return`\] Fix false-positive for variables used inside nested functions in `RET504` ([#18433](https://github.com/astral-sh/ruff/pull/18433))
|
||||
- Treat form feed as valid whitespace before a line continuation ([#19220](https://github.com/astral-sh/ruff/pull/19220))
|
||||
- \[`flake8-type-checking`\] Fix syntax error introduced by fix (`TC008`) ([#19150](https://github.com/astral-sh/ruff/pull/19150))
|
||||
- \[`pyupgrade`\] Keyword arguments in `super` should suppress the `UP008` fix ([#19131](https://github.com/astral-sh/ruff/pull/19131))
|
||||
|
||||
### Documentation
|
||||
|
||||
- \[`flake8-pyi`\] Make example error out-of-the-box (`PYI007`, `PYI008`) ([#19103](https://github.com/astral-sh/ruff/pull/19103))
|
||||
- \[`flake8-simplify`\] Make example error out-of-the-box (`SIM116`) ([#19111](https://github.com/astral-sh/ruff/pull/19111))
|
||||
- \[`flake8-type-checking`\] Make example error out-of-the-box (`TC001`) ([#19151](https://github.com/astral-sh/ruff/pull/19151))
|
||||
- \[`flake8-use-pathlib`\] Make example error out-of-the-box (`PTH210`) ([#19189](https://github.com/astral-sh/ruff/pull/19189))
|
||||
- \[`pycodestyle`\] Make example error out-of-the-box (`E272`) ([#19191](https://github.com/astral-sh/ruff/pull/19191))
|
||||
- \[`pycodestyle`\] Make example not raise unnecessary `SyntaxError` (`E114`) ([#19190](https://github.com/astral-sh/ruff/pull/19190))
|
||||
- \[`pydoclint`\] Make example error out-of-the-box (`DOC501`) ([#19218](https://github.com/astral-sh/ruff/pull/19218))
|
||||
- \[`pylint`, `pyupgrade`\] Fix syntax errors in examples (`PLW1501`, `UP028`) ([#19127](https://github.com/astral-sh/ruff/pull/19127))
|
||||
- \[`pylint`\] Update `missing-maxsplit-arg` docs and error to suggest proper usage (`PLC0207`) ([#18949](https://github.com/astral-sh/ruff/pull/18949))
|
||||
- \[`flake8-bandit`\] Make example error out-of-the-box (`S412`) ([#19241](https://github.com/astral-sh/ruff/pull/19241))
|
||||
|
||||
## 0.12.4
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-type-checking`, `pyupgrade`, `ruff`\] Add `from __future__ import annotations` when it would allow new fixes (`TC001`, `TC002`, `TC003`, `UP037`, `RUF013`) ([#19100](https://github.com/astral-sh/ruff/pull/19100))
|
||||
- \[`flake8-use-pathlib`\] Add autofix for `PTH109` ([#19245](https://github.com/astral-sh/ruff/pull/19245))
|
||||
- \[`pylint`\] Detect indirect `pathlib.Path` usages for `unspecified-encoding` (`PLW1514`) ([#19304](https://github.com/astral-sh/ruff/pull/19304))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`flake8-bugbear`\] Fix `B017` false negatives for keyword exception arguments ([#19217](https://github.com/astral-sh/ruff/pull/19217))
|
||||
- \[`flake8-use-pathlib`\] Fix false negative on direct `Path()` instantiation (`PTH210`) ([#19388](https://github.com/astral-sh/ruff/pull/19388))
|
||||
- \[`flake8-django`\] Fix `DJ008` false positive for abstract models with type-annotated `abstract` field ([#19221](https://github.com/astral-sh/ruff/pull/19221))
|
||||
- \[`isort`\] Fix `I002` import insertion after docstring with multiple string statements ([#19222](https://github.com/astral-sh/ruff/pull/19222))
|
||||
- \[`isort`\] Treat form feed as valid whitespace before a semicolon ([#19343](https://github.com/astral-sh/ruff/pull/19343))
|
||||
- \[`pydoclint`\] Fix `SyntaxError` from fixes with line continuations (`D201`, `D202`) ([#19246](https://github.com/astral-sh/ruff/pull/19246))
|
||||
- \[`refurb`\] `FURB164` fix should validate arguments and should usually be marked unsafe ([#19136](https://github.com/astral-sh/ruff/pull/19136))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-use-pathlib`\] Skip single dots for `invalid-pathlib-with-suffix` (`PTH210`) on versions >= 3.14 ([#19331](https://github.com/astral-sh/ruff/pull/19331))
|
||||
- \[`pep8_naming`\] Avoid false positives on standard library functions with uppercase names (`N802`) ([#18907](https://github.com/astral-sh/ruff/pull/18907))
|
||||
- \[`pycodestyle`\] Handle brace escapes for t-strings in logical lines ([#19358](https://github.com/astral-sh/ruff/pull/19358))
|
||||
- \[`pylint`\] Extend invalid string character rules to include t-strings ([#19355](https://github.com/astral-sh/ruff/pull/19355))
|
||||
- \[`ruff`\] Allow `strict` kwarg when checking for `starmap-zip` (`RUF058`) in Python 3.14+ ([#19333](https://github.com/astral-sh/ruff/pull/19333))
|
||||
|
||||
### Documentation
|
||||
|
||||
- \[`flake8-type-checking`\] Make `TC010` docs example more realistic ([#19356](https://github.com/astral-sh/ruff/pull/19356))
|
||||
- Make more documentation examples error out-of-the-box ([#19288](https://github.com/astral-sh/ruff/pull/19288),[#19272](https://github.com/astral-sh/ruff/pull/19272),[#19291](https://github.com/astral-sh/ruff/pull/19291),[#19296](https://github.com/astral-sh/ruff/pull/19296),[#19292](https://github.com/astral-sh/ruff/pull/19292),[#19295](https://github.com/astral-sh/ruff/pull/19295),[#19297](https://github.com/astral-sh/ruff/pull/19297),[#19309](https://github.com/astral-sh/ruff/pull/19309))
|
||||
|
||||
## 0.12.5
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-use-pathlib`\] Add autofix for `PTH101`, `PTH104`, `PTH105`, `PTH121` ([#19404](https://github.com/astral-sh/ruff/pull/19404))
|
||||
- \[`ruff`\] Support byte strings (`RUF055`) ([#18926](https://github.com/astral-sh/ruff/pull/18926))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fix `unreachable` panic in parser ([#19183](https://github.com/astral-sh/ruff/pull/19183))
|
||||
- \[`flake8-pyi`\] Skip fix if all `Union` members are `None` (`PYI016`) ([#19416](https://github.com/astral-sh/ruff/pull/19416))
|
||||
- \[`perflint`\] Parenthesize generator expressions (`PERF401`) ([#19325](https://github.com/astral-sh/ruff/pull/19325))
|
||||
- \[`pylint`\] Handle empty comments after line continuation (`PLR2044`) ([#19405](https://github.com/astral-sh/ruff/pull/19405))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`pep8-naming`\] Fix `N802` false positives for `CGIHTTPRequestHandler` and `SimpleHTTPRequestHandler` ([#19432](https://github.com/astral-sh/ruff/pull/19432))
|
||||
|
||||
## 0.12.6
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-commas`\] Add support for trailing comma checks in type parameter lists (`COM812`, `COM819`) ([#19390](https://github.com/astral-sh/ruff/pull/19390))
|
||||
- \[`pylint`\] Implement auto-fix for `missing-maxsplit-arg` (`PLC0207`) ([#19387](https://github.com/astral-sh/ruff/pull/19387))
|
||||
- \[`ruff`\] Offer fixes for `RUF039` in more cases ([#19065](https://github.com/astral-sh/ruff/pull/19065))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Support `.pyi` files in ruff analyze graph ([#19611](https://github.com/astral-sh/ruff/pull/19611))
|
||||
- \[`flake8-pyi`\] Preserve inline comment in ellipsis removal (`PYI013`) ([#19399](https://github.com/astral-sh/ruff/pull/19399))
|
||||
- \[`perflint`\] Ignore rule if target is `global` or `nonlocal` (`PERF401`) ([#19539](https://github.com/astral-sh/ruff/pull/19539))
|
||||
- \[`pyupgrade`\] Fix `UP030` to avoid modifying double curly braces in format strings ([#19378](https://github.com/astral-sh/ruff/pull/19378))
|
||||
- \[`refurb`\] Ignore decorated functions for `FURB118` ([#19339](https://github.com/astral-sh/ruff/pull/19339))
|
||||
- \[`refurb`\] Mark `int` and `bool` cases for `Decimal.from_float` as safe fixes (`FURB164`) ([#19468](https://github.com/astral-sh/ruff/pull/19468))
|
||||
- \[`ruff`\] Fix `RUF033` for named default expressions ([#19115](https://github.com/astral-sh/ruff/pull/19115))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-blind-except`\] Change `BLE001` to permit `logging.critical(..., exc_info=True)` ([#19520](https://github.com/astral-sh/ruff/pull/19520))
|
||||
|
||||
### Performance
|
||||
|
||||
- Add support for specifying minimum dots in detected string imports ([#19538](https://github.com/astral-sh/ruff/pull/19538))
|
||||
|
||||
## 0.12.7
|
||||
|
||||
This is a follow-up release to 0.12.6. Because of an issue in the package metadata, 0.12.6 failed to publish fully to PyPI and has been yanked. Similarly, there is no GitHub release or Git tag for 0.12.6. The contents of the 0.12.7 release are identical to 0.12.6, except for the updated metadata.
|
||||
|
||||
## 0.12.8
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-use-pathlib`\] Expand `PTH201` to check all `PurePath` subclasses ([#19440](https://github.com/astral-sh/ruff/pull/19440))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`flake8-blind-except`\] Change `BLE001` to correctly parse exception tuples ([#19747](https://github.com/astral-sh/ruff/pull/19747))
|
||||
- \[`flake8-errmsg`\] Exclude `typing.cast` from `EM101` ([#19656](https://github.com/astral-sh/ruff/pull/19656))
|
||||
- \[`flake8-simplify`\] Fix raw string handling in `SIM905` for embedded quotes ([#19591](https://github.com/astral-sh/ruff/pull/19591))
|
||||
- \[`flake8-import-conventions`\] Avoid false positives for NFKC-normalized `__debug__` import aliases in `ICN001` ([#19411](https://github.com/astral-sh/ruff/pull/19411))
|
||||
- \[`isort`\] Fix syntax error after docstring ending with backslash (`I002`) ([#19505](https://github.com/astral-sh/ruff/pull/19505))
|
||||
- \[`pylint`\] Mark `PLC0207` fixes as unsafe when `*args` unpacking is present ([#19679](https://github.com/astral-sh/ruff/pull/19679))
|
||||
- \[`pyupgrade`\] Prevent infinite loop with `I002` (`UP010`, `UP035`) ([#19413](https://github.com/astral-sh/ruff/pull/19413))
|
||||
- \[`ruff`\] Parenthesize generator expressions in f-strings (`RUF010`) ([#19434](https://github.com/astral-sh/ruff/pull/19434))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`eradicate`\] Don't flag `pyrefly` pragmas as unused code (`ERA001`) ([#19731](https://github.com/astral-sh/ruff/pull/19731))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Replace "associative" with "commutative" in docs for `RUF036` ([#19706](https://github.com/astral-sh/ruff/pull/19706))
|
||||
- Fix copy and line separator colors in dark mode ([#19630](https://github.com/astral-sh/ruff/pull/19630))
|
||||
- Fix link to `typing` documentation ([#19648](https://github.com/astral-sh/ruff/pull/19648))
|
||||
- \[`refurb`\] Make more examples error out-of-the-box ([#19695](https://github.com/astral-sh/ruff/pull/19695),[#19673](https://github.com/astral-sh/ruff/pull/19673),[#19672](https://github.com/astral-sh/ruff/pull/19672))
|
||||
|
||||
### Other changes
|
||||
|
||||
- Include column numbers in GitLab output format ([#19708](https://github.com/astral-sh/ruff/pull/19708))
|
||||
- Always expand tabs to four spaces in diagnostics ([#19618](https://github.com/astral-sh/ruff/pull/19618))
|
||||
- Update pre-commit's `ruff` id ([#19654](https://github.com/astral-sh/ruff/pull/19654))
|
||||
|
||||
## 0.12.9
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`airflow`\] Add check for `airflow.secrets.cache.SecretCache` (`AIR301`) ([#17707](https://github.com/astral-sh/ruff/pull/17707))
|
||||
- \[`ruff`\] Offer a safe fix for multi-digit zeros (`RUF064`) ([#19847](https://github.com/astral-sh/ruff/pull/19847))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`flake8-blind-except`\] Fix `BLE001` false-positive on `raise ... from None` ([#19755](https://github.com/astral-sh/ruff/pull/19755))
|
||||
- \[`flake8-comprehensions`\] Fix false positive for `C420` with attribute, subscript, or slice assignment targets ([#19513](https://github.com/astral-sh/ruff/pull/19513))
|
||||
- \[`flake8-simplify`\] Fix handling of U+001C..U+001F whitespace (`SIM905`) ([#19849](https://github.com/astral-sh/ruff/pull/19849))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`pylint`\] Use lowercase hex characters to match the formatter (`PLE2513`) ([#19808](https://github.com/astral-sh/ruff/pull/19808))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Fix `lint.future-annotations` link ([#19876](https://github.com/astral-sh/ruff/pull/19876))
|
||||
|
||||
### Other changes
|
||||
|
||||
- Build `riscv64` binaries for release ([#19819](https://github.com/astral-sh/ruff/pull/19819))
|
||||
|
||||
- Add rule code to error description in GitLab output ([#19896](https://github.com/astral-sh/ruff/pull/19896))
|
||||
|
||||
- Improve rendering of the `full` output format ([#19415](https://github.com/astral-sh/ruff/pull/19415))
|
||||
|
||||
Below is an example diff for [`F401`](https://docs.astral.sh/ruff/rules/unused-import/):
|
||||
|
||||
```diff
|
||||
-unused.py:8:19: F401 [*] `pathlib` imported but unused
|
||||
+F401 [*] `pathlib` imported but unused
|
||||
+ --> unused.py:8:19
|
||||
|
|
||||
7 | # Unused, _not_ marked as required (due to the alias).
|
||||
8 | import pathlib as non_alias
|
||||
- | ^^^^^^^^^ F401
|
||||
+ | ^^^^^^^^^
|
||||
9 |
|
||||
10 | # Unused, marked as required.
|
||||
|
|
||||
- = help: Remove unused import: `pathlib`
|
||||
+help: Remove unused import: `pathlib`
|
||||
```
|
||||
|
||||
For now, the primary difference is the movement of the filename, line number, and column information to a second line in the header. This new representation will allow us to make further additions to Ruff's diagnostics, such as adding sub-diagnostics and multiple annotations to the same snippet.
|
||||
|
||||
## 0.12.10
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`flake8-simplify`\] Implement fix for `maxsplit` without separator (`SIM905`) ([#19851](https://github.com/astral-sh/ruff/pull/19851))
|
||||
- \[`flake8-use-pathlib`\] Add fixes for `PTH102` and `PTH103` ([#19514](https://github.com/astral-sh/ruff/pull/19514))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`isort`\] Handle multiple continuation lines after module docstring (`I002`) ([#19818](https://github.com/astral-sh/ruff/pull/19818))
|
||||
- \[`pyupgrade`\] Avoid reporting `__future__` features as unnecessary when they are used (`UP010`) ([#19769](https://github.com/astral-sh/ruff/pull/19769))
|
||||
- \[`pyupgrade`\] Handle nested `Optional`s (`UP045`) ([#19770](https://github.com/astral-sh/ruff/pull/19770))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`pycodestyle`\] Make `E731` fix unsafe instead of display-only for class assignments ([#19700](https://github.com/astral-sh/ruff/pull/19700))
|
||||
- \[`pyflakes`\] Add secondary annotation showing previous definition (`F811`) ([#19900](https://github.com/astral-sh/ruff/pull/19900))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Fix description of global config file discovery strategy ([#19188](https://github.com/astral-sh/ruff/pull/19188))
|
||||
- Update outdated links to <https://typing.python.org/en/latest/source/stubs.html> ([#19992](https://github.com/astral-sh/ruff/pull/19992))
|
||||
- \[`flake8-annotations`\] Remove unused import in example (`ANN401`) ([#20000](https://github.com/astral-sh/ruff/pull/20000))
|
||||
|
||||
## 0.12.11
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`airflow`\] Extend `AIR311` and `AIR312` rules ([#20082](https://github.com/astral-sh/ruff/pull/20082))
|
||||
- \[`airflow`\] Replace wrong path `airflow.io.storage` with `airflow.io.store` (`AIR311`) ([#20081](https://github.com/astral-sh/ruff/pull/20081))
|
||||
- \[`flake8-async`\] Implement `blocking-http-call-httpx-in-async-function` (`ASYNC212`) ([#20091](https://github.com/astral-sh/ruff/pull/20091))
|
||||
- \[`flake8-logging-format`\] Add auto-fix for f-string logging calls (`G004`) ([#19303](https://github.com/astral-sh/ruff/pull/19303))
|
||||
- \[`flake8-use-pathlib`\] Add autofix for `PTH211` ([#20009](https://github.com/astral-sh/ruff/pull/20009))
|
||||
- \[`flake8-use-pathlib`\] Make `PTH100` fix unsafe because it can change behavior ([#20100](https://github.com/astral-sh/ruff/pull/20100))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- \[`pyflakes`, `pylint`\] Fix false positives caused by `__class__` cell handling (`F841`, `PLE0117`) ([#20048](https://github.com/astral-sh/ruff/pull/20048))
|
||||
- \[`pyflakes`\] Fix `allowed-unused-imports` matching for top-level modules (`F401`) ([#20115](https://github.com/astral-sh/ruff/pull/20115))
|
||||
- \[`ruff`\] Fix false positive for t-strings in `default-factory-kwarg` (`RUF026`) ([#20032](https://github.com/astral-sh/ruff/pull/20032))
|
||||
- \[`ruff`\] Preserve relative whitespace in multi-line expressions (`RUF033`) ([#19647](https://github.com/astral-sh/ruff/pull/19647))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`ruff`\] Handle empty t-strings in `unnecessary-empty-iterable-within-deque-call` (`RUF037`) ([#20045](https://github.com/astral-sh/ruff/pull/20045))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Fix incorrect `D413` links in docstrings convention FAQ ([#20089](https://github.com/astral-sh/ruff/pull/20089))
|
||||
- \[`flake8-use-pathlib`\] Update links to the table showing the correspondence between `os` and `pathlib` ([#20103](https://github.com/astral-sh/ruff/pull/20103))
|
||||
|
||||
## 0.12.12
|
||||
|
||||
### Preview features
|
||||
|
||||
- Show fixes by default ([#19919](https://github.com/astral-sh/ruff/pull/19919))
|
||||
- \[`airflow`\] Convert `DatasetOrTimeSchedule(datasets=...)` to `AssetOrTimeSchedule(assets=...)` (`AIR311`) ([#20202](https://github.com/astral-sh/ruff/pull/20202))
|
||||
- \[`airflow`\] Improve the `AIR002` error message ([#20173](https://github.com/astral-sh/ruff/pull/20173))
|
||||
- \[`airflow`\] Move `airflow.operators.postgres_operator.Mapping` from `AIR302` to `AIR301` ([#20172](https://github.com/astral-sh/ruff/pull/20172))
|
||||
- \[`flake8-async`\] Implement `blocking-input` rule (`ASYNC250`) ([#20122](https://github.com/astral-sh/ruff/pull/20122))
|
||||
- \[`flake8-use-pathlib`\] Make `PTH119` and `PTH120` fixes unsafe because they can change behavior ([#20118](https://github.com/astral-sh/ruff/pull/20118))
|
||||
- \[`pylint`\] Add U+061C to `PLE2502` ([#20106](https://github.com/astral-sh/ruff/pull/20106))
|
||||
- \[`ruff`\] Fix false negative for empty f-strings in `deque` calls (`RUF037`) ([#20109](https://github.com/astral-sh/ruff/pull/20109))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Less confidently mark f-strings as empty when inferring truthiness ([#20152](https://github.com/astral-sh/ruff/pull/20152))
|
||||
- \[`fastapi`\] Fix false positive for paths with spaces around parameters (`FAST003`) ([#20077](https://github.com/astral-sh/ruff/pull/20077))
|
||||
- \[`flake8-comprehensions`\] Skip `C417` when lambda contains `yield`/`yield from` ([#20201](https://github.com/astral-sh/ruff/pull/20201))
|
||||
- \[`perflint`\] Handle tuples in dictionary comprehensions (`PERF403`) ([#19934](https://github.com/astral-sh/ruff/pull/19934))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`pycodestyle`\] Preserve return type annotation for `ParamSpec` (`E731`) ([#20108](https://github.com/astral-sh/ruff/pull/20108))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add fix safety sections to docs ([#17490](https://github.com/astral-sh/ruff/pull/17490),[#17499](https://github.com/astral-sh/ruff/pull/17499))
|
||||
|
||||
[`boolean-type-hint-positional-argument`]: https://docs.astral.sh/ruff/rules/boolean-type-hint-positional-argument
|
||||
[`collection-literal-concatenation`]: https://docs.astral.sh/ruff/rules/collection-literal-concatenation
|
||||
[`if-else-block-instead-of-if-exp`]: https://docs.astral.sh/ruff/rules/if-else-block-instead-of-if-exp
|
||||
[`non-pep604-annotation-optional`]: https://docs.astral.sh/ruff/rules/non-pep604-annotation-optional
|
||||
[`non-pep604-annotation-union`]: https://docs.astral.sh/ruff/rules/non-pep604-annotation-union
|
||||
[`readlines-in-for`]: https://docs.astral.sh/ruff/rules/readlines-in-for
|
||||
[`subprocess-without-shell-equals-true`]: https://docs.astral.sh/ruff/rules/subprocess-without-shell-equals-true
|
||||
[`unused-noqa`]: https://docs.astral.sh/ruff/rules/unused-noqa
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.12.11"
|
||||
version = "0.13.0"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -1489,6 +1489,8 @@ fn deprecated_direct() {
|
||||
|
||||
#[test]
|
||||
fn deprecated_multiple_direct() {
|
||||
// Multiple deprecated rules selected by exact code should be included
|
||||
// but a warning should be displayed
|
||||
let mut cmd = RuffCheck::default()
|
||||
.args(["--select", "RUF920", "--select", "RUF921"])
|
||||
.build();
|
||||
@@ -1516,16 +1518,10 @@ fn deprecated_indirect() {
|
||||
// since it is not a "direct" selection
|
||||
let mut cmd = RuffCheck::default().args(["--select", "RUF92"]).build();
|
||||
assert_cmd_snapshot!(cmd, @r"
|
||||
success: false
|
||||
exit_code: 1
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
RUF920 Hey this is a deprecated test rule.
|
||||
--> -:1:1
|
||||
|
||||
RUF921 Hey this is another deprecated test rule.
|
||||
--> -:1:1
|
||||
|
||||
Found 2 errors.
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
");
|
||||
@@ -2155,16 +2151,10 @@ extend-safe-fixes = ["RUF9"]
|
||||
RUF903 Hey this is a stable test rule with a display only fix.
|
||||
--> -:1:1
|
||||
|
||||
RUF920 Hey this is a deprecated test rule.
|
||||
--> -:1:1
|
||||
|
||||
RUF921 Hey this is another deprecated test rule.
|
||||
--> -:1:1
|
||||
|
||||
RUF950 Hey this is a test rule that was redirected from another.
|
||||
--> -:1:1
|
||||
|
||||
Found 7 errors.
|
||||
Found 5 errors.
|
||||
[*] 1 fixable with the `--fix` option (1 hidden fix can be enabled with the `--unsafe-fixes` option).
|
||||
|
||||
----- stderr -----
|
||||
|
||||
@@ -5780,28 +5780,6 @@ match 42: # invalid-syntax
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn future_annotations_preview_warning() {
|
||||
assert_cmd_snapshot!(
|
||||
Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(["--config", "lint.future-annotations = true"])
|
||||
.args(["--select", "F"])
|
||||
.arg("--no-preview")
|
||||
.arg("-")
|
||||
.pass_stdin("1"),
|
||||
@r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
warning: The `lint.future-annotations` setting will have no effect because `preview` is disabled
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn up045_nested_optional_flatten_all() {
|
||||
let contents = "\
|
||||
|
||||
@@ -55,6 +55,10 @@ either a redundant alias or, if already present in the file, an `__all__` entry.
|
||||
to remove third-party and standard library imports -- the fix is unsafe because the module's
|
||||
interface changes.
|
||||
|
||||
See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc)
|
||||
for more details on how Ruff
|
||||
determines whether an import is first or third-party.
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
@@ -83,11 +87,6 @@ else:
|
||||
print("numpy is not installed")
|
||||
```
|
||||
|
||||
## Preview
|
||||
When [preview](https://docs.astral.sh/ruff/preview/) is enabled,
|
||||
the criterion for determining whether an import is first-party
|
||||
is stricter, which could affect the suggested fix. See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) for more details.
|
||||
|
||||
## Options
|
||||
- `lint.ignore-init-module-imports`
|
||||
- `lint.pyflakes.allowed-unused-imports`
|
||||
|
||||
@@ -95,7 +95,7 @@ exit_code: 1
|
||||
"rules": [
|
||||
{
|
||||
"fullDescription": {
|
||||
"text": "## What it does\nChecks for unused imports.\n\n## Why is this bad?\nUnused imports add a performance overhead at runtime, and risk creating\nimport cycles. They also increase the cognitive load of reading the code.\n\nIf an import statement is used to check for the availability or existence\nof a module, consider using `importlib.util.find_spec` instead.\n\nIf an import statement is used to re-export a symbol as part of a module's\npublic interface, consider using a \"redundant\" import alias, which\ninstructs Ruff (and other tools) to respect the re-export, and avoid\nmarking it as unused, as in:\n\n```python\nfrom module import member as member\n```\n\nAlternatively, you can use `__all__` to declare a symbol as part of the module's\ninterface, as in:\n\n```python\n# __init__.py\nimport some_module\n\n__all__ = [\"some_module\"]\n```\n\n## Fix safety\n\nFixes to remove unused imports are safe, except in `__init__.py` files.\n\nApplying fixes to `__init__.py` files is currently in preview. The fix offered depends on the\ntype of the unused import. Ruff will suggest a safe fix to export first-party imports with\neither a redundant alias or, if already present in the file, an `__all__` entry. If multiple\n`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix\nto remove third-party and standard library imports -- the fix is unsafe because the module's\ninterface changes.\n\n## Example\n\n```python\nimport numpy as np # unused import\n\n\ndef area(radius):\n return 3.14 * radius**2\n```\n\nUse instead:\n\n```python\ndef area(radius):\n return 3.14 * radius**2\n```\n\nTo check the availability of a module, use `importlib.util.find_spec`:\n\n```python\nfrom importlib.util import find_spec\n\nif find_spec(\"numpy\") is not None:\n print(\"numpy is installed\")\nelse:\n print(\"numpy is not installed\")\n```\n\n## Preview\nWhen [preview](https://docs.astral.sh/ruff/preview/) is enabled,\nthe criterion for determining whether an import is first-party\nis stricter, which could affect the suggested fix. See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) for more details.\n\n## Options\n- `lint.ignore-init-module-imports`\n- `lint.pyflakes.allowed-unused-imports`\n\n## References\n- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)\n- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)\n- [Typing documentation: interface conventions](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)\n"
|
||||
"text": "## What it does\nChecks for unused imports.\n\n## Why is this bad?\nUnused imports add a performance overhead at runtime, and risk creating\nimport cycles. They also increase the cognitive load of reading the code.\n\nIf an import statement is used to check for the availability or existence\nof a module, consider using `importlib.util.find_spec` instead.\n\nIf an import statement is used to re-export a symbol as part of a module's\npublic interface, consider using a \"redundant\" import alias, which\ninstructs Ruff (and other tools) to respect the re-export, and avoid\nmarking it as unused, as in:\n\n```python\nfrom module import member as member\n```\n\nAlternatively, you can use `__all__` to declare a symbol as part of the module's\ninterface, as in:\n\n```python\n# __init__.py\nimport some_module\n\n__all__ = [\"some_module\"]\n```\n\n## Fix safety\n\nFixes to remove unused imports are safe, except in `__init__.py` files.\n\nApplying fixes to `__init__.py` files is currently in preview. The fix offered depends on the\ntype of the unused import. Ruff will suggest a safe fix to export first-party imports with\neither a redundant alias or, if already present in the file, an `__all__` entry. If multiple\n`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix\nto remove third-party and standard library imports -- the fix is unsafe because the module's\ninterface changes.\n\nSee [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc)\nfor more details on how Ruff\ndetermines whether an import is first or third-party.\n\n## Example\n\n```python\nimport numpy as np # unused import\n\n\ndef area(radius):\n return 3.14 * radius**2\n```\n\nUse instead:\n\n```python\ndef area(radius):\n return 3.14 * radius**2\n```\n\nTo check the availability of a module, use `importlib.util.find_spec`:\n\n```python\nfrom importlib.util import find_spec\n\nif find_spec(\"numpy\") is not None:\n print(\"numpy is installed\")\nelse:\n print(\"numpy is not installed\")\n```\n\n## Options\n- `lint.ignore-init-module-imports`\n- `lint.pyflakes.allowed-unused-imports`\n\n## References\n- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)\n- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)\n- [Typing documentation: interface conventions](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)\n"
|
||||
},
|
||||
"help": {
|
||||
"text": "`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability"
|
||||
|
||||
@@ -454,24 +454,26 @@ impl Diagnostic {
|
||||
|
||||
/// Computes the start source location for the message.
|
||||
///
|
||||
/// Panics if the diagnostic has no primary span, if its file is not a `SourceFile`, or if the
|
||||
/// span has no range.
|
||||
pub fn expect_ruff_start_location(&self) -> LineColumn {
|
||||
self.expect_primary_span()
|
||||
.expect_ruff_file()
|
||||
.to_source_code()
|
||||
.line_column(self.expect_range().start())
|
||||
/// Returns None if the diagnostic has no primary span, if its file is not a `SourceFile`,
|
||||
/// or if the span has no range.
|
||||
pub fn ruff_start_location(&self) -> Option<LineColumn> {
|
||||
Some(
|
||||
self.ruff_source_file()?
|
||||
.to_source_code()
|
||||
.line_column(self.range()?.start()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Computes the end source location for the message.
|
||||
///
|
||||
/// Panics if the diagnostic has no primary span, if its file is not a `SourceFile`, or if the
|
||||
/// span has no range.
|
||||
pub fn expect_ruff_end_location(&self) -> LineColumn {
|
||||
self.expect_primary_span()
|
||||
.expect_ruff_file()
|
||||
.to_source_code()
|
||||
.line_column(self.expect_range().end())
|
||||
/// Returns None if the diagnostic has no primary span, if its file is not a `SourceFile`,
|
||||
/// or if the span has no range.
|
||||
pub fn ruff_end_location(&self) -> Option<LineColumn> {
|
||||
Some(
|
||||
self.ruff_source_file()?
|
||||
.to_source_code()
|
||||
.line_column(self.range()?.end()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the [`SourceFile`] which the message belongs to.
|
||||
@@ -501,13 +503,18 @@ impl Diagnostic {
|
||||
|
||||
/// Returns the ordering of diagnostics based on the start of their ranges, if they have any.
|
||||
///
|
||||
/// Panics if either diagnostic has no primary span, if the span has no range, or if its file is
|
||||
/// not a `SourceFile`.
|
||||
/// Panics if either diagnostic has no primary span, or if its file is not a `SourceFile`.
|
||||
pub fn ruff_start_ordering(&self, other: &Self) -> std::cmp::Ordering {
|
||||
(self.expect_ruff_source_file(), self.expect_range().start()).cmp(&(
|
||||
let a = (
|
||||
self.expect_ruff_source_file(),
|
||||
self.range().map(|r| r.start()),
|
||||
);
|
||||
let b = (
|
||||
other.expect_ruff_source_file(),
|
||||
other.expect_range().start(),
|
||||
))
|
||||
other.range().map(|r| r.start()),
|
||||
);
|
||||
|
||||
a.cmp(&b)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1444,7 +1451,7 @@ pub enum DiagnosticFormat {
|
||||
Junit,
|
||||
/// Print diagnostics in the JSON format used by GitLab [Code Quality] reports.
|
||||
///
|
||||
/// [Code Quality]: https://docs.gitlab.com/ee/ci/testing/code_quality.html#implement-a-custom-tool
|
||||
/// [Code Quality]: https://docs.gitlab.com/ci/testing/code_quality/#code-quality-report-format
|
||||
#[cfg(feature = "serde")]
|
||||
Gitlab,
|
||||
}
|
||||
|
||||
@@ -81,14 +81,19 @@ impl IndentStyle {
|
||||
pub const fn is_space(&self) -> bool {
|
||||
matches!(self, IndentStyle::Space)
|
||||
}
|
||||
|
||||
/// Returns the string representation of the indent style.
|
||||
pub const fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
IndentStyle::Tab => "tab",
|
||||
IndentStyle::Space => "space",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for IndentStyle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
IndentStyle::Tab => std::write!(f, "tab"),
|
||||
IndentStyle::Space => std::write!(f, "space"),
|
||||
}
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -139,4 +139,16 @@ impl LineEnding {
|
||||
LineEnding::CarriageReturn => "\r",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the string used to configure this line ending.
|
||||
///
|
||||
/// See [`LineEnding::as_str`] for the actual string representation of the line ending.
|
||||
#[inline]
|
||||
pub const fn as_setting_str(&self) -> &'static str {
|
||||
match self {
|
||||
LineEnding::LineFeed => "lf",
|
||||
LineEnding::CarriageReturnLineFeed => "crlf",
|
||||
LineEnding::CarriageReturn => "cr",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.12.11"
|
||||
version = "0.13.0"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -12,6 +12,7 @@ from airflow import (
|
||||
from airflow.api_connexion.security import requires_access
|
||||
from airflow.contrib.aws_athena_hook import AWSAthenaHook
|
||||
from airflow.datasets import DatasetAliasEvent
|
||||
from airflow.operators.postgres_operator import Mapping
|
||||
from airflow.operators.subdag import SubDagOperator
|
||||
from airflow.secrets.cache import SecretCache
|
||||
from airflow.secrets.local_filesystem import LocalFilesystemBackend
|
||||
@@ -52,6 +53,8 @@ DatasetAliasEvent()
|
||||
# airflow.operators.subdag.*
|
||||
SubDagOperator()
|
||||
|
||||
# airflow.operators.postgres_operator
|
||||
Mapping()
|
||||
|
||||
# airflow.secrets
|
||||
# get_connection
|
||||
|
||||
@@ -70,7 +70,7 @@ from airflow.timetables.datasets import DatasetOrTimeSchedule
|
||||
from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
|
||||
# airflow.timetables.datasets
|
||||
DatasetOrTimeSchedule()
|
||||
DatasetOrTimeSchedule(datasets=[])
|
||||
|
||||
# airflow.utils.dag_parsing_context
|
||||
get_parsing_context()
|
||||
|
||||
@@ -75,3 +75,7 @@ list(map(lambda x, y: x, [(1, 2), (3, 4)]))
|
||||
_ = t"{set(map(lambda x: x % 2 == 0, nums))}"
|
||||
_ = t"{dict(map(lambda v: (v, v**2), nums))}"
|
||||
|
||||
|
||||
# See https://github.com/astral-sh/ruff/issues/20198
|
||||
# No error: lambda contains `yield`, so map() should not be rewritten
|
||||
map(lambda x: (yield x), [1, 2, 3])
|
||||
|
||||
@@ -141,3 +141,133 @@ class ExampleWithKeywords:
|
||||
|
||||
def method3(self):
|
||||
super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords
|
||||
|
||||
# See: https://github.com/astral-sh/ruff/issues/19357
|
||||
# Must be detected
|
||||
class ParentD:
|
||||
def f(self):
|
||||
print("D")
|
||||
|
||||
class ChildD1(ParentD):
|
||||
def f(self):
|
||||
if False: __class__ # Python injects __class__ into scope
|
||||
builtins.super(ChildD1, self).f()
|
||||
|
||||
class ChildD2(ParentD):
|
||||
def f(self):
|
||||
if False: super # Python injects __class__ into scope
|
||||
builtins.super(ChildD2, self).f()
|
||||
|
||||
class ChildD3(ParentD):
|
||||
def f(self):
|
||||
builtins.super(ChildD3, self).f()
|
||||
super # Python injects __class__ into scope
|
||||
|
||||
import builtins as builtins_alias
|
||||
class ChildD4(ParentD):
|
||||
def f(self):
|
||||
builtins_alias.super(ChildD4, self).f()
|
||||
super # Python injects __class__ into scope
|
||||
|
||||
class ChildD5(ParentD):
|
||||
def f(self):
|
||||
super = 1
|
||||
super # Python injects __class__ into scope
|
||||
builtins.super(ChildD5, self).f()
|
||||
|
||||
class ChildD6(ParentD):
|
||||
def f(self):
|
||||
super: "Any"
|
||||
__class__ # Python injects __class__ into scope
|
||||
builtins.super(ChildD6, self).f()
|
||||
|
||||
class ChildD7(ParentD):
|
||||
def f(self):
|
||||
def x():
|
||||
__class__ # Python injects __class__ into scope
|
||||
builtins.super(ChildD7, self).f()
|
||||
|
||||
class ChildD8(ParentD):
|
||||
def f(self):
|
||||
def x():
|
||||
super = 1
|
||||
super # Python injects __class__ into scope
|
||||
builtins.super(ChildD8, self).f()
|
||||
|
||||
class ChildD9(ParentD):
|
||||
def f(self):
|
||||
def x():
|
||||
__class__ = 1
|
||||
__class__ # Python injects __class__ into scope
|
||||
builtins.super(ChildD9, self).f()
|
||||
|
||||
class ChildD10(ParentD):
|
||||
def f(self):
|
||||
def x():
|
||||
__class__ = 1
|
||||
super # Python injects __class__ into scope
|
||||
builtins.super(ChildD10, self).f()
|
||||
|
||||
|
||||
# Must be ignored
|
||||
class ParentI:
|
||||
def f(self):
|
||||
print("I")
|
||||
|
||||
class ChildI1(ParentI):
|
||||
def f(self):
|
||||
builtins.super(ChildI1, self).f() # no __class__ in the local scope
|
||||
|
||||
|
||||
class ChildI2(ParentI):
|
||||
def b(self):
|
||||
x = __class__
|
||||
if False: super
|
||||
|
||||
def f(self):
|
||||
self.b()
|
||||
builtins.super(ChildI2, self).f() # no __class__ in the local scope
|
||||
|
||||
class ChildI3(ParentI):
|
||||
def f(self):
|
||||
if False: super
|
||||
def x(_):
|
||||
builtins.super(ChildI3, self).f() # no __class__ in the local scope
|
||||
x(None)
|
||||
|
||||
class ChildI4(ParentI):
|
||||
def f(self):
|
||||
super: "str"
|
||||
builtins.super(ChildI4, self).f() # no __class__ in the local scope
|
||||
|
||||
class ChildI5(ParentI):
|
||||
def f(self):
|
||||
super = 1
|
||||
__class__ = 3
|
||||
builtins.super(ChildI5, self).f() # no __class__ in the local scope
|
||||
|
||||
class ChildI6(ParentI):
|
||||
def f(self):
|
||||
__class__ = None
|
||||
__class__
|
||||
builtins.super(ChildI6, self).f() # no __class__ in the local scope
|
||||
|
||||
class ChildI7(ParentI):
|
||||
def f(self):
|
||||
__class__ = None
|
||||
super
|
||||
builtins.super(ChildI7, self).f()
|
||||
|
||||
class ChildI8(ParentI):
|
||||
def f(self):
|
||||
__class__: "Any"
|
||||
super
|
||||
builtins.super(ChildI8, self).f()
|
||||
|
||||
class ChildI9(ParentI):
|
||||
def f(self):
|
||||
class A:
|
||||
def foo(self):
|
||||
if False: super
|
||||
if False: __class__
|
||||
builtins.super(ChildI9, self).f()
|
||||
|
||||
59
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP043.pyi
vendored
Normal file
59
crates/ruff_linter/resources/test/fixtures/pyupgrade/UP043.pyi
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
from collections.abc import Generator, AsyncGenerator
|
||||
|
||||
|
||||
def func() -> Generator[int, None, None]:
|
||||
yield 42
|
||||
|
||||
|
||||
def func() -> Generator[int, None]:
|
||||
yield 42
|
||||
|
||||
|
||||
def func() -> Generator[int]:
|
||||
yield 42
|
||||
|
||||
|
||||
def func() -> Generator[int, int, int]:
|
||||
foo = yield 42
|
||||
return foo
|
||||
|
||||
|
||||
def func() -> Generator[int, int, None]:
|
||||
_ = yield 42
|
||||
return None
|
||||
|
||||
|
||||
def func() -> Generator[int, None, int]:
|
||||
yield 42
|
||||
return 42
|
||||
|
||||
|
||||
async def func() -> AsyncGenerator[int, None]:
|
||||
yield 42
|
||||
|
||||
|
||||
async def func() -> AsyncGenerator[int]:
|
||||
yield 42
|
||||
|
||||
|
||||
async def func() -> AsyncGenerator[int, int]:
|
||||
foo = yield 42
|
||||
return foo
|
||||
|
||||
|
||||
from typing import Generator, AsyncGenerator
|
||||
|
||||
|
||||
def func() -> Generator[str, None, None]:
|
||||
yield "hello"
|
||||
|
||||
|
||||
async def func() -> AsyncGenerator[str, None]:
|
||||
yield "hello"
|
||||
|
||||
|
||||
async def func() -> AsyncGenerator[ # type: ignore
|
||||
str,
|
||||
None
|
||||
]:
|
||||
yield "hello"
|
||||
@@ -42,3 +42,7 @@ b"a" in bytes("a", "utf-8")
|
||||
1 in set(set([1]))
|
||||
'' in {""}
|
||||
frozenset() in {frozenset()}
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/20238
|
||||
"b" in f"" "" # Error
|
||||
"b" in f"" "x" # OK
|
||||
|
||||
1
crates/ruff_linter/resources/test/fixtures/syntax_errors/yield_from_in_async_function.py
vendored
Normal file
1
crates/ruff_linter/resources/test/fixtures/syntax_errors/yield_from_in_async_function.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
async def f(): yield from x # error
|
||||
@@ -8,7 +8,7 @@ use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::{
|
||||
is_assert_raises_exception_call_enabled, is_optional_as_none_in_union_enabled,
|
||||
is_optional_as_none_in_union_enabled, is_unnecessary_default_type_args_stubs_enabled,
|
||||
};
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::{
|
||||
@@ -142,7 +142,10 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
}
|
||||
|
||||
if checker.is_rule_enabled(Rule::UnnecessaryDefaultTypeArgs) {
|
||||
if checker.target_version() >= PythonVersion::PY313 {
|
||||
if checker.target_version() >= PythonVersion::PY313
|
||||
|| is_unnecessary_default_type_args_stubs_enabled(checker.settings())
|
||||
&& checker.semantic().in_stub_file()
|
||||
{
|
||||
pyupgrade::rules::unnecessary_default_type_args(checker, expr);
|
||||
}
|
||||
}
|
||||
@@ -1292,9 +1295,7 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
if checker.is_rule_enabled(Rule::NonOctalPermissions) {
|
||||
ruff::rules::non_octal_permissions(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::AssertRaisesException)
|
||||
&& is_assert_raises_exception_call_enabled(checker.settings())
|
||||
{
|
||||
if checker.is_rule_enabled(Rule::AssertRaisesException) {
|
||||
flake8_bugbear::rules::assert_raises_exception_call(checker, call);
|
||||
}
|
||||
}
|
||||
@@ -1319,13 +1320,10 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
pylint::rules::yield_in_init(checker, expr);
|
||||
}
|
||||
}
|
||||
Expr::YieldFrom(yield_from) => {
|
||||
Expr::YieldFrom(_) => {
|
||||
if checker.is_rule_enabled(Rule::YieldInInit) {
|
||||
pylint::rules::yield_in_init(checker, expr);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::YieldFromInAsyncFunction) {
|
||||
pylint::rules::yield_from_in_async_function(checker, yield_from);
|
||||
}
|
||||
}
|
||||
Expr::FString(f_string_expr @ ast::ExprFString { value, .. }) => {
|
||||
if checker.is_rule_enabled(Rule::FStringMissingPlaceholders) {
|
||||
|
||||
@@ -71,7 +71,9 @@ use crate::registry::Rule;
|
||||
use crate::rules::pyflakes::rules::{
|
||||
LateFutureImport, ReturnOutsideFunction, YieldOutsideFunction,
|
||||
};
|
||||
use crate::rules::pylint::rules::{AwaitOutsideAsync, LoadBeforeGlobalDeclaration};
|
||||
use crate::rules::pylint::rules::{
|
||||
AwaitOutsideAsync, LoadBeforeGlobalDeclaration, YieldFromInAsyncFunction,
|
||||
};
|
||||
use crate::rules::{flake8_pyi, flake8_type_checking, pyflakes, pyupgrade};
|
||||
use crate::settings::rule_table::RuleTable;
|
||||
use crate::settings::{LinterSettings, TargetVersion, flags};
|
||||
@@ -668,6 +670,12 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
self.report_diagnostic(AwaitOutsideAsync, error.range);
|
||||
}
|
||||
}
|
||||
SemanticSyntaxErrorKind::YieldFromInAsyncFunction => {
|
||||
// PLE1700
|
||||
if self.is_rule_enabled(Rule::YieldFromInAsyncFunction) {
|
||||
self.report_diagnostic(YieldFromInAsyncFunction, error.range);
|
||||
}
|
||||
}
|
||||
SemanticSyntaxErrorKind::ReboundComprehensionVariable
|
||||
| SemanticSyntaxErrorKind::DuplicateTypeParameter
|
||||
| SemanticSyntaxErrorKind::MultipleCaseAssignment(_)
|
||||
|
||||
@@ -334,7 +334,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Async, "109") => (RuleGroup::Stable, rules::flake8_async::rules::AsyncFunctionWithTimeout),
|
||||
(Flake8Async, "110") => (RuleGroup::Stable, rules::flake8_async::rules::AsyncBusyWait),
|
||||
(Flake8Async, "115") => (RuleGroup::Stable, rules::flake8_async::rules::AsyncZeroSleep),
|
||||
(Flake8Async, "116") => (RuleGroup::Preview, rules::flake8_async::rules::LongSleepNotForever),
|
||||
(Flake8Async, "116") => (RuleGroup::Stable, rules::flake8_async::rules::LongSleepNotForever),
|
||||
(Flake8Async, "210") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingHttpCallInAsyncFunction),
|
||||
(Flake8Async, "212") => (RuleGroup::Preview, rules::flake8_async::rules::BlockingHttpCallHttpxInAsyncFunction),
|
||||
(Flake8Async, "220") => (RuleGroup::Stable, rules::flake8_async::rules::CreateSubprocessInAsyncFunction),
|
||||
@@ -563,7 +563,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pyupgrade, "035") => (RuleGroup::Stable, rules::pyupgrade::rules::DeprecatedImport),
|
||||
(Pyupgrade, "036") => (RuleGroup::Stable, rules::pyupgrade::rules::OutdatedVersionBlock),
|
||||
(Pyupgrade, "037") => (RuleGroup::Stable, rules::pyupgrade::rules::QuotedAnnotation),
|
||||
(Pyupgrade, "038") => (RuleGroup::Deprecated, rules::pyupgrade::rules::NonPEP604Isinstance),
|
||||
(Pyupgrade, "038") => (RuleGroup::Removed, rules::pyupgrade::rules::NonPEP604Isinstance),
|
||||
(Pyupgrade, "039") => (RuleGroup::Stable, rules::pyupgrade::rules::UnnecessaryClassParentheses),
|
||||
(Pyupgrade, "040") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695TypeAlias),
|
||||
(Pyupgrade, "041") => (RuleGroup::Stable, rules::pyupgrade::rules::TimeoutErrorAlias),
|
||||
@@ -574,7 +574,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pyupgrade, "046") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695GenericClass),
|
||||
(Pyupgrade, "047") => (RuleGroup::Stable, rules::pyupgrade::rules::NonPEP695GenericFunction),
|
||||
(Pyupgrade, "049") => (RuleGroup::Stable, rules::pyupgrade::rules::PrivateTypeParameter),
|
||||
(Pyupgrade, "050") => (RuleGroup::Preview, rules::pyupgrade::rules::UselessClassMetaclassType),
|
||||
(Pyupgrade, "050") => (RuleGroup::Stable, rules::pyupgrade::rules::UselessClassMetaclassType),
|
||||
|
||||
// pydocstyle
|
||||
(Pydocstyle, "100") => (RuleGroup::Stable, rules::pydocstyle::rules::UndocumentedPublicModule),
|
||||
@@ -773,7 +773,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(PandasVet, "013") => (RuleGroup::Stable, rules::pandas_vet::rules::PandasUseOfDotStack),
|
||||
(PandasVet, "015") => (RuleGroup::Stable, rules::pandas_vet::rules::PandasUseOfPdMerge),
|
||||
(PandasVet, "101") => (RuleGroup::Stable, rules::pandas_vet::rules::PandasNuniqueConstantSeriesCheck),
|
||||
(PandasVet, "901") => (RuleGroup::Deprecated, rules::pandas_vet::rules::PandasDfVariableName),
|
||||
(PandasVet, "901") => (RuleGroup::Removed, rules::pandas_vet::rules::PandasDfVariableName),
|
||||
|
||||
// flake8-errmsg
|
||||
(Flake8ErrMsg, "101") => (RuleGroup::Stable, rules::flake8_errmsg::rules::RawStringInException),
|
||||
@@ -830,8 +830,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Pyi, "056") => (RuleGroup::Stable, rules::flake8_pyi::rules::UnsupportedMethodCallOnAll),
|
||||
(Flake8Pyi, "058") => (RuleGroup::Stable, rules::flake8_pyi::rules::GeneratorReturnFromIterMethod),
|
||||
(Flake8Pyi, "057") => (RuleGroup::Stable, rules::flake8_pyi::rules::ByteStringUsage),
|
||||
(Flake8Pyi, "059") => (RuleGroup::Preview, rules::flake8_pyi::rules::GenericNotLastBaseClass),
|
||||
(Flake8Pyi, "061") => (RuleGroup::Preview, rules::flake8_pyi::rules::RedundantNoneLiteral),
|
||||
(Flake8Pyi, "059") => (RuleGroup::Stable, rules::flake8_pyi::rules::GenericNotLastBaseClass),
|
||||
(Flake8Pyi, "061") => (RuleGroup::Stable, rules::flake8_pyi::rules::RedundantNoneLiteral),
|
||||
(Flake8Pyi, "062") => (RuleGroup::Stable, rules::flake8_pyi::rules::DuplicateLiteralMember),
|
||||
(Flake8Pyi, "063") => (RuleGroup::Stable, rules::flake8_pyi::rules::Pep484StylePositionalOnlyParameter),
|
||||
(Flake8Pyi, "064") => (RuleGroup::Stable, rules::flake8_pyi::rules::RedundantFinalLiteral),
|
||||
@@ -956,7 +956,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8UsePathlib, "207") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::Glob),
|
||||
(Flake8UsePathlib, "208") => (RuleGroup::Stable, rules::flake8_use_pathlib::violations::OsListdir),
|
||||
(Flake8UsePathlib, "210") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::InvalidPathlibWithSuffix),
|
||||
(Flake8UsePathlib, "211") => (RuleGroup::Preview, rules::flake8_use_pathlib::rules::OsSymlink),
|
||||
(Flake8UsePathlib, "211") => (RuleGroup::Stable, rules::flake8_use_pathlib::rules::OsSymlink),
|
||||
|
||||
// flake8-logging-format
|
||||
(Flake8LoggingFormat, "001") => (RuleGroup::Stable, rules::flake8_logging_format::violations::LoggingStringFormat),
|
||||
@@ -1032,7 +1032,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "039") => (RuleGroup::Preview, rules::ruff::rules::UnrawRePattern),
|
||||
(Ruff, "040") => (RuleGroup::Stable, rules::ruff::rules::InvalidAssertMessageLiteralArgument),
|
||||
(Ruff, "041") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryNestedLiteral),
|
||||
(Ruff, "043") => (RuleGroup::Preview, rules::ruff::rules::PytestRaisesAmbiguousPattern),
|
||||
(Ruff, "043") => (RuleGroup::Stable, rules::ruff::rules::PytestRaisesAmbiguousPattern),
|
||||
(Ruff, "045") => (RuleGroup::Preview, rules::ruff::rules::ImplicitClassVarInDataclass),
|
||||
(Ruff, "046") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryCastToInt),
|
||||
(Ruff, "047") => (RuleGroup::Preview, rules::ruff::rules::NeedlessElse),
|
||||
@@ -1046,7 +1046,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Ruff, "056") => (RuleGroup::Preview, rules::ruff::rules::FalsyDictGetFallback),
|
||||
(Ruff, "057") => (RuleGroup::Stable, rules::ruff::rules::UnnecessaryRound),
|
||||
(Ruff, "058") => (RuleGroup::Stable, rules::ruff::rules::StarmapZip),
|
||||
(Ruff, "059") => (RuleGroup::Preview, rules::ruff::rules::UnusedUnpackedVariable),
|
||||
(Ruff, "059") => (RuleGroup::Stable, rules::ruff::rules::UnusedUnpackedVariable),
|
||||
(Ruff, "060") => (RuleGroup::Preview, rules::ruff::rules::InEmptyCollection),
|
||||
(Ruff, "061") => (RuleGroup::Preview, rules::ruff::rules::LegacyFormPytestRaises),
|
||||
(Ruff, "063") => (RuleGroup::Preview, rules::ruff::rules::AccessAnnotationsFromClassDict),
|
||||
@@ -1106,11 +1106,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
|
||||
// airflow
|
||||
(Airflow, "001") => (RuleGroup::Stable, rules::airflow::rules::AirflowVariableNameTaskIdMismatch),
|
||||
(Airflow, "002") => (RuleGroup::Preview, rules::airflow::rules::AirflowDagNoScheduleArgument),
|
||||
(Airflow, "301") => (RuleGroup::Preview, rules::airflow::rules::Airflow3Removal),
|
||||
(Airflow, "302") => (RuleGroup::Preview, rules::airflow::rules::Airflow3MovedToProvider),
|
||||
(Airflow, "311") => (RuleGroup::Preview, rules::airflow::rules::Airflow3SuggestedUpdate),
|
||||
(Airflow, "312") => (RuleGroup::Preview, rules::airflow::rules::Airflow3SuggestedToMoveToProvider),
|
||||
(Airflow, "002") => (RuleGroup::Stable, rules::airflow::rules::AirflowDagNoScheduleArgument),
|
||||
(Airflow, "301") => (RuleGroup::Stable, rules::airflow::rules::Airflow3Removal),
|
||||
(Airflow, "302") => (RuleGroup::Stable, rules::airflow::rules::Airflow3MovedToProvider),
|
||||
(Airflow, "311") => (RuleGroup::Stable, rules::airflow::rules::Airflow3SuggestedUpdate),
|
||||
(Airflow, "312") => (RuleGroup::Stable, rules::airflow::rules::Airflow3SuggestedToMoveToProvider),
|
||||
|
||||
// perflint
|
||||
(Perflint, "101") => (RuleGroup::Stable, rules::perflint::rules::UnnecessaryListCast),
|
||||
@@ -1137,7 +1137,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Refurb, "105") => (RuleGroup::Stable, rules::refurb::rules::PrintEmptyString),
|
||||
(Refurb, "110") => (RuleGroup::Preview, rules::refurb::rules::IfExpInsteadOfOrOperator),
|
||||
(Refurb, "113") => (RuleGroup::Preview, rules::refurb::rules::RepeatedAppend),
|
||||
(Refurb, "116") => (RuleGroup::Preview, rules::refurb::rules::FStringNumberFormat),
|
||||
(Refurb, "116") => (RuleGroup::Stable, rules::refurb::rules::FStringNumberFormat),
|
||||
(Refurb, "118") => (RuleGroup::Preview, rules::refurb::rules::ReimplementedOperator),
|
||||
(Refurb, "122") => (RuleGroup::Stable, rules::refurb::rules::ForLoopWrites),
|
||||
(Refurb, "129") => (RuleGroup::Stable, rules::refurb::rules::ReadlinesInFor),
|
||||
|
||||
@@ -1231,6 +1231,10 @@ mod tests {
|
||||
)]
|
||||
#[test_case(Rule::AwaitOutsideAsync, Path::new("await_outside_async_function.py"))]
|
||||
#[test_case(Rule::AwaitOutsideAsync, Path::new("async_comprehension.py"))]
|
||||
#[test_case(
|
||||
Rule::YieldFromInAsyncFunction,
|
||||
Path::new("yield_from_in_async_function.py")
|
||||
)]
|
||||
fn test_syntax_errors(rule: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = path.to_string_lossy().to_string();
|
||||
let path = Path::new("resources/test/fixtures/syntax_errors").join(path);
|
||||
|
||||
@@ -19,7 +19,7 @@ impl Emitter for GithubEmitter {
|
||||
context: &EmitterContext,
|
||||
) -> anyhow::Result<()> {
|
||||
for diagnostic in diagnostics {
|
||||
let source_location = diagnostic.expect_ruff_start_location();
|
||||
let source_location = diagnostic.ruff_start_location().unwrap_or_default();
|
||||
let filename = diagnostic.expect_ruff_filename();
|
||||
let location = if context.is_notebook(&filename) {
|
||||
// We can't give a reasonable location for the structured formats,
|
||||
@@ -29,7 +29,7 @@ impl Emitter for GithubEmitter {
|
||||
source_location
|
||||
};
|
||||
|
||||
let end_location = diagnostic.expect_ruff_end_location();
|
||||
let end_location = diagnostic.ruff_end_location().unwrap_or_default();
|
||||
|
||||
write!(
|
||||
writer,
|
||||
|
||||
@@ -105,7 +105,7 @@ fn group_diagnostics_by_filename(
|
||||
.or_insert_with(Vec::new)
|
||||
.push(MessageWithLocation {
|
||||
message: diagnostic,
|
||||
start_location: diagnostic.expect_ruff_start_location(),
|
||||
start_location: diagnostic.ruff_start_location().unwrap_or_default(),
|
||||
});
|
||||
}
|
||||
grouped_messages
|
||||
|
||||
@@ -158,8 +158,8 @@ struct SarifResult<'a> {
|
||||
impl<'a> SarifResult<'a> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn from_message(message: &'a Diagnostic) -> Result<Self> {
|
||||
let start_location = message.expect_ruff_start_location();
|
||||
let end_location = message.expect_ruff_end_location();
|
||||
let start_location = message.ruff_start_location().unwrap_or_default();
|
||||
let end_location = message.ruff_end_location().unwrap_or_default();
|
||||
let path = normalize_path(&*message.expect_ruff_filename());
|
||||
Ok(Self {
|
||||
code: RuleCode::from(message),
|
||||
@@ -178,8 +178,8 @@ impl<'a> SarifResult<'a> {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[expect(clippy::unnecessary_wraps)]
|
||||
fn from_message(message: &'a Diagnostic) -> Result<Self> {
|
||||
let start_location = message.expect_ruff_start_location();
|
||||
let end_location = message.expect_ruff_end_location();
|
||||
let start_location = message.ruff_start_location().unwrap_or_default();
|
||||
let end_location = message.ruff_end_location().unwrap_or_default();
|
||||
let path = normalize_path(&*message.expect_ruff_filename());
|
||||
Ok(Self {
|
||||
code: RuleCode::from(message),
|
||||
|
||||
@@ -81,7 +81,7 @@ expression: value
|
||||
"rules": [
|
||||
{
|
||||
"fullDescription": {
|
||||
"text": "## What it does\nChecks for unused imports.\n\n## Why is this bad?\nUnused imports add a performance overhead at runtime, and risk creating\nimport cycles. They also increase the cognitive load of reading the code.\n\nIf an import statement is used to check for the availability or existence\nof a module, consider using `importlib.util.find_spec` instead.\n\nIf an import statement is used to re-export a symbol as part of a module's\npublic interface, consider using a \"redundant\" import alias, which\ninstructs Ruff (and other tools) to respect the re-export, and avoid\nmarking it as unused, as in:\n\n```python\nfrom module import member as member\n```\n\nAlternatively, you can use `__all__` to declare a symbol as part of the module's\ninterface, as in:\n\n```python\n# __init__.py\nimport some_module\n\n__all__ = [\"some_module\"]\n```\n\n## Fix safety\n\nFixes to remove unused imports are safe, except in `__init__.py` files.\n\nApplying fixes to `__init__.py` files is currently in preview. The fix offered depends on the\ntype of the unused import. Ruff will suggest a safe fix to export first-party imports with\neither a redundant alias or, if already present in the file, an `__all__` entry. If multiple\n`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix\nto remove third-party and standard library imports -- the fix is unsafe because the module's\ninterface changes.\n\n## Example\n\n```python\nimport numpy as np # unused import\n\n\ndef area(radius):\n return 3.14 * radius**2\n```\n\nUse instead:\n\n```python\ndef area(radius):\n return 3.14 * radius**2\n```\n\nTo check the availability of a module, use `importlib.util.find_spec`:\n\n```python\nfrom importlib.util import find_spec\n\nif find_spec(\"numpy\") is not None:\n print(\"numpy is installed\")\nelse:\n print(\"numpy is not installed\")\n```\n\n## Preview\nWhen [preview](https://docs.astral.sh/ruff/preview/) is enabled,\nthe criterion for determining whether an import is first-party\nis stricter, which could affect the suggested fix. See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) for more details.\n\n## Options\n- `lint.ignore-init-module-imports`\n- `lint.pyflakes.allowed-unused-imports`\n\n## References\n- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)\n- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)\n- [Typing documentation: interface conventions](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)\n"
|
||||
"text": "## What it does\nChecks for unused imports.\n\n## Why is this bad?\nUnused imports add a performance overhead at runtime, and risk creating\nimport cycles. They also increase the cognitive load of reading the code.\n\nIf an import statement is used to check for the availability or existence\nof a module, consider using `importlib.util.find_spec` instead.\n\nIf an import statement is used to re-export a symbol as part of a module's\npublic interface, consider using a \"redundant\" import alias, which\ninstructs Ruff (and other tools) to respect the re-export, and avoid\nmarking it as unused, as in:\n\n```python\nfrom module import member as member\n```\n\nAlternatively, you can use `__all__` to declare a symbol as part of the module's\ninterface, as in:\n\n```python\n# __init__.py\nimport some_module\n\n__all__ = [\"some_module\"]\n```\n\n## Fix safety\n\nFixes to remove unused imports are safe, except in `__init__.py` files.\n\nApplying fixes to `__init__.py` files is currently in preview. The fix offered depends on the\ntype of the unused import. Ruff will suggest a safe fix to export first-party imports with\neither a redundant alias or, if already present in the file, an `__all__` entry. If multiple\n`__all__` declarations are present, Ruff will not offer a fix. Ruff will suggest an unsafe fix\nto remove third-party and standard library imports -- the fix is unsafe because the module's\ninterface changes.\n\nSee [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc)\nfor more details on how Ruff\ndetermines whether an import is first or third-party.\n\n## Example\n\n```python\nimport numpy as np # unused import\n\n\ndef area(radius):\n return 3.14 * radius**2\n```\n\nUse instead:\n\n```python\ndef area(radius):\n return 3.14 * radius**2\n```\n\nTo check the availability of a module, use `importlib.util.find_spec`:\n\n```python\nfrom importlib.util import find_spec\n\nif find_spec(\"numpy\") is not None:\n print(\"numpy is installed\")\nelse:\n print(\"numpy is not installed\")\n```\n\n## Options\n- `lint.ignore-init-module-imports`\n- `lint.pyflakes.allowed-unused-imports`\n\n## References\n- [Python documentation: `import`](https://docs.python.org/3/reference/simple_stmts.html#the-import-statement)\n- [Python documentation: `importlib.util.find_spec`](https://docs.python.org/3/library/importlib.html#importlib.util.find_spec)\n- [Typing documentation: interface conventions](https://typing.python.org/en/latest/spec/distributing.html#library-interface-public-and-private-symbols)\n"
|
||||
},
|
||||
"help": {
|
||||
"text": "`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability"
|
||||
@@ -119,7 +119,7 @@ expression: value
|
||||
},
|
||||
{
|
||||
"fullDescription": {
|
||||
"text": "## What it does\nChecks for the presence of unused variables in function scopes.\n\n## Why is this bad?\nA variable that is defined but not used is likely a mistake, and should\nbe removed to avoid confusion.\n\nIf a variable is intentionally defined-but-not-used, it should be\nprefixed with an underscore, or some other value that adheres to the\n[`lint.dummy-variable-rgx`] pattern.\n\n## Example\n```python\ndef foo():\n x = 1\n y = 2\n return x\n```\n\nUse instead:\n```python\ndef foo():\n x = 1\n return x\n```\n\n## Fix safety\n\nThis rule's fix is marked as unsafe because removing an unused variable assignment may\ndelete comments that are attached to the assignment.\n\n## Options\n- `lint.dummy-variable-rgx`\n"
|
||||
"text": "## What it does\nChecks for the presence of unused variables in function scopes.\n\n## Why is this bad?\nA variable that is defined but not used is likely a mistake, and should\nbe removed to avoid confusion.\n\nIf a variable is intentionally defined-but-not-used, it should be\nprefixed with an underscore, or some other value that adheres to the\n[`lint.dummy-variable-rgx`] pattern.\n\n## Example\n```python\ndef foo():\n x = 1\n y = 2\n return x\n```\n\nUse instead:\n```python\ndef foo():\n x = 1\n return x\n```\n\n## Fix safety\n\nThis rule's fix is marked as unsafe because removing an unused variable assignment may\ndelete comments that are attached to the assignment.\n\n## See also\n\nThis rule does not apply to bindings in unpacked assignments (e.g. `x, y = 1, 2`). See\n[`unused-unpacked-variable`][RUF059] for this case.\n\n## Options\n- `lint.dummy-variable-rgx`\n\n[RUF059]: https://docs.astral.sh/ruff/rules/unused-unpacked-variable/\n"
|
||||
},
|
||||
"help": {
|
||||
"text": "Local variable `{name}` is assigned to but never used"
|
||||
|
||||
@@ -11,11 +11,6 @@ pub(crate) const fn is_py314_support_enabled(settings: &LinterSettings) -> bool
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/16565
|
||||
pub(crate) const fn is_full_path_match_source_strategy_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// Rule-specific behavior
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/15541
|
||||
@@ -200,35 +195,11 @@ pub(crate) const fn is_allow_nested_roots_enabled(settings: &LinterSettings) ->
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/18208
|
||||
pub(crate) const fn is_multiple_with_statements_fix_safe_enabled(
|
||||
settings: &LinterSettings,
|
||||
) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/18400
|
||||
pub(crate) const fn is_ignore_init_files_in_useless_alias_enabled(
|
||||
settings: &LinterSettings,
|
||||
) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/18572
|
||||
pub(crate) const fn is_optional_as_none_in_union_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/18547
|
||||
pub(crate) const fn is_invalid_async_mock_access_check_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/18867
|
||||
pub(crate) const fn is_raise_exception_byte_string_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/18683
|
||||
pub(crate) const fn is_safe_super_call_with_parameters_fix_enabled(
|
||||
settings: &LinterSettings,
|
||||
@@ -236,27 +207,14 @@ pub(crate) const fn is_safe_super_call_with_parameters_fix_enabled(
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19063
|
||||
pub(crate) const fn is_assert_raises_exception_call_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19100
|
||||
pub(crate) const fn is_add_future_annotations_imports_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19390
|
||||
pub(crate) const fn is_trailing_comma_type_params_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/19851
|
||||
pub(crate) const fn is_maxsplit_without_separator_fix_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/20106
|
||||
pub(crate) const fn is_bidi_forbid_arabic_letter_mark_enabled(settings: &LinterSettings) -> bool {
|
||||
// https://github.com/astral-sh/ruff/pull/20027
|
||||
pub(crate) const fn is_unnecessary_default_type_args_stubs_enabled(
|
||||
settings: &LinterSettings,
|
||||
) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
@@ -214,10 +214,8 @@ impl RuleSelector {
|
||||
RuleGroup::Preview => {
|
||||
preview_enabled && (self.is_exact() || !preview_require_explicit)
|
||||
}
|
||||
// Deprecated rules are excluded in preview mode and with 'All' option unless explicitly selected
|
||||
RuleGroup::Deprecated => {
|
||||
(!preview_enabled || self.is_exact()) && !matches!(self, RuleSelector::All)
|
||||
}
|
||||
// Deprecated rules are excluded by default unless explicitly selected
|
||||
RuleGroup::Deprecated => !preview_enabled && self.is_exact(),
|
||||
// Removed rules are included if explicitly selected but will error downstream
|
||||
RuleGroup::Removed => self.is_exact(),
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ pub(crate) enum Replacement {
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum ProviderReplacement {
|
||||
None,
|
||||
AutoImport {
|
||||
module: &'static str,
|
||||
name: &'static str,
|
||||
|
||||
@@ -46,7 +46,7 @@ pub(crate) struct AirflowDagNoScheduleArgument;
|
||||
impl Violation for AirflowDagNoScheduleArgument {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"DAG should have an explicit `schedule` argument".to_string()
|
||||
"`DAG` or `@dag` should have an explicit `schedule` argument".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,13 +13,13 @@ use ruff_text_size::TextRange;
|
||||
use crate::{FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of Airflow functions and values that have been moved to it providers.
|
||||
/// (e.g., apache-airflow-providers-fab)
|
||||
/// Checks for uses of Airflow functions and values that have been moved to its providers
|
||||
/// (e.g., `apache-airflow-providers-fab`).
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Airflow 3.0 moved various deprecated functions, members, and other
|
||||
/// values to its providers. The user needs to install the corresponding provider and replace
|
||||
/// the original usage with the one in the provider
|
||||
/// the original usage with the one in the provider.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -50,9 +50,6 @@ impl Violation for Airflow3MovedToProvider<'_> {
|
||||
replacement,
|
||||
} = self;
|
||||
match replacement {
|
||||
ProviderReplacement::None => {
|
||||
format!("`{deprecated}` is removed in Airflow 3.0")
|
||||
}
|
||||
ProviderReplacement::AutoImport {
|
||||
name: _,
|
||||
module: _,
|
||||
@@ -85,7 +82,6 @@ impl Violation for Airflow3MovedToProvider<'_> {
|
||||
provider,
|
||||
version,
|
||||
} => Some((module, name.as_str(), provider, version)),
|
||||
ProviderReplacement::None => None,
|
||||
} {
|
||||
Some(format!(
|
||||
"Install `apache-airflow-providers-{provider}>={version}` and use `{name}` from `{module}` instead."
|
||||
@@ -1020,7 +1016,6 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
|
||||
provider: "postgres",
|
||||
version: "1.0.0",
|
||||
},
|
||||
["airflow", "operators", "postgres_operator", "Mapping"] => ProviderReplacement::None,
|
||||
|
||||
// apache-airflow-providers-presto
|
||||
["airflow", "hooks", "presto_hook", "PrestoHook"] => ProviderReplacement::AutoImport {
|
||||
@@ -1209,16 +1204,6 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
|
||||
ProviderReplacement::SourceModuleMovedToProvider { module, name, .. } => {
|
||||
(module, name.as_str())
|
||||
}
|
||||
ProviderReplacement::None => {
|
||||
checker.report_diagnostic(
|
||||
Airflow3MovedToProvider {
|
||||
deprecated: qualified_name,
|
||||
replacement,
|
||||
},
|
||||
ranged,
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if is_guarded_by_try_except(expr, module, name, checker.semantic()) {
|
||||
|
||||
@@ -23,7 +23,7 @@ use ruff_text_size::TextRange;
|
||||
/// ## Why is this bad?
|
||||
/// Airflow 3.0 removed various deprecated functions, members, and other
|
||||
/// values. Some have more modern replacements. Others are considered too niche
|
||||
/// and not worth to be maintained in Airflow.
|
||||
/// and not worth continued maintenance in Airflow.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -704,6 +704,7 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
["airflow", "operators", "subdag", ..] => {
|
||||
Replacement::Message("The whole `airflow.subdag` module has been removed.")
|
||||
}
|
||||
["airflow", "operators", "postgres_operator", "Mapping"] => Replacement::None,
|
||||
["airflow", "operators", "python", "get_current_context"] => Replacement::AutoImport {
|
||||
module: "airflow.sdk",
|
||||
name: "get_current_context",
|
||||
|
||||
@@ -65,9 +65,6 @@ impl Violation for Airflow3SuggestedToMoveToProvider<'_> {
|
||||
replacement,
|
||||
} = self;
|
||||
match replacement {
|
||||
ProviderReplacement::None => {
|
||||
format!("`{deprecated}` is removed in Airflow 3.0")
|
||||
}
|
||||
ProviderReplacement::AutoImport {
|
||||
name: _,
|
||||
module: _,
|
||||
@@ -91,7 +88,6 @@ impl Violation for Airflow3SuggestedToMoveToProvider<'_> {
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
let Airflow3SuggestedToMoveToProvider { replacement, .. } = self;
|
||||
match replacement {
|
||||
ProviderReplacement::None => None,
|
||||
ProviderReplacement::AutoImport {
|
||||
module,
|
||||
name,
|
||||
@@ -319,16 +315,6 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
|
||||
ProviderReplacement::SourceModuleMovedToProvider { module, name, .. } => {
|
||||
(module, name.as_str())
|
||||
}
|
||||
ProviderReplacement::None => {
|
||||
checker.report_diagnostic(
|
||||
Airflow3SuggestedToMoveToProvider {
|
||||
deprecated: qualified_name,
|
||||
replacement: replacement.clone(),
|
||||
},
|
||||
ranged.range(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if is_guarded_by_try_except(expr, module, name, checker.semantic()) {
|
||||
|
||||
@@ -17,9 +17,9 @@ use ruff_text_size::TextRange;
|
||||
/// ## Why is this bad?
|
||||
/// Airflow 3.0 removed various deprecated functions, members, and other
|
||||
/// values. Some have more modern replacements. Others are considered too niche
|
||||
/// and not worth to be maintained in Airflow.
|
||||
/// and not worth continued maintenance in Airflow.
|
||||
/// Even though these symbols still work fine on Airflow 3.0, they are expected to be removed in a future version.
|
||||
/// The user is suggested to replace the original usage with the new ones.
|
||||
/// Where available, users should replace the removed functionality with the new alternatives.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -157,6 +157,9 @@ fn check_call_arguments(checker: &Checker, qualified_name: &QualifiedName, argum
|
||||
["airflow", .., "DAG" | "dag"] => {
|
||||
diagnostic_for_argument(checker, arguments, "sla_miss_callback", None);
|
||||
}
|
||||
["airflow", "timetables", "datasets", "DatasetOrTimeSchedule"] => {
|
||||
diagnostic_for_argument(checker, arguments, "datasets", Some("assets"));
|
||||
}
|
||||
segments => {
|
||||
if is_airflow_builtin_or_provider(segments, "operators", "Operator") {
|
||||
diagnostic_for_argument(checker, arguments, "sla", None);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/airflow/mod.rs
|
||||
---
|
||||
AIR002 DAG should have an explicit `schedule` argument
|
||||
AIR002 `DAG` or `@dag` should have an explicit `schedule` argument
|
||||
--> AIR002.py:4:1
|
||||
|
|
||||
2 | from airflow.timetables.simple import NullTimetable
|
||||
@@ -12,7 +12,7 @@ AIR002 DAG should have an explicit `schedule` argument
|
||||
6 | DAG(dag_id="class_schedule", schedule="@hourly")
|
||||
|
|
||||
|
||||
AIR002 DAG should have an explicit `schedule` argument
|
||||
AIR002 `DAG` or `@dag` should have an explicit `schedule` argument
|
||||
--> AIR002.py:13:2
|
||||
|
|
||||
13 | @dag()
|
||||
|
||||
@@ -2,350 +2,362 @@
|
||||
source: crates/ruff_linter/src/rules/airflow/mod.rs
|
||||
---
|
||||
AIR301 `airflow.PY36` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:39:1
|
||||
--> AIR301_names.py:40:1
|
||||
|
|
||||
38 | # airflow root
|
||||
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
39 | # airflow root
|
||||
40 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^
|
||||
40 |
|
||||
41 | # airflow.api_connexion.security
|
||||
41 |
|
||||
42 | # airflow.api_connexion.security
|
||||
|
|
||||
help: Use `sys.version_info` instead
|
||||
|
||||
AIR301 `airflow.PY37` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:39:7
|
||||
--> AIR301_names.py:40:7
|
||||
|
|
||||
38 | # airflow root
|
||||
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
39 | # airflow root
|
||||
40 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^
|
||||
40 |
|
||||
41 | # airflow.api_connexion.security
|
||||
41 |
|
||||
42 | # airflow.api_connexion.security
|
||||
|
|
||||
help: Use `sys.version_info` instead
|
||||
|
||||
AIR301 `airflow.PY38` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:39:13
|
||||
--> AIR301_names.py:40:13
|
||||
|
|
||||
38 | # airflow root
|
||||
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
39 | # airflow root
|
||||
40 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^
|
||||
40 |
|
||||
41 | # airflow.api_connexion.security
|
||||
41 |
|
||||
42 | # airflow.api_connexion.security
|
||||
|
|
||||
help: Use `sys.version_info` instead
|
||||
|
||||
AIR301 `airflow.PY39` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:39:19
|
||||
--> AIR301_names.py:40:19
|
||||
|
|
||||
38 | # airflow root
|
||||
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
39 | # airflow root
|
||||
40 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^
|
||||
40 |
|
||||
41 | # airflow.api_connexion.security
|
||||
41 |
|
||||
42 | # airflow.api_connexion.security
|
||||
|
|
||||
help: Use `sys.version_info` instead
|
||||
|
||||
AIR301 `airflow.PY310` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:39:25
|
||||
--> AIR301_names.py:40:25
|
||||
|
|
||||
38 | # airflow root
|
||||
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
39 | # airflow root
|
||||
40 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^^
|
||||
40 |
|
||||
41 | # airflow.api_connexion.security
|
||||
41 |
|
||||
42 | # airflow.api_connexion.security
|
||||
|
|
||||
help: Use `sys.version_info` instead
|
||||
|
||||
AIR301 `airflow.PY311` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:39:32
|
||||
--> AIR301_names.py:40:32
|
||||
|
|
||||
38 | # airflow root
|
||||
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
39 | # airflow root
|
||||
40 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^^
|
||||
40 |
|
||||
41 | # airflow.api_connexion.security
|
||||
41 |
|
||||
42 | # airflow.api_connexion.security
|
||||
|
|
||||
help: Use `sys.version_info` instead
|
||||
|
||||
AIR301 `airflow.PY312` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:39:39
|
||||
--> AIR301_names.py:40:39
|
||||
|
|
||||
38 | # airflow root
|
||||
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
39 | # airflow root
|
||||
40 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
| ^^^^^
|
||||
40 |
|
||||
41 | # airflow.api_connexion.security
|
||||
41 |
|
||||
42 | # airflow.api_connexion.security
|
||||
|
|
||||
help: Use `sys.version_info` instead
|
||||
|
||||
AIR301 `airflow.api_connexion.security.requires_access` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:42:1
|
||||
--> AIR301_names.py:43:1
|
||||
|
|
||||
41 | # airflow.api_connexion.security
|
||||
42 | requires_access
|
||||
42 | # airflow.api_connexion.security
|
||||
43 | requires_access
|
||||
| ^^^^^^^^^^^^^^^
|
||||
43 |
|
||||
44 | # airflow.contrib.*
|
||||
44 |
|
||||
45 | # airflow.contrib.*
|
||||
|
|
||||
help: Use `airflow.api_fastapi.core_api.security.requires_access_*` instead
|
||||
|
||||
AIR301 `airflow.contrib.aws_athena_hook.AWSAthenaHook` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:45:1
|
||||
--> AIR301_names.py:46:1
|
||||
|
|
||||
44 | # airflow.contrib.*
|
||||
45 | AWSAthenaHook()
|
||||
45 | # airflow.contrib.*
|
||||
46 | AWSAthenaHook()
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
help: The whole `airflow.contrib` module has been removed.
|
||||
|
||||
AIR301 `airflow.datasets.DatasetAliasEvent` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:49:1
|
||||
--> AIR301_names.py:50:1
|
||||
|
|
||||
48 | # airflow.datasets
|
||||
49 | DatasetAliasEvent()
|
||||
49 | # airflow.datasets
|
||||
50 | DatasetAliasEvent()
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
AIR301 `airflow.operators.subdag.SubDagOperator` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:53:1
|
||||
--> AIR301_names.py:54:1
|
||||
|
|
||||
52 | # airflow.operators.subdag.*
|
||||
53 | SubDagOperator()
|
||||
53 | # airflow.operators.subdag.*
|
||||
54 | SubDagOperator()
|
||||
| ^^^^^^^^^^^^^^
|
||||
55 |
|
||||
56 | # airflow.operators.postgres_operator
|
||||
|
|
||||
help: The whole `airflow.subdag` module has been removed.
|
||||
|
||||
AIR301 [*] `airflow.secrets.cache.SecretCache` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:61:1
|
||||
AIR301 `airflow.operators.postgres_operator.Mapping` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:57:1
|
||||
|
|
||||
60 | # airflow.secrets.cache
|
||||
61 | SecretCache()
|
||||
56 | # airflow.operators.postgres_operator
|
||||
57 | Mapping()
|
||||
| ^^^^^^^
|
||||
58 |
|
||||
59 | # airflow.secrets
|
||||
|
|
||||
|
||||
AIR301 [*] `airflow.secrets.cache.SecretCache` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:64:1
|
||||
|
|
||||
63 | # airflow.secrets.cache
|
||||
64 | SecretCache()
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
help: Use `SecretCache` from `airflow.sdk` instead.
|
||||
13 | from airflow.contrib.aws_athena_hook import AWSAthenaHook
|
||||
14 | from airflow.datasets import DatasetAliasEvent
|
||||
15 | from airflow.operators.subdag import SubDagOperator
|
||||
15 | from airflow.operators.postgres_operator import Mapping
|
||||
16 | from airflow.operators.subdag import SubDagOperator
|
||||
- from airflow.secrets.cache import SecretCache
|
||||
16 | from airflow.secrets.local_filesystem import LocalFilesystemBackend
|
||||
17 | from airflow.triggers.external_task import TaskStateTrigger
|
||||
18 | from airflow.utils import dates
|
||||
17 | from airflow.secrets.local_filesystem import LocalFilesystemBackend
|
||||
18 | from airflow.triggers.external_task import TaskStateTrigger
|
||||
19 | from airflow.utils import dates
|
||||
--------------------------------------------------------------------------------
|
||||
33 | from airflow.utils.trigger_rule import TriggerRule
|
||||
34 | from airflow.www.auth import has_access, has_access_dataset
|
||||
35 | from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_for_key
|
||||
36 + from airflow.sdk import SecretCache
|
||||
37 |
|
||||
38 | # airflow root
|
||||
39 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
34 | from airflow.utils.trigger_rule import TriggerRule
|
||||
35 | from airflow.www.auth import has_access, has_access_dataset
|
||||
36 | from airflow.www.utils import get_sensitive_variables_fields, should_hide_value_for_key
|
||||
37 + from airflow.sdk import SecretCache
|
||||
38 |
|
||||
39 | # airflow root
|
||||
40 | PY36, PY37, PY38, PY39, PY310, PY311, PY312
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
AIR301 `airflow.triggers.external_task.TaskStateTrigger` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:65:1
|
||||
|
|
||||
64 | # airflow.triggers.external_task
|
||||
65 | TaskStateTrigger()
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
66 |
|
||||
67 | # airflow.utils.date
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:68:1
|
||||
|
|
||||
67 | # airflow.utils.date
|
||||
68 | dates.date_range
|
||||
67 | # airflow.triggers.external_task
|
||||
68 | TaskStateTrigger()
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
69 | dates.days_ago
|
||||
69 |
|
||||
70 | # airflow.utils.date
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:69:1
|
||||
|
|
||||
67 | # airflow.utils.date
|
||||
68 | dates.date_range
|
||||
69 | dates.days_ago
|
||||
| ^^^^^^^^^^^^^^
|
||||
70 |
|
||||
71 | date_range
|
||||
|
|
||||
help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
|
||||
|
||||
AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:71:1
|
||||
|
|
||||
69 | dates.days_ago
|
||||
70 |
|
||||
71 | date_range
|
||||
| ^^^^^^^^^^
|
||||
72 | days_ago
|
||||
73 | infer_time_unit
|
||||
70 | # airflow.utils.date
|
||||
71 | dates.date_range
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
72 | dates.days_ago
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:72:1
|
||||
|
|
||||
71 | date_range
|
||||
72 | days_ago
|
||||
70 | # airflow.utils.date
|
||||
71 | dates.date_range
|
||||
72 | dates.days_ago
|
||||
| ^^^^^^^^^^^^^^
|
||||
73 |
|
||||
74 | date_range
|
||||
|
|
||||
help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
|
||||
|
||||
AIR301 `airflow.utils.dates.date_range` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:74:1
|
||||
|
|
||||
72 | dates.days_ago
|
||||
73 |
|
||||
74 | date_range
|
||||
| ^^^^^^^^^^
|
||||
75 | days_ago
|
||||
76 | infer_time_unit
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.dates.days_ago` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:75:1
|
||||
|
|
||||
74 | date_range
|
||||
75 | days_ago
|
||||
| ^^^^^^^^
|
||||
73 | infer_time_unit
|
||||
74 | parse_execution_date
|
||||
76 | infer_time_unit
|
||||
77 | parse_execution_date
|
||||
|
|
||||
help: Use `pendulum.today('UTC').add(days=-N, ...)` instead
|
||||
|
||||
AIR301 `airflow.utils.dates.infer_time_unit` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:73:1
|
||||
--> AIR301_names.py:76:1
|
||||
|
|
||||
71 | date_range
|
||||
72 | days_ago
|
||||
73 | infer_time_unit
|
||||
74 | date_range
|
||||
75 | days_ago
|
||||
76 | infer_time_unit
|
||||
| ^^^^^^^^^^^^^^^
|
||||
74 | parse_execution_date
|
||||
75 | round_time
|
||||
77 | parse_execution_date
|
||||
78 | round_time
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.dates.parse_execution_date` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:74:1
|
||||
--> AIR301_names.py:77:1
|
||||
|
|
||||
72 | days_ago
|
||||
73 | infer_time_unit
|
||||
74 | parse_execution_date
|
||||
75 | days_ago
|
||||
76 | infer_time_unit
|
||||
77 | parse_execution_date
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
75 | round_time
|
||||
76 | scale_time_units
|
||||
78 | round_time
|
||||
79 | scale_time_units
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.dates.round_time` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:75:1
|
||||
--> AIR301_names.py:78:1
|
||||
|
|
||||
73 | infer_time_unit
|
||||
74 | parse_execution_date
|
||||
75 | round_time
|
||||
76 | infer_time_unit
|
||||
77 | parse_execution_date
|
||||
78 | round_time
|
||||
| ^^^^^^^^^^
|
||||
76 | scale_time_units
|
||||
79 | scale_time_units
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.dates.scale_time_units` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:76:1
|
||||
--> AIR301_names.py:79:1
|
||||
|
|
||||
74 | parse_execution_date
|
||||
75 | round_time
|
||||
76 | scale_time_units
|
||||
77 | parse_execution_date
|
||||
78 | round_time
|
||||
79 | scale_time_units
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
77 |
|
||||
78 | # This one was not deprecated.
|
||||
80 |
|
||||
81 | # This one was not deprecated.
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.dag_cycle_tester.test_cycle` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:83:1
|
||||
--> AIR301_names.py:86:1
|
||||
|
|
||||
82 | # airflow.utils.dag_cycle_tester
|
||||
83 | test_cycle
|
||||
85 | # airflow.utils.dag_cycle_tester
|
||||
86 | test_cycle
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.db.create_session` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:87:1
|
||||
--> AIR301_names.py:90:1
|
||||
|
|
||||
86 | # airflow.utils.db
|
||||
87 | create_session
|
||||
89 | # airflow.utils.db
|
||||
90 | create_session
|
||||
| ^^^^^^^^^^^^^^
|
||||
88 |
|
||||
89 | # airflow.utils.decorators
|
||||
91 |
|
||||
92 | # airflow.utils.decorators
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.decorators.apply_defaults` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:90:1
|
||||
--> AIR301_names.py:93:1
|
||||
|
|
||||
89 | # airflow.utils.decorators
|
||||
90 | apply_defaults
|
||||
92 | # airflow.utils.decorators
|
||||
93 | apply_defaults
|
||||
| ^^^^^^^^^^^^^^
|
||||
91 |
|
||||
92 | # airflow.utils.file
|
||||
94 |
|
||||
95 | # airflow.utils.file
|
||||
|
|
||||
help: `apply_defaults` is now unconditionally done and can be safely removed.
|
||||
|
||||
AIR301 `airflow.utils.file.mkdirs` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:93:1
|
||||
--> AIR301_names.py:96:1
|
||||
|
|
||||
92 | # airflow.utils.file
|
||||
93 | mkdirs
|
||||
95 | # airflow.utils.file
|
||||
96 | mkdirs
|
||||
| ^^^^^^
|
||||
|
|
||||
help: Use `pathlib.Path({path}).mkdir` instead
|
||||
|
||||
AIR301 `airflow.utils.state.SHUTDOWN` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:97:1
|
||||
|
|
||||
96 | # airflow.utils.state
|
||||
97 | SHUTDOWN
|
||||
| ^^^^^^^^
|
||||
98 | terminating_states
|
||||
|
|
||||
--> AIR301_names.py:100:1
|
||||
|
|
||||
99 | # airflow.utils.state
|
||||
100 | SHUTDOWN
|
||||
| ^^^^^^^^
|
||||
101 | terminating_states
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.state.terminating_states` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:98:1
|
||||
--> AIR301_names.py:101:1
|
||||
|
|
||||
96 | # airflow.utils.state
|
||||
97 | SHUTDOWN
|
||||
98 | terminating_states
|
||||
99 | # airflow.utils.state
|
||||
100 | SHUTDOWN
|
||||
101 | terminating_states
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
99 |
|
||||
100 | # airflow.utils.trigger_rule
|
||||
102 |
|
||||
103 | # airflow.utils.trigger_rule
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.trigger_rule.TriggerRule.DUMMY` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:101:1
|
||||
--> AIR301_names.py:104:1
|
||||
|
|
||||
100 | # airflow.utils.trigger_rule
|
||||
101 | TriggerRule.DUMMY
|
||||
103 | # airflow.utils.trigger_rule
|
||||
104 | TriggerRule.DUMMY
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
102 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
||||
105 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
||||
|
|
||||
|
||||
AIR301 `airflow.utils.trigger_rule.TriggerRule.NONE_FAILED_OR_SKIPPED` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:102:1
|
||||
--> AIR301_names.py:105:1
|
||||
|
|
||||
100 | # airflow.utils.trigger_rule
|
||||
101 | TriggerRule.DUMMY
|
||||
102 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
||||
103 | # airflow.utils.trigger_rule
|
||||
104 | TriggerRule.DUMMY
|
||||
105 | TriggerRule.NONE_FAILED_OR_SKIPPED
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
AIR301 `airflow.www.auth.has_access` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:106:1
|
||||
--> AIR301_names.py:109:1
|
||||
|
|
||||
105 | # airflow.www.auth
|
||||
106 | has_access
|
||||
108 | # airflow.www.auth
|
||||
109 | has_access
|
||||
| ^^^^^^^^^^
|
||||
107 | has_access_dataset
|
||||
110 | has_access_dataset
|
||||
|
|
||||
|
||||
AIR301 `airflow.www.auth.has_access_dataset` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:107:1
|
||||
--> AIR301_names.py:110:1
|
||||
|
|
||||
105 | # airflow.www.auth
|
||||
106 | has_access
|
||||
107 | has_access_dataset
|
||||
108 | # airflow.www.auth
|
||||
109 | has_access
|
||||
110 | has_access_dataset
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
108 |
|
||||
109 | # airflow.www.utils
|
||||
111 |
|
||||
112 | # airflow.www.utils
|
||||
|
|
||||
|
||||
AIR301 `airflow.www.utils.get_sensitive_variables_fields` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:110:1
|
||||
--> AIR301_names.py:113:1
|
||||
|
|
||||
109 | # airflow.www.utils
|
||||
110 | get_sensitive_variables_fields
|
||||
112 | # airflow.www.utils
|
||||
113 | get_sensitive_variables_fields
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
111 | should_hide_value_for_key
|
||||
114 | should_hide_value_for_key
|
||||
|
|
||||
|
||||
AIR301 `airflow.www.utils.should_hide_value_for_key` is removed in Airflow 3.0
|
||||
--> AIR301_names.py:111:1
|
||||
--> AIR301_names.py:114:1
|
||||
|
|
||||
109 | # airflow.www.utils
|
||||
110 | get_sensitive_variables_fields
|
||||
111 | should_hide_value_for_key
|
||||
112 | # airflow.www.utils
|
||||
113 | get_sensitive_variables_fields
|
||||
114 | should_hide_value_for_key
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
@@ -20,11 +20,3 @@ help: Install `apache-airflow-providers-postgres>=1.0.0` and use `PostgresHook`
|
||||
6 | PostgresHook()
|
||||
7 | Mapping()
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
AIR302 `airflow.operators.postgres_operator.Mapping` is removed in Airflow 3.0
|
||||
--> AIR302_postgres.py:7:1
|
||||
|
|
||||
6 | PostgresHook()
|
||||
7 | Mapping()
|
||||
| ^^^^^^^
|
||||
|
|
||||
|
||||
@@ -558,7 +558,7 @@ AIR311 [*] `airflow.timetables.datasets.DatasetOrTimeSchedule` is removed in Air
|
||||
--> AIR311_names.py:73:1
|
||||
|
|
||||
72 | # airflow.timetables.datasets
|
||||
73 | DatasetOrTimeSchedule()
|
||||
73 | DatasetOrTimeSchedule(datasets=[])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
74 |
|
||||
75 | # airflow.utils.dag_parsing_context
|
||||
@@ -570,12 +570,31 @@ help: Use `AssetOrTimeSchedule` from `airflow.timetables.assets` instead.
|
||||
71 + from airflow.timetables.assets import AssetOrTimeSchedule
|
||||
72 |
|
||||
73 | # airflow.timetables.datasets
|
||||
- DatasetOrTimeSchedule()
|
||||
74 + AssetOrTimeSchedule()
|
||||
- DatasetOrTimeSchedule(datasets=[])
|
||||
74 + AssetOrTimeSchedule(datasets=[])
|
||||
75 |
|
||||
76 | # airflow.utils.dag_parsing_context
|
||||
77 | get_parsing_context()
|
||||
|
||||
AIR311 [*] `datasets` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
--> AIR311_names.py:73:23
|
||||
|
|
||||
72 | # airflow.timetables.datasets
|
||||
73 | DatasetOrTimeSchedule(datasets=[])
|
||||
| ^^^^^^^^
|
||||
74 |
|
||||
75 | # airflow.utils.dag_parsing_context
|
||||
|
|
||||
help: Use `assets` instead
|
||||
70 | from airflow.utils.dag_parsing_context import get_parsing_context
|
||||
71 |
|
||||
72 | # airflow.timetables.datasets
|
||||
- DatasetOrTimeSchedule(datasets=[])
|
||||
73 + DatasetOrTimeSchedule(assets=[])
|
||||
74 |
|
||||
75 | # airflow.utils.dag_parsing_context
|
||||
76 | get_parsing_context()
|
||||
|
||||
AIR311 [*] `airflow.utils.dag_parsing_context.get_parsing_context` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
--> AIR311_names.py:76:1
|
||||
|
|
||||
@@ -593,7 +612,7 @@ help: Use `get_parsing_context` from `airflow.sdk` instead.
|
||||
70 + from airflow.sdk import get_parsing_context
|
||||
71 |
|
||||
72 | # airflow.timetables.datasets
|
||||
73 | DatasetOrTimeSchedule()
|
||||
73 | DatasetOrTimeSchedule(datasets=[])
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
AIR311 [*] `airflow.decorators.base.DecoratedMappedOperator` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
|
||||
@@ -79,7 +79,6 @@ impl Violation for FastApiUnusedPathParameter {
|
||||
function_name,
|
||||
is_positional,
|
||||
} = self;
|
||||
#[expect(clippy::if_not_else)]
|
||||
if !is_positional {
|
||||
format!(
|
||||
"Parameter `{arg_name}` appears in route path, but not in `{function_name}` signature"
|
||||
|
||||
@@ -16,8 +16,6 @@ mod tests {
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::test::test_path;
|
||||
|
||||
use crate::settings::types::PreviewMode;
|
||||
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
#[test_case(Rule::AbstractBaseClassWithoutAbstractMethod, Path::new("B024.py"))]
|
||||
@@ -177,23 +175,4 @@ mod tests {
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::AssertRaisesException, Path::new("B017_0.py"))]
|
||||
#[test_case(Rule::AssertRaisesException, Path::new("B017_1.py"))]
|
||||
fn rules_preview(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_bugbear").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,41 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B017 Do not assert blind exception: `Exception`
|
||||
--> B017_1.py:20:9
|
||||
|
|
||||
18 | class Foobar(unittest.TestCase):
|
||||
19 | def call_form_raises(self) -> None:
|
||||
20 | self.assertRaises(Exception, something_else)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
21 | self.assertRaises(BaseException, something_else)
|
||||
|
|
||||
|
||||
B017 Do not assert blind exception: `BaseException`
|
||||
--> B017_1.py:21:9
|
||||
|
|
||||
19 | def call_form_raises(self) -> None:
|
||||
20 | self.assertRaises(Exception, something_else)
|
||||
21 | self.assertRaises(BaseException, something_else)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
B017 Do not assert blind exception: `Exception`
|
||||
--> B017_1.py:25:5
|
||||
|
|
||||
24 | def test_pytest_call_form() -> None:
|
||||
25 | pytest.raises(Exception, something_else)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
26 | pytest.raises(BaseException, something_else)
|
||||
|
|
||||
|
||||
B017 Do not assert blind exception: `BaseException`
|
||||
--> B017_1.py:26:5
|
||||
|
|
||||
24 | def test_pytest_call_form() -> None:
|
||||
25 | pytest.raises(Exception, something_else)
|
||||
26 | pytest.raises(BaseException, something_else)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
27 |
|
||||
28 | pytest.raises(Exception, something_else, match="hello")
|
||||
|
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B017 Do not assert blind exception: `Exception`
|
||||
--> B017_0.py:23:14
|
||||
|
|
||||
21 | class Foobar(unittest.TestCase):
|
||||
22 | def evil_raises(self) -> None:
|
||||
23 | with self.assertRaises(Exception):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
24 | raise Exception("Evil I say!")
|
||||
|
|
||||
|
||||
B017 Do not assert blind exception: `BaseException`
|
||||
--> B017_0.py:27:14
|
||||
|
|
||||
26 | def also_evil_raises(self) -> None:
|
||||
27 | with self.assertRaises(BaseException):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
28 | raise Exception("Evil I say!")
|
||||
|
|
||||
|
||||
B017 Do not assert blind exception: `Exception`
|
||||
--> B017_0.py:45:10
|
||||
|
|
||||
44 | def test_pytest_raises():
|
||||
45 | with pytest.raises(Exception):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
46 | raise ValueError("Hello")
|
||||
|
|
||||
|
||||
B017 Do not assert blind exception: `Exception`
|
||||
--> B017_0.py:48:10
|
||||
|
|
||||
46 | raise ValueError("Hello")
|
||||
47 |
|
||||
48 | with pytest.raises(Exception), pytest.raises(ValueError):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
49 | raise ValueError("Hello")
|
||||
|
|
||||
|
||||
B017 Do not assert blind exception: `Exception`
|
||||
--> B017_0.py:57:36
|
||||
|
|
||||
55 | raise ValueError("This is also fine")
|
||||
56 |
|
||||
57 | with contextlib.nullcontext(), pytest.raises(Exception):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
58 | raise ValueError("Multiple context managers")
|
||||
|
|
||||
|
||||
B017 Do not assert blind exception: `Exception`
|
||||
--> B017_0.py:62:10
|
||||
|
|
||||
61 | def test_pytest_raises_keyword():
|
||||
62 | with pytest.raises(expected_exception=Exception):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
63 | raise ValueError("Should be flagged")
|
||||
|
|
||||
|
||||
B017 Do not assert blind exception: `Exception`
|
||||
--> B017_0.py:68:18
|
||||
|
|
||||
66 | class TestKwargs(unittest.TestCase):
|
||||
67 | def test_method(self):
|
||||
68 | with self.assertRaises(exception=Exception):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
69 | raise ValueError("Should be flagged")
|
||||
|
|
||||
|
||||
B017 Do not assert blind exception: `BaseException`
|
||||
--> B017_0.py:71:18
|
||||
|
|
||||
69 | raise ValueError("Should be flagged")
|
||||
70 |
|
||||
71 | with self.assertRaises(exception=BaseException):
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
72 | raise ValueError("Should be flagged")
|
||||
|
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B017 Do not assert blind exception: `Exception`
|
||||
--> B017_1.py:20:9
|
||||
|
|
||||
18 | class Foobar(unittest.TestCase):
|
||||
19 | def call_form_raises(self) -> None:
|
||||
20 | self.assertRaises(Exception, something_else)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
21 | self.assertRaises(BaseException, something_else)
|
||||
|
|
||||
|
||||
B017 Do not assert blind exception: `BaseException`
|
||||
--> B017_1.py:21:9
|
||||
|
|
||||
19 | def call_form_raises(self) -> None:
|
||||
20 | self.assertRaises(Exception, something_else)
|
||||
21 | self.assertRaises(BaseException, something_else)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
|
||||
B017 Do not assert blind exception: `Exception`
|
||||
--> B017_1.py:25:5
|
||||
|
|
||||
24 | def test_pytest_call_form() -> None:
|
||||
25 | pytest.raises(Exception, something_else)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
26 | pytest.raises(BaseException, something_else)
|
||||
|
|
||||
|
||||
B017 Do not assert blind exception: `BaseException`
|
||||
--> B017_1.py:26:5
|
||||
|
|
||||
24 | def test_pytest_call_form() -> None:
|
||||
25 | pytest.raises(Exception, something_else)
|
||||
26 | pytest.raises(BaseException, something_else)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
27 |
|
||||
28 | pytest.raises(Exception, something_else, match="hello")
|
||||
|
|
||||
@@ -10,7 +10,7 @@ mod tests {
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::test::test_path;
|
||||
use crate::{assert_diagnostics, assert_diagnostics_diff, settings};
|
||||
use crate::{assert_diagnostics, settings};
|
||||
|
||||
#[test_case(Path::new("COM81.py"))]
|
||||
#[test_case(Path::new("COM81_syntax_error.py"))]
|
||||
@@ -27,28 +27,4 @@ mod tests {
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Path::new("COM81.py"))]
|
||||
#[test_case(Path::new("COM81_syntax_error.py"))]
|
||||
fn preview_rules(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("preview_diff__{}", path.to_string_lossy());
|
||||
let rules = vec![
|
||||
Rule::MissingTrailingComma,
|
||||
Rule::TrailingCommaOnBareTuple,
|
||||
Rule::ProhibitedTrailingComma,
|
||||
];
|
||||
let settings_before = settings::LinterSettings::for_rules(rules.clone());
|
||||
let settings_after = settings::LinterSettings {
|
||||
preview: crate::settings::types::PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rules(rules)
|
||||
};
|
||||
|
||||
assert_diagnostics_diff!(
|
||||
snapshot,
|
||||
Path::new("flake8_commas").join(path).as_path(),
|
||||
&settings_before,
|
||||
&settings_after
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::Locator;
|
||||
use crate::checkers::ast::LintContext;
|
||||
use crate::preview::is_trailing_comma_type_params_enabled;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::{AlwaysFixableViolation, Violation};
|
||||
use crate::{Edit, Fix};
|
||||
|
||||
@@ -298,7 +296,7 @@ pub(crate) fn trailing_commas(
|
||||
}
|
||||
|
||||
// Update the comma context stack.
|
||||
let context = update_context(token, prev, prev_prev, &mut stack, lint_context.settings());
|
||||
let context = update_context(token, prev, prev_prev, &mut stack);
|
||||
|
||||
check_token(token, prev, prev_prev, context, locator, lint_context);
|
||||
|
||||
@@ -417,7 +415,6 @@ fn update_context(
|
||||
prev: SimpleToken,
|
||||
prev_prev: SimpleToken,
|
||||
stack: &mut Vec<Context>,
|
||||
settings: &LinterSettings,
|
||||
) -> Context {
|
||||
let new_context = match token.ty {
|
||||
TokenType::OpeningBracket => match (prev.ty, prev_prev.ty) {
|
||||
@@ -427,19 +424,11 @@ fn update_context(
|
||||
}
|
||||
_ => Context::new(ContextType::Tuple),
|
||||
},
|
||||
TokenType::OpeningSquareBracket if is_trailing_comma_type_params_enabled(settings) => {
|
||||
match (prev.ty, prev_prev.ty) {
|
||||
(TokenType::Named, TokenType::Def | TokenType::Class | TokenType::Type) => {
|
||||
Context::new(ContextType::TypeParameters)
|
||||
}
|
||||
(TokenType::ClosingBracket | TokenType::Named | TokenType::String, _) => {
|
||||
Context::new(ContextType::Subscript)
|
||||
}
|
||||
_ => Context::new(ContextType::List),
|
||||
TokenType::OpeningSquareBracket => match (prev.ty, prev_prev.ty) {
|
||||
(TokenType::Named, TokenType::Def | TokenType::Class | TokenType::Type) => {
|
||||
Context::new(ContextType::TypeParameters)
|
||||
}
|
||||
}
|
||||
TokenType::OpeningSquareBracket => match prev.ty {
|
||||
TokenType::ClosingBracket | TokenType::Named | TokenType::String => {
|
||||
(TokenType::ClosingBracket | TokenType::Named | TokenType::String, _) => {
|
||||
Context::new(ContextType::Subscript)
|
||||
}
|
||||
_ => Context::new(ContextType::List),
|
||||
|
||||
@@ -939,3 +939,111 @@ help: Add trailing comma
|
||||
644 | )
|
||||
645 |
|
||||
646 | assert False, f"<- This is not a trailing comma"
|
||||
|
||||
COM812 [*] Trailing comma missing
|
||||
--> COM81.py:655:6
|
||||
|
|
||||
654 | type X[
|
||||
655 | T
|
||||
| ^
|
||||
656 | ] = T
|
||||
657 | def f[
|
||||
|
|
||||
help: Add trailing comma
|
||||
652 | }"""
|
||||
653 |
|
||||
654 | type X[
|
||||
- T
|
||||
655 + T,
|
||||
656 | ] = T
|
||||
657 | def f[
|
||||
658 | T
|
||||
|
||||
COM812 [*] Trailing comma missing
|
||||
--> COM81.py:658:6
|
||||
|
|
||||
656 | ] = T
|
||||
657 | def f[
|
||||
658 | T
|
||||
| ^
|
||||
659 | ](): pass
|
||||
660 | class C[
|
||||
|
|
||||
help: Add trailing comma
|
||||
655 | T
|
||||
656 | ] = T
|
||||
657 | def f[
|
||||
- T
|
||||
658 + T,
|
||||
659 | ](): pass
|
||||
660 | class C[
|
||||
661 | T
|
||||
|
||||
COM812 [*] Trailing comma missing
|
||||
--> COM81.py:661:6
|
||||
|
|
||||
659 | ](): pass
|
||||
660 | class C[
|
||||
661 | T
|
||||
| ^
|
||||
662 | ]: pass
|
||||
|
|
||||
help: Add trailing comma
|
||||
658 | T
|
||||
659 | ](): pass
|
||||
660 | class C[
|
||||
- T
|
||||
661 + T,
|
||||
662 | ]: pass
|
||||
663 |
|
||||
664 | type X[T,] = T
|
||||
|
||||
COM819 [*] Trailing comma prohibited
|
||||
--> COM81.py:664:9
|
||||
|
|
||||
662 | ]: pass
|
||||
663 |
|
||||
664 | type X[T,] = T
|
||||
| ^
|
||||
665 | def f[T,](): pass
|
||||
666 | class C[T,]: pass
|
||||
|
|
||||
help: Remove trailing comma
|
||||
661 | T
|
||||
662 | ]: pass
|
||||
663 |
|
||||
- type X[T,] = T
|
||||
664 + type X[T] = T
|
||||
665 | def f[T,](): pass
|
||||
666 | class C[T,]: pass
|
||||
|
||||
COM819 [*] Trailing comma prohibited
|
||||
--> COM81.py:665:8
|
||||
|
|
||||
664 | type X[T,] = T
|
||||
665 | def f[T,](): pass
|
||||
| ^
|
||||
666 | class C[T,]: pass
|
||||
|
|
||||
help: Remove trailing comma
|
||||
662 | ]: pass
|
||||
663 |
|
||||
664 | type X[T,] = T
|
||||
- def f[T,](): pass
|
||||
665 + def f[T](): pass
|
||||
666 | class C[T,]: pass
|
||||
|
||||
COM819 [*] Trailing comma prohibited
|
||||
--> COM81.py:666:10
|
||||
|
|
||||
664 | type X[T,] = T
|
||||
665 | def f[T,](): pass
|
||||
666 | class C[T,]: pass
|
||||
| ^
|
||||
|
|
||||
help: Remove trailing comma
|
||||
663 |
|
||||
664 | type X[T,] = T
|
||||
665 | def f[T,](): pass
|
||||
- class C[T,]: pass
|
||||
666 + class C[T]: pass
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
|
||||
--- Summary ---
|
||||
Removed: 0
|
||||
Added: 6
|
||||
|
||||
--- Added ---
|
||||
COM812 [*] Trailing comma missing
|
||||
--> COM81.py:655:6
|
||||
|
|
||||
654 | type X[
|
||||
655 | T
|
||||
| ^
|
||||
656 | ] = T
|
||||
657 | def f[
|
||||
|
|
||||
help: Add trailing comma
|
||||
652 | }"""
|
||||
653 |
|
||||
654 | type X[
|
||||
- T
|
||||
655 + T,
|
||||
656 | ] = T
|
||||
657 | def f[
|
||||
658 | T
|
||||
|
||||
|
||||
COM812 [*] Trailing comma missing
|
||||
--> COM81.py:658:6
|
||||
|
|
||||
656 | ] = T
|
||||
657 | def f[
|
||||
658 | T
|
||||
| ^
|
||||
659 | ](): pass
|
||||
660 | class C[
|
||||
|
|
||||
help: Add trailing comma
|
||||
655 | T
|
||||
656 | ] = T
|
||||
657 | def f[
|
||||
- T
|
||||
658 + T,
|
||||
659 | ](): pass
|
||||
660 | class C[
|
||||
661 | T
|
||||
|
||||
|
||||
COM812 [*] Trailing comma missing
|
||||
--> COM81.py:661:6
|
||||
|
|
||||
659 | ](): pass
|
||||
660 | class C[
|
||||
661 | T
|
||||
| ^
|
||||
662 | ]: pass
|
||||
|
|
||||
help: Add trailing comma
|
||||
658 | T
|
||||
659 | ](): pass
|
||||
660 | class C[
|
||||
- T
|
||||
661 + T,
|
||||
662 | ]: pass
|
||||
663 |
|
||||
664 | type X[T,] = T
|
||||
|
||||
|
||||
COM819 [*] Trailing comma prohibited
|
||||
--> COM81.py:664:9
|
||||
|
|
||||
662 | ]: pass
|
||||
663 |
|
||||
664 | type X[T,] = T
|
||||
| ^
|
||||
665 | def f[T,](): pass
|
||||
666 | class C[T,]: pass
|
||||
|
|
||||
help: Remove trailing comma
|
||||
661 | T
|
||||
662 | ]: pass
|
||||
663 |
|
||||
- type X[T,] = T
|
||||
664 + type X[T] = T
|
||||
665 | def f[T,](): pass
|
||||
666 | class C[T,]: pass
|
||||
|
||||
|
||||
COM819 [*] Trailing comma prohibited
|
||||
--> COM81.py:665:8
|
||||
|
|
||||
664 | type X[T,] = T
|
||||
665 | def f[T,](): pass
|
||||
| ^
|
||||
666 | class C[T,]: pass
|
||||
|
|
||||
help: Remove trailing comma
|
||||
662 | ]: pass
|
||||
663 |
|
||||
664 | type X[T,] = T
|
||||
- def f[T,](): pass
|
||||
665 + def f[T](): pass
|
||||
666 | class C[T,]: pass
|
||||
|
||||
|
||||
COM819 [*] Trailing comma prohibited
|
||||
--> COM81.py:666:10
|
||||
|
|
||||
664 | type X[T,] = T
|
||||
665 | def f[T,](): pass
|
||||
666 | class C[T,]: pass
|
||||
| ^
|
||||
|
|
||||
help: Remove trailing comma
|
||||
663 |
|
||||
664 | type X[T,] = T
|
||||
665 | def f[T,](): pass
|
||||
- class C[T,]: pass
|
||||
666 + class C[T]: pass
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_commas/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
|
||||
--- Summary ---
|
||||
Removed: 0
|
||||
Added: 0
|
||||
@@ -122,6 +122,13 @@ pub(crate) fn unnecessary_map(checker: &Checker, call: &ast::ExprCall) {
|
||||
}
|
||||
};
|
||||
|
||||
// If the lambda body contains a `yield` or `yield from`, rewriting `map(lambda ...)` to a
|
||||
// generator expression or any comprehension is invalid Python syntax
|
||||
// (e.g., `yield` is not allowed inside generator or comprehension expressions). In such cases, skip.
|
||||
if lambda_contains_yield(&lambda.body) {
|
||||
return;
|
||||
}
|
||||
|
||||
for iterable in iterables {
|
||||
// For example, (x+1 for x in (c:=a)) is invalid syntax
|
||||
// so we can't suggest it.
|
||||
@@ -183,6 +190,13 @@ fn map_lambda_and_iterables<'a>(
|
||||
Some((lambda, iterables))
|
||||
}
|
||||
|
||||
/// Returns true if the expression tree contains a `yield` or `yield from` expression.
|
||||
fn lambda_contains_yield(expr: &Expr) -> bool {
|
||||
any_over_expr(expr, &|expr| {
|
||||
matches!(expr, Expr::Yield(_) | Expr::YieldFrom(_))
|
||||
})
|
||||
}
|
||||
|
||||
/// A lambda as the first argument to `map()` has the "expected" arity when:
|
||||
///
|
||||
/// * It has exactly one parameter
|
||||
|
||||
@@ -342,6 +342,7 @@ help: Replace `map()` with a set comprehension
|
||||
75 + _ = t"{ {x % 2 == 0 for x in nums} }"
|
||||
76 | _ = t"{dict(map(lambda v: (v, v**2), nums))}"
|
||||
77 |
|
||||
78 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
C417 [*] Unnecessary `map()` usage (rewrite using a dict comprehension)
|
||||
@@ -359,4 +360,6 @@ help: Replace `map()` with a dict comprehension
|
||||
- _ = t"{dict(map(lambda v: (v, v**2), nums))}"
|
||||
76 + _ = t"{ {v: v**2 for v in nums} }"
|
||||
77 |
|
||||
78 |
|
||||
79 | # See https://github.com/astral-sh/ruff/issues/20198
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -9,7 +9,6 @@ mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::test::test_path;
|
||||
use crate::{assert_diagnostics, settings};
|
||||
|
||||
@@ -47,15 +46,14 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preview_string_exception() -> Result<()> {
|
||||
fn string_exception() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_errmsg/EM101_byte_string.py"),
|
||||
&settings::LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rule(Rule::RawStringInException)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!("preview", diagnostics);
|
||||
assert_diagnostics!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,12 @@ use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Locator;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_raise_exception_byte_string_enabled;
|
||||
use crate::registry::Rule;
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the use of string literals in exception constructors.
|
||||
///
|
||||
/// In [preview], this rule checks for byte string literals in
|
||||
/// exception constructors.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Python includes the `raise` in the default traceback (and formatters
|
||||
/// like Rich and IPython do too).
|
||||
@@ -51,8 +47,6 @@ use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
/// raise RuntimeError(msg)
|
||||
/// RuntimeError: 'Some value' is incorrect
|
||||
/// ```
|
||||
///
|
||||
/// [preview]: https://docs.astral.sh/ruff/preview/
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct RawStringInException;
|
||||
|
||||
@@ -218,9 +212,7 @@ pub(crate) fn string_in_exception(checker: &Checker, stmt: &Stmt, exc: &Expr) {
|
||||
// Check for byte string literals.
|
||||
Expr::BytesLiteral(ast::ExprBytesLiteral { value: bytes, .. }) => {
|
||||
if checker.settings().rules.enabled(Rule::RawStringInException) {
|
||||
if bytes.len() >= checker.settings().flake8_errmsg.max_string_length
|
||||
&& is_raise_exception_byte_string_enabled(checker.settings())
|
||||
{
|
||||
if bytes.len() >= checker.settings().flake8_errmsg.max_string_length {
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(RawStringInException, first.range());
|
||||
if let Some(indentation) = whitespace::indentation(checker.source(), stmt) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_ast::{self as ast, ParameterWithDefault};
|
||||
use ruff_python_semantic::analyze::function_type;
|
||||
|
||||
use crate::Violation;
|
||||
@@ -85,16 +85,9 @@ pub(crate) fn pep_484_positional_parameter(checker: &Checker, function_def: &ast
|
||||
function_type::FunctionType::Method | function_type::FunctionType::ClassMethod
|
||||
));
|
||||
|
||||
if let Some(arg) = function_def.parameters.args.get(skip) {
|
||||
if is_old_style_positional_only(arg) {
|
||||
checker.report_diagnostic(Pep484StylePositionalOnlyParameter, arg.identifier());
|
||||
if let Some(param) = function_def.parameters.args.get(skip) {
|
||||
if param.uses_pep_484_positional_only_convention() {
|
||||
checker.report_diagnostic(Pep484StylePositionalOnlyParameter, param.identifier());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the [`ParameterWithDefault`] is an old-style positional-only parameter (i.e.,
|
||||
/// its name starts with `__` and does not end with `__`).
|
||||
fn is_old_style_positional_only(param: &ParameterWithDefault) -> bool {
|
||||
let arg_name = param.name();
|
||||
arg_name.starts_with("__") && !arg_name.ends_with("__")
|
||||
}
|
||||
|
||||
@@ -59,7 +59,6 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::MultipleWithStatements, Path::new("SIM117.py"))]
|
||||
#[test_case(Rule::SplitStaticString, Path::new("SIM905.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
|
||||
@@ -10,7 +10,6 @@ use super::fix_with;
|
||||
use crate::Fix;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits::fits;
|
||||
use crate::preview::is_multiple_with_statements_fix_safe_enabled;
|
||||
use crate::{FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
@@ -45,15 +44,8 @@ use crate::{FixAvailability, Violation};
|
||||
/// pass
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
///
|
||||
/// This fix is marked as always unsafe unless [preview] mode is enabled, in which case it is always
|
||||
/// marked as safe. Note that the fix is unavailable if it would remove comments (in either case).
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: The `with` statement](https://docs.python.org/3/reference/compound_stmts.html#the-with-statement)
|
||||
///
|
||||
/// [preview]: https://docs.astral.sh/ruff/preview/
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MultipleWithStatements;
|
||||
|
||||
@@ -195,11 +187,7 @@ pub(crate) fn multiple_with_statements(
|
||||
checker.settings().tab_size,
|
||||
)
|
||||
}) {
|
||||
if is_multiple_with_statements_fix_safe_enabled(checker.settings()) {
|
||||
Ok(Some(Fix::safe_edit(edit)))
|
||||
} else {
|
||||
Ok(Some(Fix::unsafe_edit(edit)))
|
||||
}
|
||||
Ok(Some(Fix::safe_edit(edit)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ help: Combine `with` statements
|
||||
4 |
|
||||
5 | # SIM117
|
||||
6 | with A():
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:7:1
|
||||
@@ -46,7 +45,6 @@ help: Combine `with` statements
|
||||
10 |
|
||||
11 | # SIM117
|
||||
12 | with A() as a:
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
SIM117 Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:13:1
|
||||
@@ -84,7 +82,6 @@ help: Combine `with` statements
|
||||
22 |
|
||||
23 | # OK
|
||||
24 | with A() as a:
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:47:1
|
||||
@@ -107,7 +104,6 @@ help: Combine `with` statements
|
||||
49 |
|
||||
50 | while True:
|
||||
51 | # SIM117
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:53:5
|
||||
@@ -144,7 +140,6 @@ help: Combine `with` statements
|
||||
64 | "this for some reason")
|
||||
65 |
|
||||
66 | # SIM117
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:68:1
|
||||
@@ -171,7 +166,6 @@ help: Combine `with` statements
|
||||
73 |
|
||||
74 | # SIM117
|
||||
75 | with A() as a:
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:76:1
|
||||
@@ -203,7 +197,6 @@ help: Combine `with` statements
|
||||
81 |
|
||||
82 | # SIM117
|
||||
83 | with (
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:84:1
|
||||
@@ -237,7 +230,6 @@ help: Combine `with` statements
|
||||
90 |
|
||||
91 | # SIM117 (auto-fixable)
|
||||
92 | with A("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as a:
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:95:1
|
||||
@@ -260,7 +252,6 @@ help: Combine `with` statements
|
||||
97 |
|
||||
98 | # SIM117 (not auto-fixable too long)
|
||||
99 | with A("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ890") as a:
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
SIM117 Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:100:1
|
||||
@@ -319,7 +310,6 @@ help: Combine `with` statements
|
||||
136 |
|
||||
137 | # Allow cascading for some statements.
|
||||
138 | import anyio
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:163:1
|
||||
@@ -339,4 +329,3 @@ help: Combine `with` statements
|
||||
- pass
|
||||
163 + async with asyncio.timeout(1), A(), B():
|
||||
164 + pass
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -1,331 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_simplify/mod.rs
|
||||
---
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:2:1
|
||||
|
|
||||
1 | # SIM117
|
||||
2 | / with A() as a:
|
||||
3 | | with B() as b:
|
||||
| |__________________^
|
||||
4 | print("hello")
|
||||
|
|
||||
help: Combine `with` statements
|
||||
1 | # SIM117
|
||||
- with A() as a:
|
||||
- with B() as b:
|
||||
- print("hello")
|
||||
2 + with A() as a, B() as b:
|
||||
3 + print("hello")
|
||||
4 |
|
||||
5 | # SIM117
|
||||
6 | with A():
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:7:1
|
||||
|
|
||||
6 | # SIM117
|
||||
7 | / with A():
|
||||
8 | | with B():
|
||||
| |_____________^
|
||||
9 | with C():
|
||||
10 | print("hello")
|
||||
|
|
||||
help: Combine `with` statements
|
||||
4 | print("hello")
|
||||
5 |
|
||||
6 | # SIM117
|
||||
- with A():
|
||||
- with B():
|
||||
- with C():
|
||||
- print("hello")
|
||||
7 + with A(), B():
|
||||
8 + with C():
|
||||
9 + print("hello")
|
||||
10 |
|
||||
11 | # SIM117
|
||||
12 | with A() as a:
|
||||
|
||||
SIM117 Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:13:1
|
||||
|
|
||||
12 | # SIM117
|
||||
13 | / with A() as a:
|
||||
14 | | # Unfixable due to placement of this comment.
|
||||
15 | | with B() as b:
|
||||
| |__________________^
|
||||
16 | print("hello")
|
||||
|
|
||||
help: Combine `with` statements
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:19:1
|
||||
|
|
||||
18 | # SIM117
|
||||
19 | / with A() as a:
|
||||
20 | | with B() as b:
|
||||
| |__________________^
|
||||
21 | # Fixable due to placement of this comment.
|
||||
22 | print("hello")
|
||||
|
|
||||
help: Combine `with` statements
|
||||
16 | print("hello")
|
||||
17 |
|
||||
18 | # SIM117
|
||||
- with A() as a:
|
||||
- with B() as b:
|
||||
- # Fixable due to placement of this comment.
|
||||
- print("hello")
|
||||
19 + with A() as a, B() as b:
|
||||
20 + # Fixable due to placement of this comment.
|
||||
21 + print("hello")
|
||||
22 |
|
||||
23 | # OK
|
||||
24 | with A() as a:
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:47:1
|
||||
|
|
||||
46 | # SIM117
|
||||
47 | / async with A() as a:
|
||||
48 | | async with B() as b:
|
||||
| |________________________^
|
||||
49 | print("hello")
|
||||
|
|
||||
help: Combine `with` statements
|
||||
44 | print("hello")
|
||||
45 |
|
||||
46 | # SIM117
|
||||
- async with A() as a:
|
||||
- async with B() as b:
|
||||
- print("hello")
|
||||
47 + async with A() as a, B() as b:
|
||||
48 + print("hello")
|
||||
49 |
|
||||
50 | while True:
|
||||
51 | # SIM117
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:53:5
|
||||
|
|
||||
51 | while True:
|
||||
52 | # SIM117
|
||||
53 | / with A() as a:
|
||||
54 | | with B() as b:
|
||||
| |______________________^
|
||||
55 | """this
|
||||
56 | is valid"""
|
||||
|
|
||||
help: Combine `with` statements
|
||||
50 |
|
||||
51 | while True:
|
||||
52 | # SIM117
|
||||
- with A() as a:
|
||||
- with B() as b:
|
||||
- """this
|
||||
53 + with A() as a, B() as b:
|
||||
54 + """this
|
||||
55 | is valid"""
|
||||
56 |
|
||||
- """the indentation on
|
||||
57 + """the indentation on
|
||||
58 | this line is significant"""
|
||||
59 |
|
||||
- "this is" \
|
||||
60 + "this is" \
|
||||
61 | "allowed too"
|
||||
62 |
|
||||
- ("so is"
|
||||
63 + ("so is"
|
||||
64 | "this for some reason")
|
||||
65 |
|
||||
66 | # SIM117
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:68:1
|
||||
|
|
||||
67 | # SIM117
|
||||
68 | / with (
|
||||
69 | | A() as a,
|
||||
70 | | B() as b,
|
||||
71 | | ):
|
||||
72 | | with C() as c:
|
||||
| |__________________^
|
||||
73 | print("hello")
|
||||
|
|
||||
help: Combine `with` statements
|
||||
67 | # SIM117
|
||||
68 | with (
|
||||
69 | A() as a,
|
||||
- B() as b,
|
||||
70 + B() as b,C() as c
|
||||
71 | ):
|
||||
- with C() as c:
|
||||
- print("hello")
|
||||
72 + print("hello")
|
||||
73 |
|
||||
74 | # SIM117
|
||||
75 | with A() as a:
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:76:1
|
||||
|
|
||||
75 | # SIM117
|
||||
76 | / with A() as a:
|
||||
77 | | with (
|
||||
78 | | B() as b,
|
||||
79 | | C() as c,
|
||||
80 | | ):
|
||||
| |______^
|
||||
81 | print("hello")
|
||||
|
|
||||
help: Combine `with` statements
|
||||
73 | print("hello")
|
||||
74 |
|
||||
75 | # SIM117
|
||||
- with A() as a:
|
||||
- with (
|
||||
- B() as b,
|
||||
- C() as c,
|
||||
- ):
|
||||
- print("hello")
|
||||
76 + with (
|
||||
77 + A() as a, B() as b,
|
||||
78 + C() as c,
|
||||
79 + ):
|
||||
80 + print("hello")
|
||||
81 |
|
||||
82 | # SIM117
|
||||
83 | with (
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:84:1
|
||||
|
|
||||
83 | # SIM117
|
||||
84 | / with (
|
||||
85 | | A() as a,
|
||||
86 | | B() as b,
|
||||
87 | | ):
|
||||
88 | | with (
|
||||
89 | | C() as c,
|
||||
90 | | D() as d,
|
||||
91 | | ):
|
||||
| |______^
|
||||
92 | print("hello")
|
||||
|
|
||||
help: Combine `with` statements
|
||||
83 | # SIM117
|
||||
84 | with (
|
||||
85 | A() as a,
|
||||
- B() as b,
|
||||
86 + B() as b,C() as c,
|
||||
87 + D() as d,
|
||||
88 | ):
|
||||
- with (
|
||||
- C() as c,
|
||||
- D() as d,
|
||||
- ):
|
||||
- print("hello")
|
||||
89 + print("hello")
|
||||
90 |
|
||||
91 | # SIM117 (auto-fixable)
|
||||
92 | with A("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as a:
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:95:1
|
||||
|
|
||||
94 | # SIM117 (auto-fixable)
|
||||
95 | / with A("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as a:
|
||||
96 | | with B("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as b:
|
||||
| |__________________________________________________^
|
||||
97 | print("hello")
|
||||
|
|
||||
help: Combine `with` statements
|
||||
92 | print("hello")
|
||||
93 |
|
||||
94 | # SIM117 (auto-fixable)
|
||||
- with A("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as a:
|
||||
- with B("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as b:
|
||||
- print("hello")
|
||||
95 + with A("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as a, B("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as b:
|
||||
96 + print("hello")
|
||||
97 |
|
||||
98 | # SIM117 (not auto-fixable too long)
|
||||
99 | with A("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ890") as a:
|
||||
|
||||
SIM117 Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:100:1
|
||||
|
|
||||
99 | # SIM117 (not auto-fixable too long)
|
||||
100 | / with A("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ890") as a:
|
||||
101 | | with B("01ß9💣2ℝ8901ß9💣2ℝ8901ß9💣2ℝ89") as b:
|
||||
| |__________________________________________________^
|
||||
102 | print("hello")
|
||||
|
|
||||
help: Combine `with` statements
|
||||
|
||||
SIM117 Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:106:5
|
||||
|
|
||||
104 | # From issue #3025.
|
||||
105 | async def main():
|
||||
106 | / async with A() as a: # SIM117.
|
||||
107 | | async with B() as b:
|
||||
| |____________________________^
|
||||
108 | print("async-inside!")
|
||||
|
|
||||
help: Combine `with` statements
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:126:1
|
||||
|
|
||||
125 | # SIM117
|
||||
126 | / with A() as a:
|
||||
127 | | with B() as b:
|
||||
| |__________________^
|
||||
128 | type ListOrSet[T] = list[T] | set[T]
|
||||
|
|
||||
help: Combine `with` statements
|
||||
123 | f(b2, c2, d2)
|
||||
124 |
|
||||
125 | # SIM117
|
||||
- with A() as a:
|
||||
- with B() as b:
|
||||
- type ListOrSet[T] = list[T] | set[T]
|
||||
126 + with A() as a, B() as b:
|
||||
127 + type ListOrSet[T] = list[T] | set[T]
|
||||
128 |
|
||||
- class ClassA[T: str]:
|
||||
- def method1(self) -> T:
|
||||
- ...
|
||||
129 + class ClassA[T: str]:
|
||||
130 + def method1(self) -> T:
|
||||
131 + ...
|
||||
132 |
|
||||
- f" something { my_dict["key"] } something else "
|
||||
133 + f" something { my_dict["key"] } something else "
|
||||
134 |
|
||||
- f"foo {f"bar {x}"} baz"
|
||||
135 + f"foo {f"bar {x}"} baz"
|
||||
136 |
|
||||
137 | # Allow cascading for some statements.
|
||||
138 | import anyio
|
||||
|
||||
SIM117 [*] Use a single `with` statement with multiple contexts instead of nested `with` statements
|
||||
--> SIM117.py:163:1
|
||||
|
|
||||
162 | # Do not suppress combination, if a context manager is already combined with another.
|
||||
163 | / async with asyncio.timeout(1), A():
|
||||
164 | | async with B():
|
||||
| |___________________^
|
||||
165 | pass
|
||||
|
|
||||
help: Combine `with` statements
|
||||
160 | pass
|
||||
161 |
|
||||
162 | # Do not suppress combination, if a context manager is already combined with another.
|
||||
- async with asyncio.timeout(1), A():
|
||||
- async with B():
|
||||
- pass
|
||||
163 + async with asyncio.timeout(1), A(), B():
|
||||
164 + pass
|
||||
@@ -66,7 +66,7 @@ impl TypingReference {
|
||||
}
|
||||
|
||||
// prefer `from __future__ import annotations` to quoting
|
||||
if settings.future_annotations()
|
||||
if settings.future_annotations
|
||||
&& !reference.in_typing_only_annotation()
|
||||
&& reference.in_runtime_evaluated_annotation()
|
||||
{
|
||||
|
||||
@@ -14,7 +14,6 @@ mod tests {
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::registry::{Linter, Rule};
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::test::{test_path, test_snippet};
|
||||
use crate::{assert_diagnostics, settings};
|
||||
|
||||
@@ -86,7 +85,6 @@ mod tests {
|
||||
Path::new("flake8_type_checking").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
future_annotations: true,
|
||||
preview: PreviewMode::Enabled,
|
||||
// also enable quoting annotations to check the interaction. the future import
|
||||
// should take precedence.
|
||||
flake8_type_checking: super::settings::Settings {
|
||||
|
||||
@@ -11,12 +11,10 @@ use crate::checkers::ast::{Checker, DiagnosticGuard};
|
||||
use crate::codes::Rule;
|
||||
use crate::fix;
|
||||
use crate::importer::ImportedMembers;
|
||||
use crate::preview::is_full_path_match_source_strategy_enabled;
|
||||
use crate::rules::flake8_type_checking::helpers::{
|
||||
TypingReference, filter_contained, quote_annotation,
|
||||
};
|
||||
use crate::rules::flake8_type_checking::imports::ImportBinding;
|
||||
use crate::rules::isort::categorize::MatchSourceStrategy;
|
||||
use crate::rules::isort::{ImportSection, ImportType, categorize};
|
||||
use crate::{Fix, FixAvailability, Violation};
|
||||
|
||||
@@ -40,6 +38,13 @@ use crate::{Fix, FixAvailability, Violation};
|
||||
/// [`lint.flake8-type-checking.runtime-evaluated-decorators`] settings to mark them
|
||||
/// as such.
|
||||
///
|
||||
/// If [`lint.future-annotations`] is set to `true`, `from __future__ import
|
||||
/// annotations` will be added if doing so would enable an import to be
|
||||
/// moved into an `if TYPE_CHECKING:` block. This takes precedence over the
|
||||
/// [`lint.flake8-type-checking.quote-annotations`] setting described above if
|
||||
/// both settings are enabled.
|
||||
///
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from __future__ import annotations
|
||||
@@ -65,18 +70,6 @@ use crate::{Fix, FixAvailability, Violation};
|
||||
/// return len(sized)
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// ## Preview
|
||||
/// When [preview](https://docs.astral.sh/ruff/preview/) is enabled,
|
||||
/// the criterion for determining whether an import is first-party
|
||||
/// is stricter, which could affect whether this lint is triggered vs [`TC001`](https://docs.astral.sh/ruff/rules/typing-only-third-party-import/). See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) for more details.
|
||||
///
|
||||
/// If [`lint.future-annotations`] is set to `true`, `from __future__ import
|
||||
/// annotations` will be added if doing so would enable an import to be moved into an `if
|
||||
/// TYPE_CHECKING:` block. This takes precedence over the
|
||||
/// [`lint.flake8-type-checking.quote-annotations`] setting described above if both settings are
|
||||
/// enabled.
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.flake8-type-checking.quote-annotations`
|
||||
/// - `lint.flake8-type-checking.runtime-evaluated-base-classes`
|
||||
@@ -128,6 +121,12 @@ impl Violation for TypingOnlyFirstPartyImport {
|
||||
/// [`lint.flake8-type-checking.runtime-evaluated-decorators`] settings to mark them
|
||||
/// as such.
|
||||
///
|
||||
/// If [`lint.future-annotations`] is set to `true`, `from __future__ import
|
||||
/// annotations` will be added if doing so would enable an import to be
|
||||
/// moved into an `if TYPE_CHECKING:` block. This takes precedence over the
|
||||
/// [`lint.flake8-type-checking.quote-annotations`] setting described above if
|
||||
/// both settings are enabled.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from __future__ import annotations
|
||||
@@ -153,17 +152,6 @@ impl Violation for TypingOnlyFirstPartyImport {
|
||||
/// return len(df)
|
||||
/// ```
|
||||
///
|
||||
/// ## Preview
|
||||
/// When [preview](https://docs.astral.sh/ruff/preview/) is enabled,
|
||||
/// the criterion for determining whether an import is first-party
|
||||
/// is stricter, which could affect whether this lint is triggered vs [`TC001`](https://docs.astral.sh/ruff/rules/typing-only-first-party-import/). See [this FAQ section](https://docs.astral.sh/ruff/faq/#how-does-ruff-determine-which-of-my-imports-are-first-party-third-party-etc) for more details.
|
||||
///
|
||||
/// If [`lint.future-annotations`] is set to `true`, `from __future__ import
|
||||
/// annotations` will be added if doing so would enable an import to be moved into an `if
|
||||
/// TYPE_CHECKING:` block. This takes precedence over the
|
||||
/// [`lint.flake8-type-checking.quote-annotations`] setting described above if both settings are
|
||||
/// enabled.
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.flake8-type-checking.quote-annotations`
|
||||
/// - `lint.flake8-type-checking.runtime-evaluated-base-classes`
|
||||
@@ -215,6 +203,12 @@ impl Violation for TypingOnlyThirdPartyImport {
|
||||
/// [`lint.flake8-type-checking.runtime-evaluated-decorators`] settings to mark them
|
||||
/// as such.
|
||||
///
|
||||
/// If [`lint.future-annotations`] is set to `true`, `from __future__ import
|
||||
/// annotations` will be added if doing so would enable an import to be
|
||||
/// moved into an `if TYPE_CHECKING:` block. This takes precedence over the
|
||||
/// [`lint.flake8-type-checking.quote-annotations`] setting described above if
|
||||
/// both settings are enabled.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from __future__ import annotations
|
||||
@@ -240,15 +234,6 @@ impl Violation for TypingOnlyThirdPartyImport {
|
||||
/// return str(path)
|
||||
/// ```
|
||||
///
|
||||
/// ## Preview
|
||||
///
|
||||
/// When [preview](https://docs.astral.sh/ruff/preview/) is enabled, if
|
||||
/// [`lint.future-annotations`] is set to `true`, `from __future__ import
|
||||
/// annotations` will be added if doing so would enable an import to be moved into an `if
|
||||
/// TYPE_CHECKING:` block. This takes precedence over the
|
||||
/// [`lint.flake8-type-checking.quote-annotations`] setting described above if both settings are
|
||||
/// enabled.
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.flake8-type-checking.quote-annotations`
|
||||
/// - `lint.flake8-type-checking.runtime-evaluated-base-classes`
|
||||
@@ -297,7 +282,7 @@ pub(crate) fn typing_only_runtime_import(
|
||||
|
||||
// If we can't add a `__future__` import and in un-strict mode, don't flag typing-only
|
||||
// imports that are implicitly loaded by way of a valid runtime import.
|
||||
if !checker.settings().future_annotations()
|
||||
if !checker.settings().future_annotations
|
||||
&& !checker.settings().flake8_type_checking.strict
|
||||
&& runtime_imports
|
||||
.iter()
|
||||
@@ -347,13 +332,6 @@ pub(crate) fn typing_only_runtime_import(
|
||||
let source_name = import.source_name().join(".");
|
||||
|
||||
// Categorize the import, using coarse-grained categorization.
|
||||
let match_source_strategy =
|
||||
if is_full_path_match_source_strategy_enabled(checker.settings()) {
|
||||
MatchSourceStrategy::FullPath
|
||||
} else {
|
||||
MatchSourceStrategy::Root
|
||||
};
|
||||
|
||||
let import_type = match categorize(
|
||||
&source_name,
|
||||
qualified_name.is_unresolved_import(),
|
||||
@@ -365,7 +343,6 @@ pub(crate) fn typing_only_runtime_import(
|
||||
checker.settings().isort.no_sections,
|
||||
&checker.settings().isort.section_order,
|
||||
&checker.settings().isort.default_section,
|
||||
match_source_strategy,
|
||||
) {
|
||||
ImportSection::Known(ImportType::LocalFolder | ImportType::FirstParty) => {
|
||||
ImportType::FirstParty
|
||||
|
||||
@@ -60,6 +60,7 @@ pub(crate) fn check_os_pathlib_single_arg_calls(
|
||||
fn_argument: &str,
|
||||
fix_enabled: bool,
|
||||
violation: impl Violation,
|
||||
applicability: Option<Applicability>,
|
||||
) {
|
||||
if call.arguments.len() != 1 {
|
||||
return;
|
||||
@@ -74,33 +75,39 @@ pub(crate) fn check_os_pathlib_single_arg_calls(
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(violation, call.func.range());
|
||||
|
||||
if fix_enabled {
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import("pathlib", "Path"),
|
||||
call.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
|
||||
let applicability = if checker.comment_ranges().intersects(range) {
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
};
|
||||
|
||||
let replacement = if is_pathlib_path_call(checker, arg) {
|
||||
format!("{arg_code}.{attr}")
|
||||
} else {
|
||||
format!("{binding}({arg_code}).{attr}")
|
||||
};
|
||||
|
||||
Ok(Fix::applicable_edits(
|
||||
Edit::range_replacement(replacement, range),
|
||||
[import_edit],
|
||||
applicability,
|
||||
))
|
||||
});
|
||||
if !fix_enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import("pathlib", "Path"),
|
||||
call.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
|
||||
let replacement = if is_pathlib_path_call(checker, arg) {
|
||||
format!("{arg_code}.{attr}")
|
||||
} else {
|
||||
format!("{binding}({arg_code}).{attr}")
|
||||
};
|
||||
|
||||
let edit = Edit::range_replacement(replacement, range);
|
||||
|
||||
let fix = match applicability {
|
||||
Some(Applicability::Unsafe) => Fix::unsafe_edits(edit, [import_edit]),
|
||||
_ => {
|
||||
let applicability = if checker.comment_ranges().intersects(range) {
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
};
|
||||
Fix::applicable_edits(edit, [import_edit], applicability)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(fix)
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn get_name_expr(expr: &Expr) -> Option<&ast::ExprName> {
|
||||
|
||||
@@ -129,7 +129,6 @@ mod tests {
|
||||
#[test_case(Rule::OsPathGetatime, Path::new("PTH203.py"))]
|
||||
#[test_case(Rule::OsPathGetmtime, Path::new("PTH204.py"))]
|
||||
#[test_case(Rule::OsPathGetctime, Path::new("PTH205.py"))]
|
||||
#[test_case(Rule::OsSymlink, Path::new("PTH211.py"))]
|
||||
fn preview_flake8_use_pathlib(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use ruff_diagnostics::Applicability;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_basename_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.basename`.
|
||||
@@ -34,7 +36,16 @@ use ruff_python_ast::ExprCall;
|
||||
/// especially on older versions of Python.
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
/// This rule's fix is always marked as unsafe because the replacement is not always semantically
|
||||
/// equivalent to the original code. In particular, `pathlib` performs path normalization,
|
||||
/// which can alter the result compared to `os.path.basename`. For example, this normalization:
|
||||
///
|
||||
/// - Collapses consecutive slashes (e.g., `"a//b"` → `"a/b"`).
|
||||
/// - Removes trailing slashes (e.g., `"a/b/"` → `"a/b"`).
|
||||
/// - Eliminates `"."` (e.g., `"a/./b"` → `"a/b"`).
|
||||
///
|
||||
/// As a result, code relying on the exact string returned by `os.path.basename`
|
||||
/// may behave differently after the fix.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `PurePath.name`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.name)
|
||||
@@ -62,6 +73,7 @@ pub(crate) fn os_path_basename(checker: &Checker, call: &ExprCall, segments: &[&
|
||||
if segments != ["os", "path", "basename"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
@@ -69,5 +81,6 @@ pub(crate) fn os_path_basename(checker: &Checker, call: &ExprCall, segments: &[&
|
||||
"p",
|
||||
is_fix_os_path_basename_enabled(checker.settings()),
|
||||
OsPathBasename,
|
||||
Some(Applicability::Unsafe),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use ruff_diagnostics::Applicability;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_dirname_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.dirname`.
|
||||
@@ -29,7 +31,16 @@ use ruff_python_ast::ExprCall;
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix Safety
|
||||
/// This rule's fix is marked as unsafe if the replacement would remove comments attached to the original expression.
|
||||
/// This rule's fix is always marked as unsafe because the replacement is not always semantically
|
||||
/// equivalent to the original code. In particular, `pathlib` performs path normalization,
|
||||
/// which can alter the result compared to `os.path.dirname`. For example, this normalization:
|
||||
///
|
||||
/// - Collapses consecutive slashes (e.g., `"a//b"` → `"a/b"`).
|
||||
/// - Removes trailing slashes (e.g., `"a/b/"` → `"a/b"`).
|
||||
/// - Eliminates `"."` (e.g., `"a/./b"` → `"a/b"`).
|
||||
///
|
||||
/// As a result, code relying on the exact string returned by `os.path.dirname`
|
||||
/// may behave differently after the fix.
|
||||
///
|
||||
/// ## Known issues
|
||||
/// While using `pathlib` can improve the readability and type safety of your code,
|
||||
@@ -62,6 +73,7 @@ pub(crate) fn os_path_dirname(checker: &Checker, call: &ExprCall, segments: &[&s
|
||||
if segments != ["os", "path", "dirname"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
@@ -69,5 +81,6 @@ pub(crate) fn os_path_dirname(checker: &Checker, call: &ExprCall, segments: &[&s
|
||||
"p",
|
||||
is_fix_os_path_dirname_enabled(checker.settings()),
|
||||
OsPathDirname,
|
||||
Some(Applicability::Unsafe),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_exists_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.exists`.
|
||||
@@ -62,6 +63,7 @@ pub(crate) fn os_path_exists(checker: &Checker, call: &ExprCall, segments: &[&st
|
||||
if segments != ["os", "path", "exists"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
@@ -69,5 +71,6 @@ pub(crate) fn os_path_exists(checker: &Checker, call: &ExprCall, segments: &[&st
|
||||
"path",
|
||||
is_fix_os_path_exists_enabled(checker.settings()),
|
||||
OsPathExists,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_expanduser_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.expanduser`.
|
||||
@@ -62,6 +63,7 @@ pub(crate) fn os_path_expanduser(checker: &Checker, call: &ExprCall, segments: &
|
||||
if segments != ["os", "path", "expanduser"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
@@ -69,5 +71,6 @@ pub(crate) fn os_path_expanduser(checker: &Checker, call: &ExprCall, segments: &
|
||||
"path",
|
||||
is_fix_os_path_expanduser_enabled(checker.settings()),
|
||||
OsPathExpanduser,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_getatime_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.getatime`.
|
||||
@@ -65,6 +66,7 @@ pub(crate) fn os_path_getatime(checker: &Checker, call: &ExprCall, segments: &[&
|
||||
if segments != ["os", "path", "getatime"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
@@ -72,5 +74,6 @@ pub(crate) fn os_path_getatime(checker: &Checker, call: &ExprCall, segments: &[&
|
||||
"filename",
|
||||
is_fix_os_path_getatime_enabled(checker.settings()),
|
||||
OsPathGetatime,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_getctime_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.getctime`.
|
||||
@@ -66,6 +67,7 @@ pub(crate) fn os_path_getctime(checker: &Checker, call: &ExprCall, segments: &[&
|
||||
if segments != ["os", "path", "getctime"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
@@ -73,5 +75,6 @@ pub(crate) fn os_path_getctime(checker: &Checker, call: &ExprCall, segments: &[&
|
||||
"filename",
|
||||
is_fix_os_path_getctime_enabled(checker.settings()),
|
||||
OsPathGetctime,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_getmtime_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.getmtime`.
|
||||
@@ -66,6 +67,7 @@ pub(crate) fn os_path_getmtime(checker: &Checker, call: &ExprCall, segments: &[&
|
||||
if segments != ["os", "path", "getmtime"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
@@ -73,5 +75,6 @@ pub(crate) fn os_path_getmtime(checker: &Checker, call: &ExprCall, segments: &[&
|
||||
"filename",
|
||||
is_fix_os_path_getmtime_enabled(checker.settings()),
|
||||
OsPathGetmtime,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_getsize_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.getsize`.
|
||||
@@ -66,6 +67,7 @@ pub(crate) fn os_path_getsize(checker: &Checker, call: &ExprCall, segments: &[&s
|
||||
if segments != ["os", "path", "getsize"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
@@ -73,5 +75,6 @@ pub(crate) fn os_path_getsize(checker: &Checker, call: &ExprCall, segments: &[&s
|
||||
"filename",
|
||||
is_fix_os_path_getsize_enabled(checker.settings()),
|
||||
OsPathGetsize,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_isabs_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.isabs`.
|
||||
@@ -61,6 +62,7 @@ pub(crate) fn os_path_isabs(checker: &Checker, call: &ExprCall, segments: &[&str
|
||||
if segments != ["os", "path", "isabs"] {
|
||||
return;
|
||||
}
|
||||
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
@@ -68,5 +70,6 @@ pub(crate) fn os_path_isabs(checker: &Checker, call: &ExprCall, segments: &[&str
|
||||
"s",
|
||||
is_fix_os_path_isabs_enabled(checker.settings()),
|
||||
OsPathIsabs,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_isdir_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.isdir`.
|
||||
@@ -71,5 +72,6 @@ pub(crate) fn os_path_isdir(checker: &Checker, call: &ExprCall, segments: &[&str
|
||||
"s",
|
||||
is_fix_os_path_isdir_enabled(checker.settings()),
|
||||
OsPathIsdir,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_isfile_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.isfile`.
|
||||
@@ -71,5 +72,6 @@ pub(crate) fn os_path_isfile(checker: &Checker, call: &ExprCall, segments: &[&st
|
||||
"path",
|
||||
is_fix_os_path_isfile_enabled(checker.settings()),
|
||||
OsPathIsfile,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_islink_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.islink`.
|
||||
@@ -71,5 +72,6 @@ pub(crate) fn os_path_islink(checker: &Checker, call: &ExprCall, segments: &[&st
|
||||
"path",
|
||||
is_fix_os_path_islink_enabled(checker.settings()),
|
||||
OsPathIslink,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{ExprCall, PythonVersion};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_readlink_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::{
|
||||
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
|
||||
};
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{ExprCall, PythonVersion};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.readlink`.
|
||||
@@ -87,5 +88,6 @@ pub(crate) fn os_readlink(checker: &Checker, call: &ExprCall, segments: &[&str])
|
||||
"path",
|
||||
is_fix_os_readlink_enabled(checker.settings()),
|
||||
OsReadlink,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_remove_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::{
|
||||
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
|
||||
};
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.remove`.
|
||||
@@ -82,5 +83,6 @@ pub(crate) fn os_remove(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
"path",
|
||||
is_fix_os_remove_enabled(checker.settings()),
|
||||
OsRemove,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_rmdir_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::{
|
||||
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
|
||||
};
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.rmdir`.
|
||||
@@ -82,5 +83,6 @@ pub(crate) fn os_rmdir(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
"path",
|
||||
is_fix_os_rmdir_enabled(checker.settings()),
|
||||
OsRmdir,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_unlink_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::{
|
||||
check_os_pathlib_single_arg_calls, is_keyword_only_argument_non_default,
|
||||
};
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.unlink`.
|
||||
@@ -82,5 +83,6 @@ pub(crate) fn os_unlink(checker: &Checker, call: &ExprCall, segments: &[&str]) {
|
||||
"path",
|
||||
is_fix_os_unlink_enabled(checker.settings()),
|
||||
OsUnlink,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
---
|
||||
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:5:1
|
||||
|
|
||||
5 | os.symlink("usr/bin/python", "tmp/python")
|
||||
| ^^^^^^^^^^
|
||||
6 | os.symlink(b"usr/bin/python", b"tmp/python")
|
||||
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
2 | from pathlib import Path
|
||||
3 |
|
||||
4 |
|
||||
- os.symlink("usr/bin/python", "tmp/python")
|
||||
5 + Path("tmp/python").symlink_to("usr/bin/python")
|
||||
6 | os.symlink(b"usr/bin/python", b"tmp/python")
|
||||
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
||||
8 |
|
||||
|
||||
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:6:1
|
||||
|
|
||||
5 | os.symlink("usr/bin/python", "tmp/python")
|
||||
6 | os.symlink(b"usr/bin/python", b"tmp/python")
|
||||
| ^^^^^^^^^^
|
||||
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
3 |
|
||||
4 |
|
||||
5 | os.symlink("usr/bin/python", "tmp/python")
|
||||
- os.symlink(b"usr/bin/python", b"tmp/python")
|
||||
6 + Path(b"tmp/python").symlink_to(b"usr/bin/python")
|
||||
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
||||
8 |
|
||||
9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
|
||||
|
||||
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:9:1
|
||||
|
|
||||
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
||||
8 |
|
||||
9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
|
||||
| ^^^^^^^^^^
|
||||
10 | os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
|
||||
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
6 | os.symlink(b"usr/bin/python", b"tmp/python")
|
||||
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
||||
8 |
|
||||
- os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
|
||||
9 + Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True)
|
||||
10 | os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
|
||||
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
||||
12 |
|
||||
|
||||
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:10:1
|
||||
|
|
||||
9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
|
||||
10 | os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
|
||||
| ^^^^^^^^^^
|
||||
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
7 | Path("tmp/python").symlink_to("usr/bin/python") # Ok
|
||||
8 |
|
||||
9 | os.symlink("usr/bin/python", "tmp/python", target_is_directory=True)
|
||||
- os.symlink(b"usr/bin/python", b"tmp/python", target_is_directory=True)
|
||||
10 + Path(b"tmp/python").symlink_to(b"usr/bin/python", target_is_directory=True)
|
||||
11 | Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True) # Ok
|
||||
12 |
|
||||
13 | fd = os.open(".", os.O_RDONLY)
|
||||
|
||||
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:17:1
|
||||
|
|
||||
15 | os.close(fd)
|
||||
16 |
|
||||
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
|
||||
| ^^^^^^^^^^
|
||||
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
|
||||
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:18:1
|
||||
|
|
||||
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
|
||||
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
||||
| ^^^^^^^^^^
|
||||
19 |
|
||||
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
15 | os.close(fd)
|
||||
16 |
|
||||
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
|
||||
- os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
||||
18 + Path("tmp/python").symlink_to("usr/bin/python")
|
||||
19 |
|
||||
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
||||
21 |
|
||||
|
||||
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:20:1
|
||||
|
|
||||
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
||||
19 |
|
||||
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
||||
| ^^^^^^^^^^
|
||||
21 |
|
||||
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
17 | os.symlink(src="usr/bin/python", dst="tmp/python", unknown=True)
|
||||
18 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory=False)
|
||||
19 |
|
||||
- os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
||||
20 + Path("tmp/python").symlink_to("usr/bin/python")
|
||||
21 |
|
||||
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
||||
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
|
||||
|
||||
PTH211 [*] `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:22:1
|
||||
|
|
||||
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
||||
21 |
|
||||
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
||||
| ^^^^^^^^^^
|
||||
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
19 |
|
||||
20 | os.symlink(src="usr/bin/python", dst="tmp/python", dir_fd=None)
|
||||
21 |
|
||||
- os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
||||
22 + Path("tmp/python").symlink_to("usr/bin/python", target_is_directory=True)
|
||||
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
|
||||
|
||||
PTH211 `os.symlink` should be replaced by `Path.symlink_to`
|
||||
--> PTH211.py:23:1
|
||||
|
|
||||
22 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory= True )
|
||||
23 | os.symlink("usr/bin/python", dst="tmp/python", target_is_directory="nonboolean")
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
help: Replace with `Path(...).symlink_to(...)`
|
||||
@@ -466,6 +466,7 @@ help: Replace with `Path(...).name`
|
||||
30 | os.path.dirname(p)
|
||||
31 | os.path.samefile(p)
|
||||
32 | os.path.splitext(p)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PTH120 [*] `os.path.dirname()` should be replaced by `Path.parent`
|
||||
--> full_name.py:29:1
|
||||
@@ -493,6 +494,7 @@ help: Replace with `Path(...).parent`
|
||||
31 | os.path.samefile(p)
|
||||
32 | os.path.splitext(p)
|
||||
33 | with open(p) as fp:
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
--> full_name.py:30:1
|
||||
|
||||
@@ -466,6 +466,7 @@ help: Replace with `Path(...).name`
|
||||
30 | foo_p.dirname(p)
|
||||
31 | foo_p.samefile(p)
|
||||
32 | foo_p.splitext(p)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PTH120 [*] `os.path.dirname()` should be replaced by `Path.parent`
|
||||
--> import_as.py:29:1
|
||||
@@ -492,6 +493,7 @@ help: Replace with `Path(...).parent`
|
||||
30 + pathlib.Path(p).parent
|
||||
31 | foo_p.samefile(p)
|
||||
32 | foo_p.splitext(p)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
--> import_as.py:30:1
|
||||
|
||||
@@ -480,6 +480,7 @@ help: Replace with `Path(...).name`
|
||||
32 | dirname(p)
|
||||
33 | samefile(p)
|
||||
34 | splitext(p)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PTH120 [*] `os.path.dirname()` should be replaced by `Path.parent`
|
||||
--> import_from.py:31:1
|
||||
@@ -508,6 +509,7 @@ help: Replace with `Path(...).parent`
|
||||
33 | samefile(p)
|
||||
34 | splitext(p)
|
||||
35 | with open(p) as fp:
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
--> import_from.py:32:1
|
||||
|
||||
@@ -480,6 +480,7 @@ help: Replace with `Path(...).name`
|
||||
37 | xdirname(p)
|
||||
38 | xsamefile(p)
|
||||
39 | xsplitext(p)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PTH120 [*] `os.path.dirname()` should be replaced by `Path.parent`
|
||||
--> import_from_as.py:36:1
|
||||
@@ -507,6 +508,7 @@ help: Replace with `Path(...).parent`
|
||||
37 + pathlib.Path(p).parent
|
||||
38 | xsamefile(p)
|
||||
39 | xsplitext(p)
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PTH121 `os.path.samefile()` should be replaced by `Path.samefile()`
|
||||
--> import_from_as.py:37:1
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::iter;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@@ -101,7 +100,6 @@ pub(crate) fn categorize<'a>(
|
||||
no_sections: bool,
|
||||
section_order: &'a [ImportSection],
|
||||
default_section: &'a ImportSection,
|
||||
match_source_strategy: MatchSourceStrategy,
|
||||
) -> &'a ImportSection {
|
||||
let module_base = module_name.split('.').next().unwrap();
|
||||
let (mut import_type, mut reason) = {
|
||||
@@ -129,7 +127,7 @@ pub(crate) fn categorize<'a>(
|
||||
&ImportSection::Known(ImportType::FirstParty),
|
||||
Reason::SamePackage,
|
||||
)
|
||||
} else if let Some(src) = match_sources(src, module_name, match_source_strategy) {
|
||||
} else if let Some(src) = match_sources(src, module_name) {
|
||||
(
|
||||
&ImportSection::Known(ImportType::FirstParty),
|
||||
Reason::SourceMatch(src),
|
||||
@@ -161,61 +159,29 @@ fn same_package(package: Option<PackageRoot<'_>>, module_base: &str) -> bool {
|
||||
/// Returns the source path with respect to which the module `name`
|
||||
/// should be considered first party, or `None` if no path is found.
|
||||
///
|
||||
/// The [`MatchSourceStrategy`] is the criterion used to decide whether
|
||||
/// the module path matches a given source directory.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// - The module named `foo` will match `[SRC]` if `[SRC]/foo` is a directory,
|
||||
/// no matter the strategy.
|
||||
/// - The module named `foo` will match `[SRC]` if `[SRC]/foo` is a directory
|
||||
///
|
||||
/// - With `match_source_strategy == MatchSourceStrategy::Root`, the module
|
||||
/// named `foo.baz` will match `[SRC]` if `[SRC]/foo` is a
|
||||
/// directory or `[SRC]/foo.py` exists.
|
||||
///
|
||||
/// - With `match_source_stratgy == MatchSourceStrategy::FullPath`, the module
|
||||
/// named `foo.baz` will match `[SRC]` only if `[SRC]/foo/baz` is a directory,
|
||||
/// or `[SRC]/foo/baz.py` exists or `[SRC]/foo/baz.pyi` exists.
|
||||
fn match_sources<'a>(
|
||||
paths: &'a [PathBuf],
|
||||
name: &str,
|
||||
match_source_strategy: MatchSourceStrategy,
|
||||
) -> Option<&'a Path> {
|
||||
match match_source_strategy {
|
||||
MatchSourceStrategy::Root => {
|
||||
let base = name.split('.').next()?;
|
||||
for path in paths {
|
||||
if let Ok(metadata) = fs::metadata(path.join(base)) {
|
||||
if metadata.is_dir() {
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
if let Ok(metadata) = fs::metadata(path.join(format!("{base}.py"))) {
|
||||
if metadata.is_file() {
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
/// - The module named `foo.baz` will match `[SRC]` only if `[SRC]/foo/baz`
|
||||
/// is a directory, or `[SRC]/foo/baz.py` exists,
|
||||
/// or `[SRC]/foo/baz.pyi` exists.
|
||||
fn match_sources<'a>(paths: &'a [PathBuf], name: &str) -> Option<&'a Path> {
|
||||
let relative_path: PathBuf = name.split('.').collect();
|
||||
relative_path.components().next()?;
|
||||
for root in paths {
|
||||
let candidate = root.join(&relative_path);
|
||||
if candidate.is_dir() {
|
||||
return Some(root);
|
||||
}
|
||||
MatchSourceStrategy::FullPath => {
|
||||
let relative_path: PathBuf = name.split('.').collect();
|
||||
relative_path.components().next()?;
|
||||
for root in paths {
|
||||
let candidate = root.join(&relative_path);
|
||||
if candidate.is_dir() {
|
||||
return Some(root);
|
||||
}
|
||||
if ["py", "pyi"]
|
||||
.into_iter()
|
||||
.any(|extension| candidate.with_extension(extension).is_file())
|
||||
{
|
||||
return Some(root);
|
||||
}
|
||||
}
|
||||
None
|
||||
if ["py", "pyi"]
|
||||
.into_iter()
|
||||
.any(|extension| candidate.with_extension(extension).is_file())
|
||||
{
|
||||
return Some(root);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
@@ -229,7 +195,6 @@ pub(crate) fn categorize_imports<'a>(
|
||||
no_sections: bool,
|
||||
section_order: &'a [ImportSection],
|
||||
default_section: &'a ImportSection,
|
||||
match_source_strategy: MatchSourceStrategy,
|
||||
) -> BTreeMap<&'a ImportSection, ImportBlock<'a>> {
|
||||
let mut block_by_type: BTreeMap<&ImportSection, ImportBlock> = BTreeMap::default();
|
||||
// Categorize `Stmt::Import`.
|
||||
@@ -245,7 +210,6 @@ pub(crate) fn categorize_imports<'a>(
|
||||
no_sections,
|
||||
section_order,
|
||||
default_section,
|
||||
match_source_strategy,
|
||||
);
|
||||
block_by_type
|
||||
.entry(import_type)
|
||||
@@ -266,7 +230,6 @@ pub(crate) fn categorize_imports<'a>(
|
||||
no_sections,
|
||||
section_order,
|
||||
default_section,
|
||||
match_source_strategy,
|
||||
);
|
||||
block_by_type
|
||||
.entry(classification)
|
||||
@@ -287,7 +250,6 @@ pub(crate) fn categorize_imports<'a>(
|
||||
no_sections,
|
||||
section_order,
|
||||
default_section,
|
||||
match_source_strategy,
|
||||
);
|
||||
block_by_type
|
||||
.entry(classification)
|
||||
@@ -308,7 +270,6 @@ pub(crate) fn categorize_imports<'a>(
|
||||
no_sections,
|
||||
section_order,
|
||||
default_section,
|
||||
match_source_strategy,
|
||||
);
|
||||
block_by_type
|
||||
.entry(classification)
|
||||
@@ -463,25 +424,9 @@ impl fmt::Display for KnownModules {
|
||||
}
|
||||
}
|
||||
|
||||
/// Rule to determine whether a module path matches
|
||||
/// a relative path from a source directory.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) enum MatchSourceStrategy {
|
||||
/// Matches if first term in module path is found in file system
|
||||
///
|
||||
/// # Example
|
||||
/// Module is `foo.bar.baz` and `[SRC]/foo` exists
|
||||
Root,
|
||||
/// Matches only if full module path is reflected in file system
|
||||
///
|
||||
/// # Example
|
||||
/// Module is `foo.bar.baz` and `[SRC]/foo/bar/baz` exists
|
||||
FullPath,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::rules::isort::categorize::{MatchSourceStrategy, match_sources};
|
||||
use crate::rules::isort::categorize::match_sources;
|
||||
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -522,49 +467,17 @@ mod tests {
|
||||
|
||||
let paths = vec![project_dir.clone()];
|
||||
|
||||
// Test with Root strategy
|
||||
|
||||
assert_eq!(
|
||||
match_sources(&paths, "mypackage", MatchSourceStrategy::Root),
|
||||
match_sources(&paths, "mypackage"),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
match_sources(&paths, "mypackage.module1", MatchSourceStrategy::Root),
|
||||
match_sources(&paths, "mypackage.module1"),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
match_sources(&paths, "mypackage.nonexistent", MatchSourceStrategy::Root),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
match_sources(&paths, "nonexistent", MatchSourceStrategy::Root),
|
||||
None
|
||||
);
|
||||
|
||||
// Test with FullPath strategy
|
||||
|
||||
assert_eq!(
|
||||
match_sources(&paths, "mypackage", MatchSourceStrategy::FullPath),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
match_sources(&paths, "mypackage.module1", MatchSourceStrategy::FullPath),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
|
||||
// Differs in behavior from [`MatchSourceStrategy::Root`]
|
||||
assert_eq!(
|
||||
match_sources(
|
||||
&paths,
|
||||
"mypackage.nonexistent",
|
||||
MatchSourceStrategy::FullPath
|
||||
),
|
||||
None
|
||||
);
|
||||
assert_eq!(match_sources(&paths, "mypackage.nonexistent",), None);
|
||||
}
|
||||
|
||||
/// Tests a src-based Python package layout:
|
||||
@@ -588,39 +501,12 @@ mod tests {
|
||||
|
||||
let paths = vec![src_dir.clone()];
|
||||
|
||||
// Test with Root strategy
|
||||
|
||||
assert_eq!(
|
||||
match_sources(&paths, "mypackage", MatchSourceStrategy::Root),
|
||||
match_sources(&paths, "mypackage.module1"),
|
||||
Some(src_dir.as_path())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
match_sources(&paths, "mypackage.module1", MatchSourceStrategy::Root),
|
||||
Some(src_dir.as_path())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
match_sources(&paths, "mypackage.nonexistent", MatchSourceStrategy::Root),
|
||||
Some(src_dir.as_path())
|
||||
);
|
||||
|
||||
// Test with FullPath strategy
|
||||
|
||||
assert_eq!(
|
||||
match_sources(&paths, "mypackage.module1", MatchSourceStrategy::FullPath),
|
||||
Some(src_dir.as_path())
|
||||
);
|
||||
|
||||
// Differs in behavior from [`MatchSourceStrategy::Root`]
|
||||
assert_eq!(
|
||||
match_sources(
|
||||
&paths,
|
||||
"mypackage.nonexistent",
|
||||
MatchSourceStrategy::FullPath
|
||||
),
|
||||
None
|
||||
);
|
||||
assert_eq!(match_sources(&paths, "mypackage.nonexistent"), None);
|
||||
}
|
||||
|
||||
/// Tests a nested package layout:
|
||||
@@ -647,35 +533,13 @@ mod tests {
|
||||
|
||||
let paths = vec![project_dir.clone()];
|
||||
|
||||
// Test with Root strategy
|
||||
assert_eq!(
|
||||
match_sources(&paths, "mypackage", MatchSourceStrategy::Root),
|
||||
match_sources(&paths, "mypackage.subpackage.module2"),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
match_sources(&paths, "mypackage.subpackage", MatchSourceStrategy::Root),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
|
||||
// Test with FullPath strategy
|
||||
|
||||
assert_eq!(
|
||||
match_sources(
|
||||
&paths,
|
||||
"mypackage.subpackage.module2",
|
||||
MatchSourceStrategy::FullPath
|
||||
),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
|
||||
// Differs in behavior from [`MatchSourceStrategy::Root`]
|
||||
assert_eq!(
|
||||
match_sources(
|
||||
&paths,
|
||||
"mypackage.subpackage.nonexistent",
|
||||
MatchSourceStrategy::FullPath
|
||||
),
|
||||
match_sources(&paths, "mypackage.subpackage.nonexistent"),
|
||||
None
|
||||
);
|
||||
}
|
||||
@@ -699,52 +563,17 @@ mod tests {
|
||||
create_file(project_dir.join("namespace/package1/module1.py"));
|
||||
|
||||
let paths = vec![project_dir.clone()];
|
||||
// Test with Root strategy
|
||||
|
||||
assert_eq!(
|
||||
match_sources(&paths, "namespace", MatchSourceStrategy::Root),
|
||||
match_sources(&paths, "namespace.package1"),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
match_sources(&paths, "namespace.package1", MatchSourceStrategy::Root),
|
||||
match_sources(&paths, "namespace.package1.module1"),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
match_sources(
|
||||
&paths,
|
||||
"namespace.package2.module1",
|
||||
MatchSourceStrategy::Root
|
||||
),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
|
||||
// Test with FullPath strategy
|
||||
|
||||
assert_eq!(
|
||||
match_sources(&paths, "namespace.package1", MatchSourceStrategy::FullPath),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
match_sources(
|
||||
&paths,
|
||||
"namespace.package1.module1",
|
||||
MatchSourceStrategy::FullPath
|
||||
),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
|
||||
// Differs in behavior from [`MatchSourceStrategy::Root`]
|
||||
assert_eq!(
|
||||
match_sources(
|
||||
&paths,
|
||||
"namespace.package2.module1",
|
||||
MatchSourceStrategy::FullPath
|
||||
),
|
||||
None
|
||||
);
|
||||
assert_eq!(match_sources(&paths, "namespace.package2.module1"), None);
|
||||
}
|
||||
|
||||
/// Tests a package with type stubs (.pyi files):
|
||||
@@ -764,12 +593,11 @@ mod tests {
|
||||
create_file(project_dir.join("mypackage/__init__.py"));
|
||||
create_file(project_dir.join("mypackage/module1.pyi")); // Only create .pyi file, not .py
|
||||
|
||||
// Test with FullPath strategy
|
||||
let paths = vec![project_dir.clone()];
|
||||
|
||||
// Module "mypackage.module1" should match project_dir using .pyi file
|
||||
assert_eq!(
|
||||
match_sources(&paths, "mypackage.module1", MatchSourceStrategy::FullPath),
|
||||
match_sources(&paths, "mypackage.module1"),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
}
|
||||
@@ -796,30 +624,17 @@ mod tests {
|
||||
create_file(project_dir.join("mypackage/feature/__init__.py"));
|
||||
create_file(project_dir.join("mypackage/feature/submodule.py"));
|
||||
|
||||
// Test with Root strategy
|
||||
let paths = vec![project_dir.clone()];
|
||||
|
||||
// Module "mypackage.feature" should match project_dir (matches the file first)
|
||||
assert_eq!(
|
||||
match_sources(&paths, "mypackage.feature", MatchSourceStrategy::Root),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
|
||||
// Test with FullPath strategy
|
||||
|
||||
// Module "mypackage.feature" should match project_dir
|
||||
assert_eq!(
|
||||
match_sources(&paths, "mypackage.feature", MatchSourceStrategy::FullPath),
|
||||
match_sources(&paths, "mypackage.feature"),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
|
||||
// Module "mypackage.feature.submodule" should match project_dir
|
||||
assert_eq!(
|
||||
match_sources(
|
||||
&paths,
|
||||
"mypackage.feature.submodule",
|
||||
MatchSourceStrategy::FullPath
|
||||
),
|
||||
match_sources(&paths, "mypackage.feature.submodule"),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
}
|
||||
@@ -857,13 +672,13 @@ mod tests {
|
||||
|
||||
// Module "package1" should match project1_dir
|
||||
assert_eq!(
|
||||
match_sources(&paths, "package1", MatchSourceStrategy::Root),
|
||||
match_sources(&paths, "package1"),
|
||||
Some(project1_dir.as_path())
|
||||
);
|
||||
|
||||
// Module "package2" should match project2_dir
|
||||
assert_eq!(
|
||||
match_sources(&paths, "package2", MatchSourceStrategy::Root),
|
||||
match_sources(&paths, "package2"),
|
||||
Some(project2_dir.as_path())
|
||||
);
|
||||
|
||||
@@ -872,7 +687,7 @@ mod tests {
|
||||
|
||||
// Module "package1" should still match project1_dir
|
||||
assert_eq!(
|
||||
match_sources(&paths_reversed, "package1", MatchSourceStrategy::Root),
|
||||
match_sources(&paths_reversed, "package1"),
|
||||
Some(project1_dir.as_path())
|
||||
);
|
||||
}
|
||||
@@ -885,8 +700,7 @@ mod tests {
|
||||
///
|
||||
/// In theory this should never happen since we expect
|
||||
/// module names to have been normalized by the time we
|
||||
/// call `match_sources`. But it is worth noting that the
|
||||
/// behavior is different depending on the [`MatchSourceStrategy`]
|
||||
/// call `match_sources`.
|
||||
#[test]
|
||||
fn test_empty_module_name() {
|
||||
let temp_dir = tempdir().unwrap();
|
||||
@@ -894,16 +708,9 @@ mod tests {
|
||||
|
||||
create_dir(project_dir.join("mypackage"));
|
||||
|
||||
let paths = vec![project_dir.clone()];
|
||||
let paths = vec![project_dir];
|
||||
|
||||
assert_eq!(
|
||||
match_sources(&paths, "", MatchSourceStrategy::Root),
|
||||
Some(project_dir.as_path())
|
||||
);
|
||||
assert_eq!(
|
||||
match_sources(&paths, "", MatchSourceStrategy::FullPath),
|
||||
None
|
||||
);
|
||||
assert_eq!(match_sources(&paths, ""), None);
|
||||
}
|
||||
|
||||
/// Tests behavior with an empty list of source paths
|
||||
@@ -911,14 +718,6 @@ mod tests {
|
||||
fn test_empty_paths() {
|
||||
let paths: Vec<PathBuf> = vec![];
|
||||
|
||||
// Empty paths should return None
|
||||
assert_eq!(
|
||||
match_sources(&paths, "mypackage", MatchSourceStrategy::Root),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
match_sources(&paths, "mypackage", MatchSourceStrategy::FullPath),
|
||||
None
|
||||
);
|
||||
assert_eq!(match_sources(&paths, "mypackage"), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ use std::path::PathBuf;
|
||||
use annotate::annotate_imports;
|
||||
use block::{Block, Trailer};
|
||||
pub(crate) use categorize::categorize;
|
||||
use categorize::categorize_imports;
|
||||
pub use categorize::{ImportSection, ImportType};
|
||||
use categorize::{MatchSourceStrategy, categorize_imports};
|
||||
use comments::Comment;
|
||||
use normalize::normalize_imports;
|
||||
use order::order_imports;
|
||||
@@ -76,7 +76,6 @@ pub(crate) fn format_imports(
|
||||
source_type: PySourceType,
|
||||
target_version: PythonVersion,
|
||||
settings: &Settings,
|
||||
match_source_strategy: MatchSourceStrategy,
|
||||
tokens: &Tokens,
|
||||
) -> String {
|
||||
let trailer = &block.trailer;
|
||||
@@ -104,7 +103,6 @@ pub(crate) fn format_imports(
|
||||
package,
|
||||
target_version,
|
||||
settings,
|
||||
match_source_strategy,
|
||||
);
|
||||
|
||||
if !block_output.is_empty() && !output.is_empty() {
|
||||
@@ -161,7 +159,6 @@ fn format_import_block(
|
||||
package: Option<PackageRoot<'_>>,
|
||||
target_version: PythonVersion,
|
||||
settings: &Settings,
|
||||
match_source_strategy: MatchSourceStrategy,
|
||||
) -> String {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum LineInsertion {
|
||||
@@ -182,7 +179,6 @@ fn format_import_block(
|
||||
settings.no_sections,
|
||||
&settings.section_order,
|
||||
&settings.default_section,
|
||||
match_source_strategy,
|
||||
);
|
||||
|
||||
let mut output = String::new();
|
||||
|
||||
@@ -14,9 +14,7 @@ use crate::Locator;
|
||||
use crate::checkers::ast::LintContext;
|
||||
use crate::line_width::LineWidthBuilder;
|
||||
use crate::package::PackageRoot;
|
||||
use crate::preview::is_full_path_match_source_strategy_enabled;
|
||||
use crate::rules::isort::block::Block;
|
||||
use crate::rules::isort::categorize::MatchSourceStrategy;
|
||||
use crate::rules::isort::{comments, format_imports};
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
@@ -40,12 +38,6 @@ use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
/// import pandas
|
||||
/// ```
|
||||
///
|
||||
/// ## Preview
|
||||
/// When [`preview`](https://docs.astral.sh/ruff/preview/) mode is enabled, Ruff applies a stricter criterion
|
||||
/// for determining whether an import should be classified as first-party.
|
||||
/// Specifically, for an import of the form `import foo.bar.baz`, Ruff will
|
||||
/// check that `foo/bar`, relative to a [user-specified `src`](https://docs.astral.sh/ruff/settings/#src) directory, contains either
|
||||
/// the directory `baz` or else a file with the name `baz.py` or `baz.pyi`.
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct UnsortedImports;
|
||||
|
||||
@@ -129,12 +121,6 @@ pub(crate) fn organize_imports(
|
||||
trailing_lines_end(block.imports.last().unwrap(), locator.contents())
|
||||
};
|
||||
|
||||
let match_source_strategy = if is_full_path_match_source_strategy_enabled(settings) {
|
||||
MatchSourceStrategy::FullPath
|
||||
} else {
|
||||
MatchSourceStrategy::Root
|
||||
};
|
||||
|
||||
// Generate the sorted import block.
|
||||
let expected = format_imports(
|
||||
block,
|
||||
@@ -148,7 +134,6 @@ pub(crate) fn organize_imports(
|
||||
source_type,
|
||||
target_version,
|
||||
&settings.isort,
|
||||
match_source_strategy,
|
||||
tokens,
|
||||
);
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ use ruff_text_size::Ranged;
|
||||
|
||||
use crate::{Violation, checkers::ast::Checker};
|
||||
|
||||
/// ## Deprecated
|
||||
/// ## Removed
|
||||
///
|
||||
/// This rule has been deprecated as it's highly opinionated and overly strict in most cases.
|
||||
/// This rule has been removed as it's highly opinionated and overly strict in most cases.
|
||||
///
|
||||
/// ## What it does
|
||||
/// Checks for assignments to the variable `df`.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user