Compare commits
84 Commits
david/fix-
...
david/sign
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a3101b0be | ||
|
|
0f371f6efd | ||
|
|
cbaac56cf1 | ||
|
|
9cb37db510 | ||
|
|
ed06fb5ce2 | ||
|
|
54df73c9f7 | ||
|
|
d7524ea6d4 | ||
|
|
bf66178959 | ||
|
|
9cdac2d6fb | ||
|
|
79706a2e26 | ||
|
|
25853e2377 | ||
|
|
99ecb657f6 | ||
|
|
61f906d8e7 | ||
|
|
08a561fc05 | ||
|
|
aa5d665d52 | ||
|
|
d55edb3d74 | ||
|
|
ab86ae1760 | ||
|
|
31566d67cb | ||
|
|
f0b0d2ef87 | ||
|
|
916968d0ff | ||
|
|
deb3d3d150 | ||
|
|
4c7f7d199b | ||
|
|
a66add41be | ||
|
|
982a0a2a7c | ||
|
|
f893b19930 | ||
|
|
c96ebe3936 | ||
|
|
22ca5dd890 | ||
|
|
f7995f4aef | ||
|
|
480fb278d0 | ||
|
|
b2f364d9cb | ||
|
|
aa82137d9f | ||
|
|
adfe2438e9 | ||
|
|
3247991429 | ||
|
|
08fcf7e106 | ||
|
|
2467c4352e | ||
|
|
a27c64811e | ||
|
|
4064fa28fc | ||
|
|
a51982bac6 | ||
|
|
a27013bdd3 | ||
|
|
5d52902e18 | ||
|
|
c24236cc73 | ||
|
|
eb6154f792 | ||
|
|
fdfb51b595 | ||
|
|
7ee863b6d7 | ||
|
|
8ade6c4eaf | ||
|
|
9e45bfa9fd | ||
|
|
7509d376eb | ||
|
|
a24a4b55ee | ||
|
|
888a22e849 | ||
|
|
08c1d3660c | ||
|
|
0b19caedec | ||
|
|
670fffef37 | ||
|
|
178f48fc0b | ||
|
|
f34b6d8245 | ||
|
|
8dd183e55c | ||
|
|
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:
|
||||
|
||||
28
CHANGELOG.md
28
CHANGELOG.md
@@ -1,5 +1,33 @@
|
||||
# Changelog
|
||||
|
||||
## 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))
|
||||
|
||||
## 0.12.11
|
||||
|
||||
### Preview features
|
||||
|
||||
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.12.12"
|
||||
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.12.12"
|
||||
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.12.12"
|
||||
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.12.12/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.12.12/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.12.12
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.12.11"
|
||||
version = "0.12.12"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -232,7 +232,7 @@ static STATIC_FRAME: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLo
|
||||
max_dep_date: "2025-08-09",
|
||||
python_version: PythonVersion::PY311,
|
||||
},
|
||||
500,
|
||||
600,
|
||||
)
|
||||
});
|
||||
|
||||
|
||||
@@ -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.12.12"
|
||||
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
|
||||
@@ -9,6 +9,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_unnecessary_default_type_args_stubs_enabled,
|
||||
};
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::{
|
||||
@@ -142,7 +143,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);
|
||||
}
|
||||
}
|
||||
@@ -1319,13 +1323,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(_)
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -260,3 +260,10 @@ pub(crate) const fn is_maxsplit_without_separator_fix_enabled(settings: &LinterS
|
||||
pub(crate) const fn is_bidi_forbid_arabic_letter_mark_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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("__")
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -141,7 +141,6 @@ impl Violation for InvalidFirstArgumentNameForClassMethod {
|
||||
#[derive_message_formats]
|
||||
// The first string below is what shows up in the documentation
|
||||
// in the rule table, and it is the more common case.
|
||||
#[expect(clippy::if_not_else)]
|
||||
fn message(&self) -> String {
|
||||
if !self.is_new {
|
||||
"First argument of a class method should be named `cls`".to_string()
|
||||
|
||||
@@ -18,8 +18,8 @@ use crate::rules::pep8_naming::helpers;
|
||||
/// > (Let’s hope that these variables are meant for use inside one module
|
||||
/// > only.) The conventions are about the same as those for functions.
|
||||
/// >
|
||||
/// > Modules that are designed for use via from M import * should use the
|
||||
/// > __all__ mechanism to prevent exporting globals, or use the older
|
||||
/// > Modules that are designed for use via `from M import *` should use the
|
||||
/// > `__all__` mechanism to prevent exporting globals, or use the older
|
||||
/// > convention of prefixing such globals with an underscore (which you might
|
||||
/// > want to do to indicate these globals are “module non-public”).
|
||||
/// >
|
||||
|
||||
@@ -47,7 +47,6 @@ impl Violation for UselessImportAlias {
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
#[expect(clippy::if_not_else)]
|
||||
if !self.required_import_conflict {
|
||||
"Import alias does not rename original package".to_string()
|
||||
} else {
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{self as ast};
|
||||
use ruff_python_semantic::ScopeKind;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `yield from` in async functions.
|
||||
@@ -36,13 +32,3 @@ impl Violation for YieldFromInAsyncFunction {
|
||||
"`yield from` statement in async function; use `async for` instead".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// PLE1700
|
||||
pub(crate) fn yield_from_in_async_function(checker: &Checker, expr: &ast::ExprYieldFrom) {
|
||||
if matches!(
|
||||
checker.semantic().current_scope().kind,
|
||||
ScopeKind::Function(ast::StmtFunctionDef { is_async: true, .. })
|
||||
) {
|
||||
checker.report_diagnostic(YieldFromInAsyncFunction, expr.range());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,4 +357,19 @@ mod tests {
|
||||
2 | from pipes import quote, Template
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unnecessary_default_type_args_stubs_py312_preview() -> Result<()> {
|
||||
let snapshot = format!("{}__preview", "UP043.pyi");
|
||||
let diagnostics = test_path(
|
||||
Path::new("pyupgrade/UP043.pyi"),
|
||||
&settings::LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
unresolved_target_version: PythonVersion::PY312.into(),
|
||||
..settings::LinterSettings::for_rule(Rule::UnnecessaryDefaultTypeArgs)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ fn remove_specifiers<'a>(value: &mut Expression<'a>, arena: &'a typed_arena::Are
|
||||
stack.push(&mut string.left);
|
||||
stack.push(&mut string.right);
|
||||
}
|
||||
libcst_native::String::Formatted(_) => {}
|
||||
libcst_native::String::Formatted(_) | libcst_native::String::Templated(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use ruff_diagnostics::Applicability;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::visitor::{Visitor, walk_expr, walk_stmt};
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -94,14 +96,22 @@ pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall
|
||||
};
|
||||
|
||||
// Find the enclosing function definition (if any).
|
||||
let Some(Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
parameters: parent_parameters,
|
||||
..
|
||||
})) = parents.find(|stmt| stmt.is_function_def_stmt())
|
||||
let Some(
|
||||
func_stmt @ Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||
parameters: parent_parameters,
|
||||
..
|
||||
}),
|
||||
) = parents.find(|stmt| stmt.is_function_def_stmt())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if is_builtins_super(checker.semantic(), call)
|
||||
&& !has_local_dunder_class_var_ref(checker.semantic(), func_stmt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract the name of the first argument to the enclosing function.
|
||||
let Some(parent_arg) = parent_parameters.args.first() else {
|
||||
return;
|
||||
@@ -193,3 +203,67 @@ pub(crate) fn super_call_with_parameters(checker: &Checker, call: &ast::ExprCall
|
||||
fn is_super_call_with_arguments(call: &ast::ExprCall, checker: &Checker) -> bool {
|
||||
checker.semantic().match_builtin_expr(&call.func, "super") && !call.arguments.is_empty()
|
||||
}
|
||||
|
||||
/// Returns `true` if the function contains load references to `__class__` or `super` without
|
||||
/// local binding.
|
||||
///
|
||||
/// This indicates that the function relies on the implicit `__class__` cell variable created by
|
||||
/// Python when `super()` is called without arguments, making it unsafe to remove `super()` parameters.
|
||||
fn has_local_dunder_class_var_ref(semantic: &SemanticModel, func_stmt: &Stmt) -> bool {
|
||||
if semantic.current_scope().has("__class__") {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut finder = ClassCellReferenceFinder::new();
|
||||
finder.visit_stmt(func_stmt);
|
||||
|
||||
finder.found()
|
||||
}
|
||||
|
||||
/// Returns `true` if the call is to the built-in `builtins.super` function.
|
||||
fn is_builtins_super(semantic: &SemanticModel, call: &ast::ExprCall) -> bool {
|
||||
semantic
|
||||
.resolve_qualified_name(&call.func)
|
||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["builtins", "super"]))
|
||||
}
|
||||
|
||||
/// A [`Visitor`] that searches for implicit reference to `__class__` cell,
|
||||
/// excluding nested class definitions.
|
||||
#[derive(Debug)]
|
||||
struct ClassCellReferenceFinder {
|
||||
has_class_cell: bool,
|
||||
}
|
||||
|
||||
impl ClassCellReferenceFinder {
|
||||
pub(crate) fn new() -> Self {
|
||||
ClassCellReferenceFinder {
|
||||
has_class_cell: false,
|
||||
}
|
||||
}
|
||||
pub(crate) fn found(&self) -> bool {
|
||||
self.has_class_cell
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Visitor<'a> for ClassCellReferenceFinder {
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
match stmt {
|
||||
Stmt::ClassDef(_) => {}
|
||||
_ => {
|
||||
if !self.has_class_cell {
|
||||
walk_stmt(self, stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||
if expr.as_name_expr().is_some_and(|name| {
|
||||
matches!(name.id.as_str(), "super" | "__class__") && name.ctx.is_load()
|
||||
}) {
|
||||
self.has_class_cell = true;
|
||||
return;
|
||||
}
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::{AlwaysFixableViolation, Applicability, Edit, Fix};
|
||||
/// ## What it does
|
||||
/// Checks for unnecessary default type arguments for `Generator` and
|
||||
/// `AsyncGenerator` on Python 3.13+.
|
||||
/// In [preview], this rule will also apply to stub files.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Python 3.13 introduced the ability for type parameters to specify default
|
||||
@@ -59,6 +60,8 @@ use crate::{AlwaysFixableViolation, Applicability, Edit, Fix};
|
||||
/// - [Annotating generators and coroutines](https://docs.python.org/3/library/typing.html#annotating-generators-and-coroutines)
|
||||
/// - [Python documentation: `typing.Generator`](https://docs.python.org/3/library/typing.html#typing.Generator)
|
||||
/// - [Python documentation: `typing.AsyncGenerator`](https://docs.python.org/3/library/typing.html#typing.AsyncGenerator)
|
||||
///
|
||||
/// [preview]: https://docs.astral.sh/ruff/preview/
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct UnnecessaryDefaultTypeArgs;
|
||||
|
||||
|
||||
@@ -146,25 +146,6 @@ help: Remove `super()` parameters
|
||||
95 | # see: https://github.com/astral-sh/ruff/issues/18684
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:107:23
|
||||
|
|
||||
105 | class C:
|
||||
106 | def f(self):
|
||||
107 | builtins.super(C, self)
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
104 |
|
||||
105 | class C:
|
||||
106 | def f(self):
|
||||
- builtins.super(C, self)
|
||||
107 + builtins.super()
|
||||
108 |
|
||||
109 |
|
||||
110 | # see: https://github.com/astral-sh/ruff/issues/18533
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:113:14
|
||||
|
|
||||
@@ -294,6 +275,8 @@ UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
142 | def method3(self):
|
||||
143 | super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
144 |
|
||||
145 | # See: https://github.com/astral-sh/ruff/issues/19357
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
140 | super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed
|
||||
@@ -301,4 +284,213 @@ help: Remove `super()` parameters
|
||||
142 | def method3(self):
|
||||
- super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords
|
||||
143 + super().some_method() # Should be fixed - no keywords
|
||||
144 |
|
||||
145 | # See: https://github.com/astral-sh/ruff/issues/19357
|
||||
146 | # Must be detected
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:154:23
|
||||
|
|
||||
152 | def f(self):
|
||||
153 | if False: __class__ # Python injects __class__ into scope
|
||||
154 | builtins.super(ChildD1, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
155 |
|
||||
156 | class ChildD2(ParentD):
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
151 | class ChildD1(ParentD):
|
||||
152 | def f(self):
|
||||
153 | if False: __class__ # Python injects __class__ into scope
|
||||
- builtins.super(ChildD1, self).f()
|
||||
154 + builtins.super().f()
|
||||
155 |
|
||||
156 | class ChildD2(ParentD):
|
||||
157 | def f(self):
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:159:23
|
||||
|
|
||||
157 | def f(self):
|
||||
158 | if False: super # Python injects __class__ into scope
|
||||
159 | builtins.super(ChildD2, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
160 |
|
||||
161 | class ChildD3(ParentD):
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
156 | class ChildD2(ParentD):
|
||||
157 | def f(self):
|
||||
158 | if False: super # Python injects __class__ into scope
|
||||
- builtins.super(ChildD2, self).f()
|
||||
159 + builtins.super().f()
|
||||
160 |
|
||||
161 | class ChildD3(ParentD):
|
||||
162 | def f(self):
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:163:23
|
||||
|
|
||||
161 | class ChildD3(ParentD):
|
||||
162 | def f(self):
|
||||
163 | builtins.super(ChildD3, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
164 | super # Python injects __class__ into scope
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
160 |
|
||||
161 | class ChildD3(ParentD):
|
||||
162 | def f(self):
|
||||
- builtins.super(ChildD3, self).f()
|
||||
163 + builtins.super().f()
|
||||
164 | super # Python injects __class__ into scope
|
||||
165 |
|
||||
166 | import builtins as builtins_alias
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:169:29
|
||||
|
|
||||
167 | class ChildD4(ParentD):
|
||||
168 | def f(self):
|
||||
169 | builtins_alias.super(ChildD4, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
170 | super # Python injects __class__ into scope
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
166 | import builtins as builtins_alias
|
||||
167 | class ChildD4(ParentD):
|
||||
168 | def f(self):
|
||||
- builtins_alias.super(ChildD4, self).f()
|
||||
169 + builtins_alias.super().f()
|
||||
170 | super # Python injects __class__ into scope
|
||||
171 |
|
||||
172 | class ChildD5(ParentD):
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:176:23
|
||||
|
|
||||
174 | super = 1
|
||||
175 | super # Python injects __class__ into scope
|
||||
176 | builtins.super(ChildD5, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
177 |
|
||||
178 | class ChildD6(ParentD):
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
173 | def f(self):
|
||||
174 | super = 1
|
||||
175 | super # Python injects __class__ into scope
|
||||
- builtins.super(ChildD5, self).f()
|
||||
176 + builtins.super().f()
|
||||
177 |
|
||||
178 | class ChildD6(ParentD):
|
||||
179 | def f(self):
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:182:23
|
||||
|
|
||||
180 | super: "Any"
|
||||
181 | __class__ # Python injects __class__ into scope
|
||||
182 | builtins.super(ChildD6, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
183 |
|
||||
184 | class ChildD7(ParentD):
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
179 | def f(self):
|
||||
180 | super: "Any"
|
||||
181 | __class__ # Python injects __class__ into scope
|
||||
- builtins.super(ChildD6, self).f()
|
||||
182 + builtins.super().f()
|
||||
183 |
|
||||
184 | class ChildD7(ParentD):
|
||||
185 | def f(self):
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:188:23
|
||||
|
|
||||
186 | def x():
|
||||
187 | __class__ # Python injects __class__ into scope
|
||||
188 | builtins.super(ChildD7, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
189 |
|
||||
190 | class ChildD8(ParentD):
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
185 | def f(self):
|
||||
186 | def x():
|
||||
187 | __class__ # Python injects __class__ into scope
|
||||
- builtins.super(ChildD7, self).f()
|
||||
188 + builtins.super().f()
|
||||
189 |
|
||||
190 | class ChildD8(ParentD):
|
||||
191 | def f(self):
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:195:23
|
||||
|
|
||||
193 | super = 1
|
||||
194 | super # Python injects __class__ into scope
|
||||
195 | builtins.super(ChildD8, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
196 |
|
||||
197 | class ChildD9(ParentD):
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
192 | def x():
|
||||
193 | super = 1
|
||||
194 | super # Python injects __class__ into scope
|
||||
- builtins.super(ChildD8, self).f()
|
||||
195 + builtins.super().f()
|
||||
196 |
|
||||
197 | class ChildD9(ParentD):
|
||||
198 | def f(self):
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:202:23
|
||||
|
|
||||
200 | __class__ = 1
|
||||
201 | __class__ # Python injects __class__ into scope
|
||||
202 | builtins.super(ChildD9, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
203 |
|
||||
204 | class ChildD10(ParentD):
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
199 | def x():
|
||||
200 | __class__ = 1
|
||||
201 | __class__ # Python injects __class__ into scope
|
||||
- builtins.super(ChildD9, self).f()
|
||||
202 + builtins.super().f()
|
||||
203 |
|
||||
204 | class ChildD10(ParentD):
|
||||
205 | def f(self):
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:209:23
|
||||
|
|
||||
207 | __class__ = 1
|
||||
208 | super # Python injects __class__ into scope
|
||||
209 | builtins.super(ChildD10, self).f()
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
206 | def x():
|
||||
207 | __class__ = 1
|
||||
208 | super # Python injects __class__ into scope
|
||||
- builtins.super(ChildD10, self).f()
|
||||
209 + builtins.super().f()
|
||||
210 |
|
||||
211 |
|
||||
212 | # Must be ignored
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -139,24 +139,6 @@ help: Remove `super()` parameters
|
||||
94 |
|
||||
95 | # see: https://github.com/astral-sh/ruff/issues/18684
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:107:23
|
||||
|
|
||||
105 | class C:
|
||||
106 | def f(self):
|
||||
107 | builtins.super(C, self)
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
104 |
|
||||
105 | class C:
|
||||
106 | def f(self):
|
||||
- builtins.super(C, self)
|
||||
107 + builtins.super()
|
||||
108 |
|
||||
109 |
|
||||
110 | # see: https://github.com/astral-sh/ruff/issues/18533
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:113:14
|
||||
|
|
||||
@@ -286,6 +268,8 @@ UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
142 | def method3(self):
|
||||
143 | super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
144 |
|
||||
145 | # See: https://github.com/astral-sh/ruff/issues/19357
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
140 | super(ExampleWithKeywords, self, **{"kwarg": "value"}).some_method() # Should emit diagnostic but NOT be fixed
|
||||
@@ -293,3 +277,202 @@ help: Remove `super()` parameters
|
||||
142 | def method3(self):
|
||||
- super(ExampleWithKeywords, self).some_method() # Should be fixed - no keywords
|
||||
143 + super().some_method() # Should be fixed - no keywords
|
||||
144 |
|
||||
145 | # See: https://github.com/astral-sh/ruff/issues/19357
|
||||
146 | # Must be detected
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:154:23
|
||||
|
|
||||
152 | def f(self):
|
||||
153 | if False: __class__ # Python injects __class__ into scope
|
||||
154 | builtins.super(ChildD1, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
155 |
|
||||
156 | class ChildD2(ParentD):
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
151 | class ChildD1(ParentD):
|
||||
152 | def f(self):
|
||||
153 | if False: __class__ # Python injects __class__ into scope
|
||||
- builtins.super(ChildD1, self).f()
|
||||
154 + builtins.super().f()
|
||||
155 |
|
||||
156 | class ChildD2(ParentD):
|
||||
157 | def f(self):
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:159:23
|
||||
|
|
||||
157 | def f(self):
|
||||
158 | if False: super # Python injects __class__ into scope
|
||||
159 | builtins.super(ChildD2, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
160 |
|
||||
161 | class ChildD3(ParentD):
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
156 | class ChildD2(ParentD):
|
||||
157 | def f(self):
|
||||
158 | if False: super # Python injects __class__ into scope
|
||||
- builtins.super(ChildD2, self).f()
|
||||
159 + builtins.super().f()
|
||||
160 |
|
||||
161 | class ChildD3(ParentD):
|
||||
162 | def f(self):
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:163:23
|
||||
|
|
||||
161 | class ChildD3(ParentD):
|
||||
162 | def f(self):
|
||||
163 | builtins.super(ChildD3, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
164 | super # Python injects __class__ into scope
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
160 |
|
||||
161 | class ChildD3(ParentD):
|
||||
162 | def f(self):
|
||||
- builtins.super(ChildD3, self).f()
|
||||
163 + builtins.super().f()
|
||||
164 | super # Python injects __class__ into scope
|
||||
165 |
|
||||
166 | import builtins as builtins_alias
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:169:29
|
||||
|
|
||||
167 | class ChildD4(ParentD):
|
||||
168 | def f(self):
|
||||
169 | builtins_alias.super(ChildD4, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
170 | super # Python injects __class__ into scope
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
166 | import builtins as builtins_alias
|
||||
167 | class ChildD4(ParentD):
|
||||
168 | def f(self):
|
||||
- builtins_alias.super(ChildD4, self).f()
|
||||
169 + builtins_alias.super().f()
|
||||
170 | super # Python injects __class__ into scope
|
||||
171 |
|
||||
172 | class ChildD5(ParentD):
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:176:23
|
||||
|
|
||||
174 | super = 1
|
||||
175 | super # Python injects __class__ into scope
|
||||
176 | builtins.super(ChildD5, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
177 |
|
||||
178 | class ChildD6(ParentD):
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
173 | def f(self):
|
||||
174 | super = 1
|
||||
175 | super # Python injects __class__ into scope
|
||||
- builtins.super(ChildD5, self).f()
|
||||
176 + builtins.super().f()
|
||||
177 |
|
||||
178 | class ChildD6(ParentD):
|
||||
179 | def f(self):
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:182:23
|
||||
|
|
||||
180 | super: "Any"
|
||||
181 | __class__ # Python injects __class__ into scope
|
||||
182 | builtins.super(ChildD6, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
183 |
|
||||
184 | class ChildD7(ParentD):
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
179 | def f(self):
|
||||
180 | super: "Any"
|
||||
181 | __class__ # Python injects __class__ into scope
|
||||
- builtins.super(ChildD6, self).f()
|
||||
182 + builtins.super().f()
|
||||
183 |
|
||||
184 | class ChildD7(ParentD):
|
||||
185 | def f(self):
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:188:23
|
||||
|
|
||||
186 | def x():
|
||||
187 | __class__ # Python injects __class__ into scope
|
||||
188 | builtins.super(ChildD7, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
189 |
|
||||
190 | class ChildD8(ParentD):
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
185 | def f(self):
|
||||
186 | def x():
|
||||
187 | __class__ # Python injects __class__ into scope
|
||||
- builtins.super(ChildD7, self).f()
|
||||
188 + builtins.super().f()
|
||||
189 |
|
||||
190 | class ChildD8(ParentD):
|
||||
191 | def f(self):
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:195:23
|
||||
|
|
||||
193 | super = 1
|
||||
194 | super # Python injects __class__ into scope
|
||||
195 | builtins.super(ChildD8, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
196 |
|
||||
197 | class ChildD9(ParentD):
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
192 | def x():
|
||||
193 | super = 1
|
||||
194 | super # Python injects __class__ into scope
|
||||
- builtins.super(ChildD8, self).f()
|
||||
195 + builtins.super().f()
|
||||
196 |
|
||||
197 | class ChildD9(ParentD):
|
||||
198 | def f(self):
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:202:23
|
||||
|
|
||||
200 | __class__ = 1
|
||||
201 | __class__ # Python injects __class__ into scope
|
||||
202 | builtins.super(ChildD9, self).f()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
203 |
|
||||
204 | class ChildD10(ParentD):
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
199 | def x():
|
||||
200 | __class__ = 1
|
||||
201 | __class__ # Python injects __class__ into scope
|
||||
- builtins.super(ChildD9, self).f()
|
||||
202 + builtins.super().f()
|
||||
203 |
|
||||
204 | class ChildD10(ParentD):
|
||||
205 | def f(self):
|
||||
|
||||
UP008 [*] Use `super()` instead of `super(__class__, self)`
|
||||
--> UP008.py:209:23
|
||||
|
|
||||
207 | __class__ = 1
|
||||
208 | super # Python injects __class__ into scope
|
||||
209 | builtins.super(ChildD10, self).f()
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Remove `super()` parameters
|
||||
206 | def x():
|
||||
207 | __class__ = 1
|
||||
208 | super # Python injects __class__ into scope
|
||||
- builtins.super(ChildD10, self).f()
|
||||
209 + builtins.super().f()
|
||||
210 |
|
||||
211 |
|
||||
212 | # Must be ignored
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
|
||||
---
|
||||
UP043 [*] Unnecessary default type arguments
|
||||
--> UP043.pyi:4:15
|
||||
|
|
||||
4 | def func() -> Generator[int, None, None]:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
5 | yield 42
|
||||
|
|
||||
help: Remove default type arguments
|
||||
1 | from collections.abc import Generator, AsyncGenerator
|
||||
2 |
|
||||
3 |
|
||||
- def func() -> Generator[int, None, None]:
|
||||
4 + def func() -> Generator[int]:
|
||||
5 | yield 42
|
||||
6 |
|
||||
7 |
|
||||
|
||||
UP043 [*] Unnecessary default type arguments
|
||||
--> UP043.pyi:8:15
|
||||
|
|
||||
8 | def func() -> Generator[int, None]:
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
9 | yield 42
|
||||
|
|
||||
help: Remove default type arguments
|
||||
5 | yield 42
|
||||
6 |
|
||||
7 |
|
||||
- def func() -> Generator[int, None]:
|
||||
8 + def func() -> Generator[int]:
|
||||
9 | yield 42
|
||||
10 |
|
||||
11 |
|
||||
|
||||
UP043 [*] Unnecessary default type arguments
|
||||
--> UP043.pyi:21:15
|
||||
|
|
||||
21 | def func() -> Generator[int, int, None]:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
22 | _ = yield 42
|
||||
23 | return None
|
||||
|
|
||||
help: Remove default type arguments
|
||||
18 | return foo
|
||||
19 |
|
||||
20 |
|
||||
- def func() -> Generator[int, int, None]:
|
||||
21 + def func() -> Generator[int, int]:
|
||||
22 | _ = yield 42
|
||||
23 | return None
|
||||
24 |
|
||||
|
||||
UP043 [*] Unnecessary default type arguments
|
||||
--> UP043.pyi:31:21
|
||||
|
|
||||
31 | async def func() -> AsyncGenerator[int, None]:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
32 | yield 42
|
||||
|
|
||||
help: Remove default type arguments
|
||||
28 | return 42
|
||||
29 |
|
||||
30 |
|
||||
- async def func() -> AsyncGenerator[int, None]:
|
||||
31 + async def func() -> AsyncGenerator[int]:
|
||||
32 | yield 42
|
||||
33 |
|
||||
34 |
|
||||
|
||||
UP043 [*] Unnecessary default type arguments
|
||||
--> UP043.pyi:47:15
|
||||
|
|
||||
47 | def func() -> Generator[str, None, None]:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
48 | yield "hello"
|
||||
|
|
||||
help: Remove default type arguments
|
||||
44 | from typing import Generator, AsyncGenerator
|
||||
45 |
|
||||
46 |
|
||||
- def func() -> Generator[str, None, None]:
|
||||
47 + def func() -> Generator[str]:
|
||||
48 | yield "hello"
|
||||
49 |
|
||||
50 |
|
||||
|
||||
UP043 [*] Unnecessary default type arguments
|
||||
--> UP043.pyi:51:21
|
||||
|
|
||||
51 | async def func() -> AsyncGenerator[str, None]:
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
52 | yield "hello"
|
||||
|
|
||||
help: Remove default type arguments
|
||||
48 | yield "hello"
|
||||
49 |
|
||||
50 |
|
||||
- async def func() -> AsyncGenerator[str, None]:
|
||||
51 + async def func() -> AsyncGenerator[str]:
|
||||
52 | yield "hello"
|
||||
53 |
|
||||
54 |
|
||||
|
||||
UP043 [*] Unnecessary default type arguments
|
||||
--> UP043.pyi:55:21
|
||||
|
|
||||
55 | async def func() -> AsyncGenerator[ # type: ignore
|
||||
| _____________________^
|
||||
56 | | str,
|
||||
57 | | None
|
||||
58 | | ]:
|
||||
| |_^
|
||||
59 | yield "hello"
|
||||
|
|
||||
help: Remove default type arguments
|
||||
52 | yield "hello"
|
||||
53 |
|
||||
54 |
|
||||
- async def func() -> AsyncGenerator[ # type: ignore
|
||||
- str,
|
||||
- None
|
||||
- ]:
|
||||
55 + async def func() -> AsyncGenerator[str]:
|
||||
56 | yield "hello"
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{self as ast, CmpOp, Expr};
|
||||
use ruff_python_ast::{self as ast, CmpOp, Expr, helpers::is_empty_f_string};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -75,10 +75,7 @@ fn is_empty(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
Expr::Dict(ast::ExprDict { items, .. }) => items.is_empty(),
|
||||
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => value.is_empty(),
|
||||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value.is_empty(),
|
||||
Expr::FString(s) => s
|
||||
.value
|
||||
.elements()
|
||||
.all(|elt| elt.as_literal().is_some_and(|elt| elt.is_empty())),
|
||||
Expr::FString(s) => is_empty_f_string(s),
|
||||
Expr::Call(ast::ExprCall {
|
||||
func,
|
||||
arguments,
|
||||
|
||||
@@ -251,3 +251,12 @@ RUF060 Unnecessary membership test on empty collection
|
||||
25 |
|
||||
26 | # OK
|
||||
|
|
||||
|
||||
RUF060 Unnecessary membership test on empty collection
|
||||
--> RUF060.py:47:1
|
||||
|
|
||||
46 | # https://github.com/astral-sh/ruff/issues/20238
|
||||
47 | "b" in f"" "" # Error
|
||||
| ^^^^^^^^^^^^^
|
||||
48 | "b" in f"" "x" # OK
|
||||
|
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
PLE1700 `yield from` statement in async function; use `async for` instead
|
||||
--> resources/test/fixtures/syntax_errors/yield_from_in_async_function.py:1:16
|
||||
|
|
||||
1 | async def f(): yield from x # error
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
@@ -3030,6 +3030,12 @@ impl Parameters {
|
||||
.find(|arg| arg.parameter.name.as_str() == name)
|
||||
}
|
||||
|
||||
/// Returns the index of the parameter with the given name
|
||||
pub fn index(&self, name: &str) -> Option<usize> {
|
||||
self.iter_non_variadic_params()
|
||||
.position(|arg| arg.parameter.name.as_str() == name)
|
||||
}
|
||||
|
||||
/// Returns an iterator over all parameters included in this [`Parameters`] node.
|
||||
pub fn iter(&self) -> ParametersIterator<'_> {
|
||||
ParametersIterator::new(self)
|
||||
@@ -3219,7 +3225,6 @@ impl<'a> IntoIterator for &'a Box<Parameters> {
|
||||
/// Used by `Arguments` original type.
|
||||
///
|
||||
/// NOTE: This type is different from original Python AST.
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
|
||||
pub struct ParameterWithDefault {
|
||||
@@ -3241,6 +3246,14 @@ impl ParameterWithDefault {
|
||||
pub fn annotation(&self) -> Option<&Expr> {
|
||||
self.parameter.annotation()
|
||||
}
|
||||
|
||||
/// Return `true` if the parameter name uses the pre-PEP-570 convention
|
||||
/// (specified in PEP 484) to indicate to a type checker that it should be treated
|
||||
/// as positional-only.
|
||||
pub fn uses_pep_484_positional_only_convention(&self) -> bool {
|
||||
let name = self.name();
|
||||
name.starts_with("__") && !name.ends_with("__")
|
||||
}
|
||||
}
|
||||
|
||||
/// An AST node used to represent the arguments passed to a function call or class definition.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pub use generator::Generator;
|
||||
use ruff_python_parser::{ParseError, parse_module};
|
||||
pub use stylist::Stylist;
|
||||
pub use stylist::{Indentation, Stylist};
|
||||
|
||||
mod generator;
|
||||
mod stylist;
|
||||
|
||||
@@ -252,15 +252,20 @@ impl QuoteStyle {
|
||||
pub const fn is_preserve(self) -> bool {
|
||||
matches!(self, QuoteStyle::Preserve)
|
||||
}
|
||||
|
||||
/// Returns the string representation of the quote style.
|
||||
pub const fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
QuoteStyle::Single => "single",
|
||||
QuoteStyle::Double => "double",
|
||||
QuoteStyle::Preserve => "preserve",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for QuoteStyle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Single => write!(f, "single"),
|
||||
Self::Double => write!(f, "double"),
|
||||
Self::Preserve => write!(f, "preserve"),
|
||||
}
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,10 +307,10 @@ impl MagicTrailingComma {
|
||||
|
||||
impl fmt::Display for MagicTrailingComma {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Respect => write!(f, "respect"),
|
||||
Self::Ignore => write!(f, "ignore"),
|
||||
}
|
||||
f.write_str(match self {
|
||||
MagicTrailingComma::Respect => "respect",
|
||||
MagicTrailingComma::Ignore => "ignore",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
async def f(): yield from x
|
||||
@@ -709,6 +709,16 @@ impl SemanticSyntaxChecker {
|
||||
}
|
||||
Expr::YieldFrom(_) => {
|
||||
Self::yield_outside_function(ctx, expr, YieldOutsideFunctionKind::YieldFrom);
|
||||
if ctx.in_function_scope() && ctx.in_async_context() {
|
||||
// test_err yield_from_in_async_function
|
||||
// async def f(): yield from x
|
||||
|
||||
Self::add_error(
|
||||
ctx,
|
||||
SemanticSyntaxErrorKind::YieldFromInAsyncFunction,
|
||||
expr.range(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Expr::Await(_) => {
|
||||
Self::yield_outside_function(ctx, expr, YieldOutsideFunctionKind::Await);
|
||||
@@ -989,6 +999,9 @@ impl Display for SemanticSyntaxError {
|
||||
SemanticSyntaxErrorKind::AnnotatedNonlocal(name) => {
|
||||
write!(f, "annotated name `{name}` can't be nonlocal")
|
||||
}
|
||||
SemanticSyntaxErrorKind::YieldFromInAsyncFunction => {
|
||||
f.write_str("`yield from` statement in async function; use `async for` instead")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1346,6 +1359,9 @@ pub enum SemanticSyntaxErrorKind {
|
||||
|
||||
/// Represents a type annotation on a variable that's been declared nonlocal
|
||||
AnnotatedNonlocal(String),
|
||||
|
||||
/// Represents the use of `yield from` inside an asynchronous function.
|
||||
YieldFromInAsyncFunction,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
|
||||
@@ -465,7 +465,7 @@ impl<'ast> SourceOrderVisitor<'ast> for ValidateAstVisitor<'ast> {
|
||||
|
||||
enum Scope {
|
||||
Module,
|
||||
Function,
|
||||
Function { is_async: bool },
|
||||
Comprehension { is_async: bool },
|
||||
Class,
|
||||
}
|
||||
@@ -528,7 +528,15 @@ impl SemanticSyntaxContext for SemanticSyntaxCheckerVisitor<'_> {
|
||||
}
|
||||
|
||||
fn in_async_context(&self) -> bool {
|
||||
true
|
||||
if let Some(scope) = self.scopes.iter().next_back() {
|
||||
match scope {
|
||||
Scope::Class | Scope::Module => false,
|
||||
Scope::Comprehension { is_async } => *is_async,
|
||||
Scope::Function { is_async } => *is_async,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn in_sync_comprehension(&self) -> bool {
|
||||
@@ -589,8 +597,10 @@ impl Visitor<'_> for SemanticSyntaxCheckerVisitor<'_> {
|
||||
self.visit_body(body);
|
||||
self.scopes.pop().unwrap();
|
||||
}
|
||||
ast::Stmt::FunctionDef(ast::StmtFunctionDef { .. }) => {
|
||||
self.scopes.push(Scope::Function);
|
||||
ast::Stmt::FunctionDef(ast::StmtFunctionDef { is_async, .. }) => {
|
||||
self.scopes.push(Scope::Function {
|
||||
is_async: *is_async,
|
||||
});
|
||||
ast::visitor::walk_stmt(self, stmt);
|
||||
self.scopes.pop().unwrap();
|
||||
}
|
||||
@@ -604,7 +614,7 @@ impl Visitor<'_> for SemanticSyntaxCheckerVisitor<'_> {
|
||||
self.with_semantic_checker(|semantic, context| semantic.visit_expr(expr, context));
|
||||
match expr {
|
||||
ast::Expr::Lambda(_) => {
|
||||
self.scopes.push(Scope::Function);
|
||||
self.scopes.push(Scope::Function { is_async: false });
|
||||
ast::visitor::walk_expr(self, expr);
|
||||
self.scopes.pop().unwrap();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
source: crates/ruff_python_parser/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_parser/resources/inline/err/yield_from_in_async_function.py
|
||||
---
|
||||
## AST
|
||||
|
||||
```
|
||||
Module(
|
||||
ModModule {
|
||||
node_index: NodeIndex(None),
|
||||
range: 0..28,
|
||||
body: [
|
||||
FunctionDef(
|
||||
StmtFunctionDef {
|
||||
node_index: NodeIndex(None),
|
||||
range: 0..27,
|
||||
is_async: true,
|
||||
decorator_list: [],
|
||||
name: Identifier {
|
||||
id: Name("f"),
|
||||
range: 10..11,
|
||||
node_index: NodeIndex(None),
|
||||
},
|
||||
type_params: None,
|
||||
parameters: Parameters {
|
||||
range: 11..13,
|
||||
node_index: NodeIndex(None),
|
||||
posonlyargs: [],
|
||||
args: [],
|
||||
vararg: None,
|
||||
kwonlyargs: [],
|
||||
kwarg: None,
|
||||
},
|
||||
returns: None,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
node_index: NodeIndex(None),
|
||||
range: 15..27,
|
||||
value: YieldFrom(
|
||||
ExprYieldFrom {
|
||||
node_index: NodeIndex(None),
|
||||
range: 15..27,
|
||||
value: Name(
|
||||
ExprName {
|
||||
node_index: NodeIndex(None),
|
||||
range: 26..27,
|
||||
id: Name("x"),
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
```
|
||||
## Semantic Syntax Errors
|
||||
|
||||
|
|
||||
1 | async def f(): yield from x
|
||||
| ^^^^^^^^^^^^ Syntax Error: `yield from` statement in async function; use `async for` instead
|
||||
|
|
||||
@@ -50,5 +50,8 @@ insta = { workspace = true }
|
||||
[target.'cfg(target_vendor = "apple")'.dependencies]
|
||||
libc = { workspace = true }
|
||||
|
||||
[features]
|
||||
test-uv = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,18 +1,52 @@
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use ruff_formatter::PrintedRange;
|
||||
use anyhow::Context;
|
||||
|
||||
use ruff_formatter::{FormatOptions, PrintedRange};
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_formatter::{FormatModuleError, format_module_source};
|
||||
use ruff_python_formatter::{FormatModuleError, PyFormatOptions, format_module_source};
|
||||
use ruff_source_file::LineIndex;
|
||||
use ruff_text_size::TextRange;
|
||||
use ruff_workspace::FormatterSettings;
|
||||
|
||||
use crate::edit::TextDocument;
|
||||
|
||||
/// The backend to use for formatting.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub(crate) enum FormatBackend {
|
||||
/// Use the built-in Ruff formatter.
|
||||
///
|
||||
/// The formatter version will match the LSP version.
|
||||
#[default]
|
||||
Internal,
|
||||
/// Use uv for formatting.
|
||||
///
|
||||
/// The formatter version may differ from the LSP version.
|
||||
Uv,
|
||||
}
|
||||
|
||||
pub(crate) fn format(
|
||||
document: &TextDocument,
|
||||
source_type: PySourceType,
|
||||
formatter_settings: &FormatterSettings,
|
||||
path: &Path,
|
||||
backend: FormatBackend,
|
||||
) -> crate::Result<Option<String>> {
|
||||
match backend {
|
||||
FormatBackend::Uv => format_external(document, source_type, formatter_settings, path),
|
||||
FormatBackend::Internal => format_internal(document, source_type, formatter_settings, path),
|
||||
}
|
||||
}
|
||||
|
||||
/// Format using the built-in Ruff formatter.
|
||||
fn format_internal(
|
||||
document: &TextDocument,
|
||||
source_type: PySourceType,
|
||||
formatter_settings: &FormatterSettings,
|
||||
path: &Path,
|
||||
) -> crate::Result<Option<String>> {
|
||||
let format_options =
|
||||
formatter_settings.to_format_options(source_type, document.contents(), Some(path));
|
||||
@@ -35,12 +69,44 @@ pub(crate) fn format(
|
||||
}
|
||||
}
|
||||
|
||||
/// Format using an external uv command.
|
||||
fn format_external(
|
||||
document: &TextDocument,
|
||||
source_type: PySourceType,
|
||||
formatter_settings: &FormatterSettings,
|
||||
path: &Path,
|
||||
) -> crate::Result<Option<String>> {
|
||||
let format_options =
|
||||
formatter_settings.to_format_options(source_type, document.contents(), Some(path));
|
||||
let uv_command = UvFormatCommand::from(format_options);
|
||||
uv_command.format_document(document.contents(), path)
|
||||
}
|
||||
|
||||
pub(crate) fn format_range(
|
||||
document: &TextDocument,
|
||||
source_type: PySourceType,
|
||||
formatter_settings: &FormatterSettings,
|
||||
range: TextRange,
|
||||
path: &Path,
|
||||
backend: FormatBackend,
|
||||
) -> crate::Result<Option<PrintedRange>> {
|
||||
match backend {
|
||||
FormatBackend::Uv => {
|
||||
format_range_external(document, source_type, formatter_settings, range, path)
|
||||
}
|
||||
FormatBackend::Internal => {
|
||||
format_range_internal(document, source_type, formatter_settings, range, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Format range using the built-in Ruff formatter
|
||||
fn format_range_internal(
|
||||
document: &TextDocument,
|
||||
source_type: PySourceType,
|
||||
formatter_settings: &FormatterSettings,
|
||||
range: TextRange,
|
||||
path: &Path,
|
||||
) -> crate::Result<Option<PrintedRange>> {
|
||||
let format_options =
|
||||
formatter_settings.to_format_options(source_type, document.contents(), Some(path));
|
||||
@@ -63,6 +129,198 @@ pub(crate) fn format_range(
|
||||
}
|
||||
}
|
||||
|
||||
/// Format range using an external command, i.e., `uv`.
|
||||
fn format_range_external(
|
||||
document: &TextDocument,
|
||||
source_type: PySourceType,
|
||||
formatter_settings: &FormatterSettings,
|
||||
range: TextRange,
|
||||
path: &Path,
|
||||
) -> crate::Result<Option<PrintedRange>> {
|
||||
let format_options =
|
||||
formatter_settings.to_format_options(source_type, document.contents(), Some(path));
|
||||
let uv_command = UvFormatCommand::from(format_options);
|
||||
|
||||
// Format the range using uv and convert the result to `PrintedRange`
|
||||
match uv_command.format_range(document.contents(), range, path, document.index())? {
|
||||
Some(formatted) => Ok(Some(PrintedRange::new(formatted, range))),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for uv format commands
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct UvFormatCommand {
|
||||
options: PyFormatOptions,
|
||||
}
|
||||
|
||||
impl From<PyFormatOptions> for UvFormatCommand {
|
||||
fn from(options: PyFormatOptions) -> Self {
|
||||
Self { options }
|
||||
}
|
||||
}
|
||||
|
||||
impl UvFormatCommand {
|
||||
/// Build the command with all necessary arguments
|
||||
fn build_command(
|
||||
&self,
|
||||
path: &Path,
|
||||
range_with_index: Option<(TextRange, &LineIndex, &str)>,
|
||||
) -> Command {
|
||||
let mut command = Command::new("uv");
|
||||
command.arg("format");
|
||||
command.arg("--");
|
||||
|
||||
let target_version = format!(
|
||||
"py{}{}",
|
||||
self.options.target_version().major,
|
||||
self.options.target_version().minor
|
||||
);
|
||||
|
||||
// Add only the formatting options that the CLI supports
|
||||
command.arg("--target-version");
|
||||
command.arg(&target_version);
|
||||
|
||||
command.arg("--line-length");
|
||||
command.arg(self.options.line_width().to_string());
|
||||
|
||||
if self.options.preview().is_enabled() {
|
||||
command.arg("--preview");
|
||||
}
|
||||
|
||||
// Pass other formatting options via --config
|
||||
command.arg("--config");
|
||||
command.arg(format!(
|
||||
"format.indent-style = '{}'",
|
||||
self.options.indent_style()
|
||||
));
|
||||
|
||||
command.arg("--config");
|
||||
command.arg(format!("indent-width = {}", self.options.indent_width()));
|
||||
|
||||
command.arg("--config");
|
||||
command.arg(format!(
|
||||
"format.quote-style = '{}'",
|
||||
self.options.quote_style()
|
||||
));
|
||||
|
||||
command.arg("--config");
|
||||
command.arg(format!(
|
||||
"format.line-ending = '{}'",
|
||||
self.options.line_ending().as_setting_str()
|
||||
));
|
||||
|
||||
command.arg("--config");
|
||||
command.arg(format!(
|
||||
"format.skip-magic-trailing-comma = {}",
|
||||
match self.options.magic_trailing_comma() {
|
||||
ruff_python_formatter::MagicTrailingComma::Respect => "false",
|
||||
ruff_python_formatter::MagicTrailingComma::Ignore => "true",
|
||||
}
|
||||
));
|
||||
|
||||
if let Some((range, line_index, source)) = range_with_index {
|
||||
// The CLI expects line:column format
|
||||
let start_pos = line_index.line_column(range.start(), source);
|
||||
let end_pos = line_index.line_column(range.end(), source);
|
||||
let range_str = format!(
|
||||
"{}:{}-{}:{}",
|
||||
start_pos.line.get(),
|
||||
start_pos.column.get(),
|
||||
end_pos.line.get(),
|
||||
end_pos.column.get()
|
||||
);
|
||||
command.arg("--range");
|
||||
command.arg(&range_str);
|
||||
}
|
||||
|
||||
command.arg("--stdin-filename");
|
||||
command.arg(path.to_string_lossy().as_ref());
|
||||
|
||||
command.stdin(Stdio::piped());
|
||||
command.stdout(Stdio::piped());
|
||||
command.stderr(Stdio::piped());
|
||||
|
||||
command
|
||||
}
|
||||
|
||||
/// Execute the format command on the given source.
|
||||
pub(crate) fn format(
|
||||
&self,
|
||||
source: &str,
|
||||
path: &Path,
|
||||
range_with_index: Option<(TextRange, &LineIndex)>,
|
||||
) -> crate::Result<Option<String>> {
|
||||
let mut command =
|
||||
self.build_command(path, range_with_index.map(|(r, idx)| (r, idx, source)));
|
||||
let mut child = match command.spawn() {
|
||||
Ok(child) => child,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||
anyhow::bail!("uv was not found; is it installed and on the PATH?")
|
||||
}
|
||||
Err(err) => return Err(err).context("Failed to spawn uv"),
|
||||
};
|
||||
|
||||
let mut stdin = child
|
||||
.stdin
|
||||
.take()
|
||||
.context("Failed to get stdin from format subprocess")?;
|
||||
stdin
|
||||
.write_all(source.as_bytes())
|
||||
.context("Failed to write to stdin")?;
|
||||
drop(stdin);
|
||||
|
||||
let result = child
|
||||
.wait_with_output()
|
||||
.context("Failed to get output from format subprocess")?;
|
||||
|
||||
if !result.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&result.stderr);
|
||||
// We don't propagate format errors due to invalid syntax
|
||||
if stderr.contains("Failed to parse") {
|
||||
tracing::warn!("Unable to format document: {}", stderr);
|
||||
return Ok(None);
|
||||
}
|
||||
// Special-case for when `uv format` is not available
|
||||
if stderr.contains("unrecognized subcommand 'format'") {
|
||||
anyhow::bail!(
|
||||
"The installed version of uv does not support `uv format`; upgrade to a newer version"
|
||||
);
|
||||
}
|
||||
anyhow::bail!("Failed to format document: {}", stderr);
|
||||
}
|
||||
|
||||
let formatted = String::from_utf8(result.stdout)
|
||||
.context("Failed to parse stdout from format subprocess as utf-8")?;
|
||||
|
||||
if formatted == source {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(formatted))
|
||||
}
|
||||
}
|
||||
|
||||
/// Format the entire document.
|
||||
pub(crate) fn format_document(
|
||||
&self,
|
||||
source: &str,
|
||||
path: &Path,
|
||||
) -> crate::Result<Option<String>> {
|
||||
self.format(source, path, None)
|
||||
}
|
||||
|
||||
/// Format a specific range.
|
||||
pub(crate) fn format_range(
|
||||
&self,
|
||||
source: &str,
|
||||
range: TextRange,
|
||||
path: &Path,
|
||||
line_index: &LineIndex,
|
||||
) -> crate::Result<Option<String>> {
|
||||
self.format(source, path, Some((range, line_index)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
@@ -74,7 +332,7 @@ mod tests {
|
||||
use ruff_workspace::FormatterSettings;
|
||||
|
||||
use crate::TextDocument;
|
||||
use crate::format::{format, format_range};
|
||||
use crate::format::{FormatBackend, format, format_range};
|
||||
|
||||
#[test]
|
||||
fn format_per_file_version() {
|
||||
@@ -98,6 +356,7 @@ with open("a_really_long_foo") as foo, open("a_really_long_bar") as bar, open("a
|
||||
..Default::default()
|
||||
},
|
||||
Path::new("test.py"),
|
||||
FormatBackend::Internal,
|
||||
)
|
||||
.expect("Expected no errors when formatting")
|
||||
.expect("Expected formatting changes");
|
||||
@@ -120,6 +379,7 @@ with open("a_really_long_foo") as foo, open("a_really_long_bar") as bar, open("a
|
||||
..Default::default()
|
||||
},
|
||||
Path::new("test.py"),
|
||||
FormatBackend::Internal,
|
||||
)
|
||||
.expect("Expected no errors when formatting")
|
||||
.expect("Expected formatting changes");
|
||||
@@ -168,6 +428,7 @@ sys.exit(
|
||||
},
|
||||
range,
|
||||
Path::new("test.py"),
|
||||
FormatBackend::Internal,
|
||||
)
|
||||
.expect("Expected no errors when formatting")
|
||||
.expect("Expected formatting changes");
|
||||
@@ -191,6 +452,7 @@ sys.exit(
|
||||
},
|
||||
range,
|
||||
Path::new("test.py"),
|
||||
FormatBackend::Internal,
|
||||
)
|
||||
.expect("Expected no errors when formatting")
|
||||
.expect("Expected formatting changes");
|
||||
@@ -204,4 +466,279 @@ sys.exit(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "test-uv")]
|
||||
mod uv_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_uv_format_document() {
|
||||
let document = TextDocument::new(
|
||||
r#"
|
||||
def hello( x,y ,z ):
|
||||
return x+y +z
|
||||
|
||||
|
||||
def world( ):
|
||||
pass
|
||||
"#
|
||||
.to_string(),
|
||||
0,
|
||||
);
|
||||
|
||||
let result = format(
|
||||
&document,
|
||||
PySourceType::Python,
|
||||
&FormatterSettings::default(),
|
||||
Path::new("test.py"),
|
||||
FormatBackend::Uv,
|
||||
)
|
||||
.expect("Expected no errors when formatting with uv")
|
||||
.expect("Expected formatting changes");
|
||||
|
||||
// uv should format this to a consistent style
|
||||
assert_snapshot!(result, @r#"
|
||||
def hello(x, y, z):
|
||||
return x + y + z
|
||||
|
||||
|
||||
def world():
|
||||
pass
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uv_format_range() -> anyhow::Result<()> {
|
||||
let document = TextDocument::new(
|
||||
r#"
|
||||
def messy_function( a, b,c ):
|
||||
return a+b+c
|
||||
|
||||
def another_function(x,y,z):
|
||||
result=x+y+z
|
||||
return result
|
||||
"#
|
||||
.to_string(),
|
||||
0,
|
||||
);
|
||||
|
||||
// Find the range of the second function
|
||||
let start = document.contents().find("def another_function").unwrap();
|
||||
let end = document.contents().find("return result").unwrap() + "return result".len();
|
||||
let range = TextRange::new(TextSize::try_from(start)?, TextSize::try_from(end)?);
|
||||
|
||||
let result = format_range(
|
||||
&document,
|
||||
PySourceType::Python,
|
||||
&FormatterSettings::default(),
|
||||
range,
|
||||
Path::new("test.py"),
|
||||
FormatBackend::Uv,
|
||||
)
|
||||
.expect("Expected no errors when formatting range with uv")
|
||||
.expect("Expected formatting changes");
|
||||
|
||||
assert_snapshot!(result.as_code(), @r#"
|
||||
def messy_function( a, b,c ):
|
||||
return a+b+c
|
||||
|
||||
def another_function(x, y, z):
|
||||
result = x + y + z
|
||||
return result
|
||||
"#);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uv_format_with_line_length() {
|
||||
use ruff_formatter::LineWidth;
|
||||
|
||||
let document = TextDocument::new(
|
||||
r#"
|
||||
def hello(very_long_parameter_name_1, very_long_parameter_name_2, very_long_parameter_name_3):
|
||||
return very_long_parameter_name_1 + very_long_parameter_name_2 + very_long_parameter_name_3
|
||||
"#
|
||||
.to_string(),
|
||||
0,
|
||||
);
|
||||
|
||||
// Test with shorter line length
|
||||
let formatter_settings = FormatterSettings {
|
||||
line_width: LineWidth::try_from(60).unwrap(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = format(
|
||||
&document,
|
||||
PySourceType::Python,
|
||||
&formatter_settings,
|
||||
Path::new("test.py"),
|
||||
FormatBackend::Uv,
|
||||
)
|
||||
.expect("Expected no errors when formatting with uv")
|
||||
.expect("Expected formatting changes");
|
||||
|
||||
// With line length 60, the function should be wrapped
|
||||
assert_snapshot!(result, @r#"
|
||||
def hello(
|
||||
very_long_parameter_name_1,
|
||||
very_long_parameter_name_2,
|
||||
very_long_parameter_name_3,
|
||||
):
|
||||
return (
|
||||
very_long_parameter_name_1
|
||||
+ very_long_parameter_name_2
|
||||
+ very_long_parameter_name_3
|
||||
)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uv_format_with_indent_style() {
|
||||
use ruff_formatter::IndentStyle;
|
||||
|
||||
let document = TextDocument::new(
|
||||
r#"
|
||||
def hello():
|
||||
if True:
|
||||
print("Hello")
|
||||
if False:
|
||||
print("World")
|
||||
"#
|
||||
.to_string(),
|
||||
0,
|
||||
);
|
||||
|
||||
// Test with tabs instead of spaces
|
||||
let formatter_settings = FormatterSettings {
|
||||
indent_style: IndentStyle::Tab,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = format(
|
||||
&document,
|
||||
PySourceType::Python,
|
||||
&formatter_settings,
|
||||
Path::new("test.py"),
|
||||
FormatBackend::Uv,
|
||||
)
|
||||
.expect("Expected no errors when formatting with uv")
|
||||
.expect("Expected formatting changes");
|
||||
|
||||
// Should have formatting changes (spaces to tabs)
|
||||
assert_snapshot!(result, @r#"
|
||||
def hello():
|
||||
if True:
|
||||
print("Hello")
|
||||
if False:
|
||||
print("World")
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uv_format_syntax_error() {
|
||||
let document = TextDocument::new(
|
||||
r#"
|
||||
def broken(:
|
||||
pass
|
||||
"#
|
||||
.to_string(),
|
||||
0,
|
||||
);
|
||||
|
||||
// uv should return None for syntax errors (as indicated by the TODO comment)
|
||||
let result = format(
|
||||
&document,
|
||||
PySourceType::Python,
|
||||
&FormatterSettings::default(),
|
||||
Path::new("test.py"),
|
||||
FormatBackend::Uv,
|
||||
)
|
||||
.expect("Expected no errors from format function");
|
||||
|
||||
// Should return None since the syntax is invalid
|
||||
assert_eq!(result, None, "Expected None for syntax error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uv_format_with_quote_style() {
|
||||
use ruff_python_formatter::QuoteStyle;
|
||||
|
||||
let document = TextDocument::new(
|
||||
r#"
|
||||
x = "hello"
|
||||
y = 'world'
|
||||
z = '''multi
|
||||
line'''
|
||||
"#
|
||||
.to_string(),
|
||||
0,
|
||||
);
|
||||
|
||||
// Test with single quotes
|
||||
let formatter_settings = FormatterSettings {
|
||||
quote_style: QuoteStyle::Single,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = format(
|
||||
&document,
|
||||
PySourceType::Python,
|
||||
&formatter_settings,
|
||||
Path::new("test.py"),
|
||||
FormatBackend::Uv,
|
||||
)
|
||||
.expect("Expected no errors when formatting with uv")
|
||||
.expect("Expected formatting changes");
|
||||
|
||||
assert_snapshot!(result, @r#"
|
||||
x = 'hello'
|
||||
y = 'world'
|
||||
z = """multi
|
||||
line"""
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uv_format_with_magic_trailing_comma() {
|
||||
use ruff_python_formatter::MagicTrailingComma;
|
||||
|
||||
let document = TextDocument::new(
|
||||
r#"
|
||||
foo = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]
|
||||
|
||||
bar = [1, 2, 3,]
|
||||
"#
|
||||
.to_string(),
|
||||
0,
|
||||
);
|
||||
|
||||
// Test with ignore magic trailing comma
|
||||
let formatter_settings = FormatterSettings {
|
||||
magic_trailing_comma: MagicTrailingComma::Ignore,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = format(
|
||||
&document,
|
||||
PySourceType::Python,
|
||||
&formatter_settings,
|
||||
Path::new("test.py"),
|
||||
FormatBackend::Uv,
|
||||
)
|
||||
.expect("Expected no errors when formatting with uv")
|
||||
.expect("Expected formatting changes");
|
||||
|
||||
assert_snapshot!(result, @r#"
|
||||
foo = [1, 2, 3]
|
||||
|
||||
bar = [1, 2, 3]
|
||||
"#);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ impl super::BackgroundDocumentRequestHandler for Format {
|
||||
pub(super) fn format_full_document(snapshot: &DocumentSnapshot) -> Result<Fixes> {
|
||||
let mut fixes = Fixes::default();
|
||||
let query = snapshot.query();
|
||||
let backend = snapshot
|
||||
.client_settings()
|
||||
.editor_settings()
|
||||
.format_backend();
|
||||
|
||||
match snapshot.query() {
|
||||
DocumentQuery::Notebook { notebook, .. } => {
|
||||
@@ -41,7 +45,7 @@ pub(super) fn format_full_document(snapshot: &DocumentSnapshot) -> Result<Fixes>
|
||||
.map(|url| (url.clone(), notebook.cell_document_by_uri(url).unwrap()))
|
||||
{
|
||||
if let Some(changes) =
|
||||
format_text_document(text_document, query, snapshot.encoding(), true)?
|
||||
format_text_document(text_document, query, snapshot.encoding(), true, backend)?
|
||||
{
|
||||
fixes.insert(url, changes);
|
||||
}
|
||||
@@ -49,7 +53,7 @@ pub(super) fn format_full_document(snapshot: &DocumentSnapshot) -> Result<Fixes>
|
||||
}
|
||||
DocumentQuery::Text { document, .. } => {
|
||||
if let Some(changes) =
|
||||
format_text_document(document, query, snapshot.encoding(), false)?
|
||||
format_text_document(document, query, snapshot.encoding(), false, backend)?
|
||||
{
|
||||
fixes.insert(snapshot.query().make_key().into_url(), changes);
|
||||
}
|
||||
@@ -68,11 +72,16 @@ pub(super) fn format_document(snapshot: &DocumentSnapshot) -> Result<super::Form
|
||||
.context("Failed to get text document for the format request")
|
||||
.unwrap();
|
||||
let query = snapshot.query();
|
||||
let backend = snapshot
|
||||
.client_settings()
|
||||
.editor_settings()
|
||||
.format_backend();
|
||||
format_text_document(
|
||||
text_document,
|
||||
query,
|
||||
snapshot.encoding(),
|
||||
query.as_notebook().is_some(),
|
||||
backend,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -81,6 +90,7 @@ fn format_text_document(
|
||||
query: &DocumentQuery,
|
||||
encoding: PositionEncoding,
|
||||
is_notebook: bool,
|
||||
backend: crate::format::FormatBackend,
|
||||
) -> Result<super::FormatResponse> {
|
||||
let settings = query.settings();
|
||||
let file_path = query.virtual_file_path();
|
||||
@@ -101,6 +111,7 @@ fn format_text_document(
|
||||
query.source_type(),
|
||||
&settings.formatter,
|
||||
&file_path,
|
||||
backend,
|
||||
)
|
||||
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
|
||||
let Some(mut formatted) = formatted else {
|
||||
|
||||
@@ -36,7 +36,11 @@ fn format_document_range(
|
||||
.context("Failed to get text document for the format range request")
|
||||
.unwrap();
|
||||
let query = snapshot.query();
|
||||
format_text_document_range(text_document, range, query, snapshot.encoding())
|
||||
let backend = snapshot
|
||||
.client_settings()
|
||||
.editor_settings()
|
||||
.format_backend();
|
||||
format_text_document_range(text_document, range, query, snapshot.encoding(), backend)
|
||||
}
|
||||
|
||||
/// Formats the specified [`Range`] in the [`TextDocument`].
|
||||
@@ -45,6 +49,7 @@ fn format_text_document_range(
|
||||
range: Range,
|
||||
query: &DocumentQuery,
|
||||
encoding: PositionEncoding,
|
||||
backend: crate::format::FormatBackend,
|
||||
) -> Result<super::FormatResponse> {
|
||||
let settings = query.settings();
|
||||
let file_path = query.virtual_file_path();
|
||||
@@ -68,6 +73,7 @@ fn format_text_document_range(
|
||||
&settings.formatter,
|
||||
range,
|
||||
&file_path,
|
||||
backend,
|
||||
)
|
||||
.with_failure_code(lsp_server::ErrorCode::InternalError)?;
|
||||
|
||||
|
||||
@@ -401,6 +401,7 @@ impl ConfigurationTransformer for EditorConfigurationTransformer<'_> {
|
||||
configuration,
|
||||
format_preview,
|
||||
lint_preview,
|
||||
format_backend: _,
|
||||
select,
|
||||
extend_select,
|
||||
ignore,
|
||||
|
||||
@@ -7,9 +7,12 @@ use serde_json::{Map, Value};
|
||||
|
||||
use ruff_linter::{RuleSelector, line_width::LineLength, rule_selector::ParseError};
|
||||
|
||||
use crate::session::{
|
||||
Client,
|
||||
settings::{ClientSettings, EditorSettings, GlobalClientSettings, ResolvedConfiguration},
|
||||
use crate::{
|
||||
format::FormatBackend,
|
||||
session::{
|
||||
Client,
|
||||
settings::{ClientSettings, EditorSettings, GlobalClientSettings, ResolvedConfiguration},
|
||||
},
|
||||
};
|
||||
|
||||
pub(crate) type WorkspaceOptionsMap = FxHashMap<Url, ClientOptions>;
|
||||
@@ -124,6 +127,7 @@ impl ClientOptions {
|
||||
configuration,
|
||||
lint_preview: lint.preview,
|
||||
format_preview: format.preview,
|
||||
format_backend: format.backend,
|
||||
select: lint.select.and_then(|select| {
|
||||
Self::resolve_rules(
|
||||
&select,
|
||||
@@ -283,11 +287,13 @@ impl Combine for LintOptions {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct FormatOptions {
|
||||
preview: Option<bool>,
|
||||
backend: Option<FormatBackend>,
|
||||
}
|
||||
|
||||
impl Combine for FormatOptions {
|
||||
fn combine_with(&mut self, other: Self) {
|
||||
self.preview.combine_with(other.preview);
|
||||
self.backend.combine_with(other.backend);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -443,6 +449,12 @@ pub(crate) trait Combine {
|
||||
fn combine_with(&mut self, other: Self);
|
||||
}
|
||||
|
||||
impl Combine for FormatBackend {
|
||||
fn combine_with(&mut self, other: Self) {
|
||||
*self = other;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Combine for Option<T>
|
||||
where
|
||||
T: Combine,
|
||||
@@ -584,6 +596,7 @@ mod tests {
|
||||
format: Some(
|
||||
FormatOptions {
|
||||
preview: None,
|
||||
backend: None,
|
||||
},
|
||||
),
|
||||
code_action: Some(
|
||||
@@ -640,6 +653,7 @@ mod tests {
|
||||
format: Some(
|
||||
FormatOptions {
|
||||
preview: None,
|
||||
backend: None,
|
||||
},
|
||||
),
|
||||
code_action: Some(
|
||||
@@ -704,6 +718,7 @@ mod tests {
|
||||
format: Some(
|
||||
FormatOptions {
|
||||
preview: None,
|
||||
backend: None,
|
||||
},
|
||||
),
|
||||
code_action: Some(
|
||||
@@ -782,6 +797,7 @@ mod tests {
|
||||
configuration: None,
|
||||
lint_preview: Some(true),
|
||||
format_preview: None,
|
||||
format_backend: None,
|
||||
select: Some(vec![
|
||||
RuleSelector::Linter(Linter::Pyflakes),
|
||||
RuleSelector::Linter(Linter::Isort)
|
||||
@@ -819,6 +835,7 @@ mod tests {
|
||||
configuration: None,
|
||||
lint_preview: Some(false),
|
||||
format_preview: None,
|
||||
format_backend: None,
|
||||
select: Some(vec![
|
||||
RuleSelector::Linter(Linter::Pyflakes),
|
||||
RuleSelector::Linter(Linter::Isort)
|
||||
@@ -919,6 +936,7 @@ mod tests {
|
||||
configuration: None,
|
||||
lint_preview: None,
|
||||
format_preview: None,
|
||||
format_backend: None,
|
||||
select: None,
|
||||
extend_select: None,
|
||||
ignore: Some(vec![RuleSelector::from_str("RUF001").unwrap()]),
|
||||
|
||||
@@ -8,6 +8,7 @@ use ruff_workspace::options::Options;
|
||||
|
||||
use crate::{
|
||||
ClientOptions,
|
||||
format::FormatBackend,
|
||||
session::{
|
||||
Client,
|
||||
options::{ClientConfiguration, ConfigurationPreference},
|
||||
@@ -84,6 +85,7 @@ pub(crate) struct EditorSettings {
|
||||
pub(super) configuration: Option<ResolvedConfiguration>,
|
||||
pub(super) lint_preview: Option<bool>,
|
||||
pub(super) format_preview: Option<bool>,
|
||||
pub(super) format_backend: Option<FormatBackend>,
|
||||
pub(super) select: Option<Vec<RuleSelector>>,
|
||||
pub(super) extend_select: Option<Vec<RuleSelector>>,
|
||||
pub(super) ignore: Option<Vec<RuleSelector>>,
|
||||
@@ -163,3 +165,9 @@ impl ClientSettings {
|
||||
&self.editor_settings
|
||||
}
|
||||
}
|
||||
|
||||
impl EditorSettings {
|
||||
pub(crate) fn format_backend(&self) -> FormatBackend {
|
||||
self.format_backend.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_wasm"
|
||||
version = "0.12.11"
|
||||
version = "0.12.12"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
3
crates/ty/docs/cli.md
generated
3
crates/ty/docs/cli.md
generated
@@ -60,8 +60,9 @@ over all configuration files.</p>
|
||||
</dd><dt id="ty-check--output-format"><a href="#ty-check--output-format"><code>--output-format</code></a> <i>output-format</i></dt><dd><p>The format to use for printing diagnostic messages</p>
|
||||
<p>Possible values:</p>
|
||||
<ul>
|
||||
<li><code>full</code>: Print diagnostics verbosely, with context and helpful hints [default]</li>
|
||||
<li><code>full</code>: Print diagnostics verbosely, with context and helpful hints (default)</li>
|
||||
<li><code>concise</code>: Print diagnostics concisely, one per line</li>
|
||||
<li><code>gitlab</code>: Print diagnostics in the JSON format expected by GitLab Code Quality reports</li>
|
||||
</ul></dd><dt id="ty-check--project"><a href="#ty-check--project"><code>--project</code></a> <i>project</i></dt><dd><p>Run the command within the given project directory.</p>
|
||||
<p>All <code>pyproject.toml</code> files will be discovered by walking up the directory tree from the given project directory, as will the project's virtual environment (<code>.venv</code>) unless the <code>venv-path</code> option is set.</p>
|
||||
<p>Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.</p>
|
||||
|
||||
2
crates/ty/docs/configuration.md
generated
2
crates/ty/docs/configuration.md
generated
@@ -144,7 +144,7 @@ If left unspecified, ty will try to detect common project layouts and initialize
|
||||
* if a `./<project-name>/<project-name>` directory exists, include `.` and `./<project-name>` in the first party search path
|
||||
* otherwise, default to `.` (flat layout)
|
||||
|
||||
Besides, if a `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` file),
|
||||
Besides, if a `./python` or `./tests` directory exists and is not a package (i.e. it does not contain an `__init__.py` or `__init__.pyi` file),
|
||||
it will also be included in the first party search path.
|
||||
|
||||
**Default value**: `null`
|
||||
|
||||
@@ -306,7 +306,7 @@ impl clap::Args for RulesArg {
|
||||
/// The diagnostic output format.
|
||||
#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord, Default, clap::ValueEnum)]
|
||||
pub enum OutputFormat {
|
||||
/// Print diagnostics verbosely, with context and helpful hints \[default\].
|
||||
/// Print diagnostics verbosely, with context and helpful hints (default).
|
||||
///
|
||||
/// Diagnostic messages may include additional context and
|
||||
/// annotations on the input to help understand the message.
|
||||
@@ -321,6 +321,9 @@ pub enum OutputFormat {
|
||||
/// dropped.
|
||||
#[value(name = "concise")]
|
||||
Concise,
|
||||
/// Print diagnostics in the JSON format expected by GitLab Code Quality reports.
|
||||
#[value(name = "gitlab")]
|
||||
Gitlab,
|
||||
}
|
||||
|
||||
impl From<OutputFormat> for ty_project::metadata::options::OutputFormat {
|
||||
@@ -328,6 +331,7 @@ impl From<OutputFormat> for ty_project::metadata::options::OutputFormat {
|
||||
match format {
|
||||
OutputFormat::Full => Self::Full,
|
||||
OutputFormat::Concise => Self::Concise,
|
||||
OutputFormat::Gitlab => Self::Gitlab,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ use clap::{CommandFactory, Parser};
|
||||
use colored::Colorize;
|
||||
use crossbeam::channel as crossbeam_channel;
|
||||
use rayon::ThreadPoolBuilder;
|
||||
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, Severity};
|
||||
use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig, DisplayDiagnostics, Severity};
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::max_parallelism;
|
||||
use ruff_db::system::{OsSystem, SystemPath, SystemPathBuf};
|
||||
@@ -319,37 +319,48 @@ impl MainLoop {
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
|
||||
let is_human_readable = terminal_settings.output_format.is_human_readable();
|
||||
|
||||
if result.is_empty() {
|
||||
writeln!(
|
||||
self.printer.stream_for_success_summary(),
|
||||
"{}",
|
||||
"All checks passed!".green().bold()
|
||||
)?;
|
||||
if is_human_readable {
|
||||
writeln!(
|
||||
self.printer.stream_for_success_summary(),
|
||||
"{}",
|
||||
"All checks passed!".green().bold()
|
||||
)?;
|
||||
}
|
||||
|
||||
if self.watcher.is_none() {
|
||||
return Ok(ExitStatus::Success);
|
||||
}
|
||||
} else {
|
||||
let mut max_severity = Severity::Info;
|
||||
let diagnostics_count = result.len();
|
||||
|
||||
let mut stdout = self.printer.stream_for_details().lock();
|
||||
for diagnostic in result {
|
||||
// Only render diagnostics if they're going to be displayed, since doing
|
||||
// so is expensive.
|
||||
if stdout.is_enabled() {
|
||||
write!(stdout, "{}", diagnostic.display(db, &display_config))?;
|
||||
}
|
||||
let max_severity = result
|
||||
.iter()
|
||||
.map(Diagnostic::severity)
|
||||
.max()
|
||||
.unwrap_or(Severity::Info);
|
||||
|
||||
max_severity = max_severity.max(diagnostic.severity());
|
||||
// Only render diagnostics if they're going to be displayed, since doing
|
||||
// so is expensive.
|
||||
if stdout.is_enabled() {
|
||||
write!(
|
||||
stdout,
|
||||
"{}",
|
||||
DisplayDiagnostics::new(db, &display_config, &result)
|
||||
)?;
|
||||
}
|
||||
|
||||
writeln!(
|
||||
self.printer.stream_for_failure_summary(),
|
||||
"Found {} diagnostic{}",
|
||||
diagnostics_count,
|
||||
if diagnostics_count > 1 { "s" } else { "" }
|
||||
)?;
|
||||
if is_human_readable {
|
||||
writeln!(
|
||||
self.printer.stream_for_failure_summary(),
|
||||
"Found {} diagnostic{}",
|
||||
diagnostics_count,
|
||||
if diagnostics_count > 1 { "s" } else { "" }
|
||||
)?;
|
||||
}
|
||||
|
||||
if max_severity.is_fatal() {
|
||||
tracing::warn!(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user