Compare commits
55 Commits
ibraheem/p
...
jack/newty
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
136dc15352 | ||
|
|
18eaa659c1 | ||
|
|
af259faed5 | ||
|
|
d75ef3823c | ||
|
|
89ca493fd9 | ||
|
|
4b80f5fa4f | ||
|
|
5663426d73 | ||
|
|
0b3548755c | ||
|
|
ce1dc21e7e | ||
|
|
7d0c8e045c | ||
|
|
d71518b369 | ||
|
|
9ab276b345 | ||
|
|
a60fb3f2c8 | ||
|
|
f558bf721c | ||
|
|
ea1c080881 | ||
|
|
73720c73be | ||
|
|
136abace92 | ||
|
|
bc7274d148 | ||
|
|
911d5cc973 | ||
|
|
8d6dc7d3a3 | ||
|
|
ef4897f9f3 | ||
|
|
ecf3c4ca11 | ||
|
|
33c5f6f4f8 | ||
|
|
ba47010150 | ||
|
|
db423ee978 | ||
|
|
a04823cfad | ||
|
|
d0bcf56bd9 | ||
|
|
f9bbee33f6 | ||
|
|
376e3ff395 | ||
|
|
b57cc5be33 | ||
|
|
3c1fe12259 | ||
|
|
e4289deb5a | ||
|
|
dbbcb7f452 | ||
|
|
c6dfdb1d39 | ||
|
|
c65029f9a5 | ||
|
|
a0bba718f6 | ||
|
|
1eab4dbd95 | ||
|
|
3eb3c3572b | ||
|
|
41bb24a87e | ||
|
|
87f0da139a | ||
|
|
f9bfc9ab5b | ||
|
|
59f7102606 | ||
|
|
862d2d0687 | ||
|
|
48edf46f3b | ||
|
|
ec86a4e960 | ||
|
|
e7237652a9 | ||
|
|
205eae14d2 | ||
|
|
f407f12f4c | ||
|
|
fb2d0af18c | ||
|
|
8ead02e0b1 | ||
|
|
330bb4efbf | ||
|
|
ad8c98117a | ||
|
|
06dbec8479 | ||
|
|
85931ab594 | ||
|
|
b3cc733f06 |
16
.github/workflows/ci.yaml
vendored
16
.github/workflows/ci.yaml
vendored
@@ -441,7 +441,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: "Install cargo-binstall"
|
||||
uses: cargo-bins/cargo-binstall@79e4beb1e02f733a26129a6bf26c37dab4ab3307 # v1.14.4
|
||||
uses: cargo-bins/cargo-binstall@0dca8cf8dfb40cb77a29cece06933ce674674523 # v1.15.1
|
||||
with:
|
||||
tool: cargo-fuzz@0.11.2
|
||||
- name: "Install cargo-fuzz"
|
||||
@@ -463,7 +463,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
name: Download Ruff binary to test
|
||||
id: download-cached-binary
|
||||
@@ -664,7 +664,7 @@ jobs:
|
||||
branch: ${{ github.event.pull_request.base.ref }}
|
||||
workflow: "ci.yaml"
|
||||
check_artifacts: true
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- name: Fuzz
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
@@ -694,7 +694,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: cargo-bins/cargo-binstall@79e4beb1e02f733a26129a6bf26c37dab4ab3307 # v1.14.4
|
||||
- uses: cargo-bins/cargo-binstall@0dca8cf8dfb40cb77a29cece06933ce674674523 # v1.15.1
|
||||
- run: cargo binstall --no-confirm cargo-shear
|
||||
- run: cargo shear
|
||||
|
||||
@@ -734,7 +734,7 @@ jobs:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
with:
|
||||
@@ -777,7 +777,7 @@ jobs:
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- name: "Install Insiders dependencies"
|
||||
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
|
||||
run: uv pip install -r docs/requirements-insiders.txt --system
|
||||
@@ -909,7 +909,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
|
||||
- name: "Install Rust toolchain"
|
||||
run: rustup show
|
||||
@@ -942,7 +942,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
- uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
|
||||
- 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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- 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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
|
||||
- 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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
|
||||
- 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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
pattern: wheels-*
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -61,7 +61,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -124,7 +124,7 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -175,7 +175,7 @@ jobs:
|
||||
outputs:
|
||||
val: ${{ steps.host.outputs.manifest }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
@@ -251,7 +251,7 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- 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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- 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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
- uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --global user.name typeshedbot
|
||||
|
||||
2
.github/workflows/ty-ecosystem-analyzer.yaml
vendored
2
.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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
|
||||
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@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@4959332f0f014c5280e7eac8b70c90cb574c9f9b # v6.6.0
|
||||
|
||||
- uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0
|
||||
with:
|
||||
|
||||
199
Cargo.lock
generated
199
Cargo.lock
generated
@@ -257,12 +257,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.2"
|
||||
version = "2.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
@@ -298,7 +295,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-automata 0.4.10",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -488,7 +485,7 @@ checksum = "85a8ab73a1c02b0c15597b22e09c7dc36e63b2f601f9d1e83ac0c3decd38b1ae"
|
||||
dependencies = [
|
||||
"nix 0.29.0",
|
||||
"terminfo",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.16",
|
||||
"which",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
@@ -958,7 +955,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1031,16 +1028,6 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "erased-serde"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"typeid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.13"
|
||||
@@ -1048,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.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1097,14 +1084,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.25"
|
||||
version = "0.2.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
|
||||
checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"libredox",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1131,9 +1118,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
|
||||
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
@@ -1244,7 +1231,7 @@ dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"log",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-automata 0.4.10",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
@@ -1254,7 +1241,7 @@ version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
|
||||
dependencies = [
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"ignore",
|
||||
"walkdir",
|
||||
]
|
||||
@@ -1443,9 +1430,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.0.3"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
|
||||
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
|
||||
dependencies = [
|
||||
"idna_adapter",
|
||||
"smallvec",
|
||||
@@ -1472,7 +1459,7 @@ dependencies = [
|
||||
"globset",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-automata 0.4.10",
|
||||
"same-file",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
@@ -1499,9 +1486,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.10.0"
|
||||
version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
|
||||
checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.5",
|
||||
@@ -1534,7 +1521,7 @@ version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
|
||||
dependencies = [
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
@@ -1793,7 +1780,7 @@ dependencies = [
|
||||
"paste",
|
||||
"peg",
|
||||
"regex",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1822,7 +1809,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
|
||||
dependencies = [
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
]
|
||||
@@ -2027,7 +2014,7 @@ version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
@@ -2039,7 +2026,7 @@ version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
@@ -2067,7 +2054,7 @@ version = "8.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
|
||||
dependencies = [
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
@@ -2140,9 +2127,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordermap"
|
||||
version = "0.5.8"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d6bff06e4a5dc6416bead102d3e63c480dd852ffbb278bf8cfeb4966b329609"
|
||||
checksum = "2fd6fedcd996c8c97932075cc3811d83f53280f48d5620e4e3cab7f6a12678c4"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -2296,9 +2283,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
@@ -2307,7 +2294,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.16",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
@@ -2486,9 +2473,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.96"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "beef09f85ae72cea1ef96ba6870c51e6382ebfa4f0e85b643459331f3daa5be0"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -2503,7 +2490,7 @@ dependencies = [
|
||||
"pep440_rs",
|
||||
"pep508_rs",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.16",
|
||||
"toml 0.8.23",
|
||||
]
|
||||
|
||||
@@ -2518,7 +2505,7 @@ dependencies = [
|
||||
"newtype-uuid",
|
||||
"quick-xml",
|
||||
"strip-ansi-escapes",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.16",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@@ -2679,7 +2666,7 @@ version = "0.5.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
|
||||
dependencies = [
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2690,18 +2677,18 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"libredox",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-automata 0.4.10",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
@@ -2716,9 +2703,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -2762,7 +2749,7 @@ dependencies = [
|
||||
"argfile",
|
||||
"assert_fs",
|
||||
"bincode 2.0.1",
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"cachedir",
|
||||
"clap",
|
||||
"clap_complete_command",
|
||||
@@ -2806,7 +2793,7 @@ dependencies = [
|
||||
"strum",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.16",
|
||||
"tikv-jemallocator",
|
||||
"toml 0.9.5",
|
||||
"tracing",
|
||||
@@ -2901,7 +2888,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"similar",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.16",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"ty_static",
|
||||
@@ -2948,6 +2935,7 @@ dependencies = [
|
||||
"tracing-subscriber",
|
||||
"ty",
|
||||
"ty_project",
|
||||
"ty_python_semantic",
|
||||
"ty_static",
|
||||
"url",
|
||||
]
|
||||
@@ -3014,7 +3002,7 @@ version = "0.12.10"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"clap",
|
||||
"colored 3.0.0",
|
||||
"fern",
|
||||
@@ -3061,7 +3049,7 @@ dependencies = [
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.16",
|
||||
"toml 0.9.5",
|
||||
"typed-arena",
|
||||
"unicode-normalization",
|
||||
@@ -3104,7 +3092,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"test-case",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.16",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@@ -3120,7 +3108,7 @@ name = "ruff_python_ast"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"compact_str",
|
||||
"get-size2",
|
||||
"is-macro",
|
||||
@@ -3135,7 +3123,7 @@ dependencies = [
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3189,7 +3177,7 @@ dependencies = [
|
||||
"similar",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.16",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -3208,7 +3196,7 @@ dependencies = [
|
||||
name = "ruff_python_literal"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"itertools 0.14.0",
|
||||
"ruff_python_ast",
|
||||
"unic-ucd-category",
|
||||
@@ -3219,7 +3207,7 @@ name = "ruff_python_parser"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"bstr",
|
||||
"compact_str",
|
||||
"get-size2",
|
||||
@@ -3244,7 +3232,7 @@ dependencies = [
|
||||
name = "ruff_python_semantic"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"insta",
|
||||
"is-macro",
|
||||
"ruff_cache",
|
||||
@@ -3265,7 +3253,7 @@ dependencies = [
|
||||
name = "ruff_python_stdlib"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
@@ -3319,7 +3307,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.16",
|
||||
"toml 0.9.5",
|
||||
"tracing",
|
||||
"tracing-log",
|
||||
@@ -3442,11 +3430,11 @@ version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||
dependencies = [
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3464,13 +3452,12 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a0e7a06#a0e7a0660c93136f23bf08b4f1604eee3d1f6b11"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a3ffa22cb26756473d56f867aedec3fd907c4dd9#a3ffa22cb26756473d56f867aedec3fd907c4dd9"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
"erased-serde",
|
||||
"hashbrown 0.15.5",
|
||||
"hashlink",
|
||||
"indexmap",
|
||||
@@ -3481,7 +3468,6 @@ dependencies = [
|
||||
"rustc-hash",
|
||||
"salsa-macro-rules",
|
||||
"salsa-macros",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"thin-vec",
|
||||
"tracing",
|
||||
@@ -3490,12 +3476,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a0e7a06#a0e7a0660c93136f23bf08b4f1604eee3d1f6b11"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a3ffa22cb26756473d56f867aedec3fd907c4dd9#a3ffa22cb26756473d56f867aedec3fd907c4dd9"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a0e7a06#a0e7a0660c93136f23bf08b4f1604eee3d1f6b11"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=a3ffa22cb26756473d56f867aedec3fd907c4dd9#a3ffa22cb26756473d56f867aedec3fd907c4dd9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3592,9 +3578,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.142"
|
||||
version = "1.0.143"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
|
||||
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
@@ -3715,9 +3701,6 @@ name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snapbox"
|
||||
@@ -3808,9 +3791,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.104"
|
||||
version = "2.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3922,9 +3905,6 @@ name = "thin-vec"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
@@ -3937,11 +3917,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
version = "2.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.12",
|
||||
"thiserror-impl 2.0.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3957,9 +3937,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.12"
|
||||
version = "2.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4158,9 +4138,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-indicatif"
|
||||
version = "0.3.12"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1983afead46ff13a3c93581e0cec31d20b29efdd22cbdaa8b9f850eccf2c352"
|
||||
checksum = "04d4e11e0e27acef25a47f27e9435355fecdc488867fa2bc90e75b0700d2823d"
|
||||
dependencies = [
|
||||
"indicatif",
|
||||
"tracing",
|
||||
@@ -4260,17 +4240,22 @@ dependencies = [
|
||||
name = "ty_ide"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"get-size2",
|
||||
"insta",
|
||||
"itertools 0.14.0",
|
||||
"rayon",
|
||||
"regex",
|
||||
"ruff_db",
|
||||
"ruff_index",
|
||||
"ruff_memory_usage",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"salsa",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
"ty_project",
|
||||
@@ -4282,7 +4267,6 @@ name = "ty_project"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode 2.0.1",
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
"crossbeam",
|
||||
@@ -4294,7 +4278,7 @@ dependencies = [
|
||||
"pep440_rs",
|
||||
"rayon",
|
||||
"regex",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-automata 0.4.10",
|
||||
"ruff_cache",
|
||||
"ruff_db",
|
||||
"ruff_macros",
|
||||
@@ -4307,12 +4291,11 @@ dependencies = [
|
||||
"salsa",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.16",
|
||||
"toml 0.9.5",
|
||||
"tracing",
|
||||
"ty_combine",
|
||||
"ty_python_semantic",
|
||||
"ty_static",
|
||||
"ty_vendored",
|
||||
]
|
||||
|
||||
@@ -4321,7 +4304,7 @@ name = "ty_python_semantic"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"bitvec",
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
@@ -4361,7 +4344,7 @@ dependencies = [
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"test-case",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.16",
|
||||
"tracing",
|
||||
"ty_python_semantic",
|
||||
"ty_static",
|
||||
@@ -4374,7 +4357,7 @@ name = "ty_server"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"crossbeam",
|
||||
"dunce",
|
||||
"insta",
|
||||
@@ -4395,7 +4378,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"shellexpand",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.16",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"ty_combine",
|
||||
@@ -4417,7 +4400,7 @@ name = "ty_test"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
"camino",
|
||||
"colored 3.0.0",
|
||||
"insta",
|
||||
@@ -4436,7 +4419,7 @@ dependencies = [
|
||||
"serde",
|
||||
"smallvec",
|
||||
"tempfile",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.16",
|
||||
"toml 0.9.5",
|
||||
"tracing",
|
||||
"ty_python_semantic",
|
||||
@@ -4484,12 +4467,6 @@ version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
@@ -4619,9 +4596,9 @@ checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.4"
|
||||
version = "2.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
|
||||
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
@@ -5173,7 +5150,7 @@ version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.2",
|
||||
"bitflags 2.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
15
Cargo.toml
15
Cargo.toml
@@ -57,8 +57,8 @@ anyhow = { version = "1.0.80" }
|
||||
arc-swap = { version = "1.7.1" }
|
||||
assert_fs = { version = "1.1.0" }
|
||||
argfile = { version = "0.2.0" }
|
||||
bincode = { version = "2.0.0", features = ["serde"] }
|
||||
bitflags = { version = "2.5.0", features = ["serde"] }
|
||||
bincode = { version = "2.0.0" }
|
||||
bitflags = { version = "2.5.0" }
|
||||
bitvec = { version = "1.0.1", default-features = false, features = [
|
||||
"alloc",
|
||||
] }
|
||||
@@ -126,7 +126,7 @@ memchr = { version = "2.7.1" }
|
||||
mimalloc = { version = "0.1.39" }
|
||||
natord = { version = "1.0.9" }
|
||||
notify = { version = "8.0.0" }
|
||||
ordermap = { version = "0.5.0", features = ["serde"] }
|
||||
ordermap = { version = "0.5.0" }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
path-slash = { version = "0.2.1" }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
@@ -143,25 +143,24 @@ regex-automata = { version = "0.4.9" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
rustc-stable-hash = { version = "0.1.2" }
|
||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "a0e7a06", default-features = false, features = [
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "a3ffa22cb26756473d56f867aedec3fd907c4dd9", default-features = false, features = [
|
||||
"compact_str",
|
||||
"macros",
|
||||
"salsa_unstable",
|
||||
"inventory",
|
||||
"persistence",
|
||||
] }
|
||||
schemars = { version = "0.8.16" }
|
||||
seahash = { version = "4.1.0" }
|
||||
serde = { version = "1.0.197", features = ["derive", "rc"] }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde-wasm-bindgen = { version = "0.6.4" }
|
||||
serde_json = { version = "1.0.142" }
|
||||
serde_json = { version = "1.0.113" }
|
||||
serde_test = { version = "1.0.152" }
|
||||
serde_with = { version = "3.6.0", default-features = false, features = [
|
||||
"macros",
|
||||
] }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
similar = { version = "2.4.0", features = ["inline"] }
|
||||
smallvec = { version = "1.13.2", features = ["union", "const_generics", "const_new", "serde"] }
|
||||
smallvec = { version = "1.13.2", features = ["union", "const_generics", "const_new"] }
|
||||
snapbox = { version = "0.6.0", features = [
|
||||
"diff",
|
||||
"term-svg",
|
||||
|
||||
@@ -31,7 +31,7 @@ ruff_workspace = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
argfile = { workspace = true }
|
||||
bincode = { workspace = true }
|
||||
bincode = { workspace = true, features = ["serde"] }
|
||||
bitflags = { workspace = true }
|
||||
cachedir = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive", "env", "wrap_help"] }
|
||||
|
||||
@@ -22,7 +22,6 @@ mod stylesheet;
|
||||
/// a characteristic is a deficiency. An example of a characteristic that is
|
||||
/// _not_ a deficiency is the `reveal_type` diagnostic for our type checker.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Diagnostic {
|
||||
/// The actual diagnostic.
|
||||
///
|
||||
@@ -326,6 +325,11 @@ impl Diagnostic {
|
||||
self.inner.fix.as_ref()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn fix_mut(&mut self) -> Option<&mut Fix> {
|
||||
Arc::make_mut(&mut self.inner).fix.as_mut()
|
||||
}
|
||||
|
||||
/// Set the fix for this diagnostic.
|
||||
pub fn set_fix(&mut self, fix: Fix) {
|
||||
debug_assert!(
|
||||
@@ -501,7 +505,6 @@ impl Diagnostic {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
struct DiagnosticInner {
|
||||
id: DiagnosticId,
|
||||
severity: Severity,
|
||||
@@ -578,7 +581,6 @@ impl Eq for RenderingSortKey<'_> {}
|
||||
/// another (for a single parent diagnostic) is the order in which they were
|
||||
/// attached to the diagnostic.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct SubDiagnostic {
|
||||
/// Like with `Diagnostic`, we box the `SubDiagnostic` to make it
|
||||
/// pointer-sized.
|
||||
@@ -688,7 +690,6 @@ impl SubDiagnostic {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
struct SubDiagnosticInner {
|
||||
severity: SubDiagnosticSeverity,
|
||||
message: DiagnosticMessage,
|
||||
@@ -717,7 +718,6 @@ struct SubDiagnosticInner {
|
||||
/// Messages attached to annotations should also be as brief and specific as
|
||||
/// possible. Long messages could negative impact the quality of rendering.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Annotation {
|
||||
/// The span of this annotation, corresponding to some subsequence of the
|
||||
/// user's input that we want to highlight.
|
||||
@@ -860,7 +860,6 @@ impl Annotation {
|
||||
/// These tags are used to provide additional information about the annotation.
|
||||
/// and are passed through to the language server protocol.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum DiagnosticTag {
|
||||
/// Unused or unnecessary code. Used for unused parameters, unreachable code, etc.
|
||||
Unnecessary,
|
||||
@@ -875,7 +874,6 @@ pub enum DiagnosticTag {
|
||||
///
|
||||
/// Rules use kebab case, e.g. `no-foo`.
|
||||
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
|
||||
pub struct LintName(&'static str);
|
||||
|
||||
impl LintName {
|
||||
@@ -888,66 +886,6 @@ impl LintName {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
pub use lint_name_serde::LintRegistryGuard;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
mod lint_name_serde {
|
||||
use super::LintName;
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
thread_local! {
|
||||
/// Serde doesn't provide any easy means to pass a value to a [`Deserialize`] implementation,
|
||||
/// but we need a way to retrieve static [`LintName`]s from the lint registry when deserializing.
|
||||
///
|
||||
/// Use the [`LintRegistryGuard`] to initialize the thread local before calling into any
|
||||
/// deserialization code. It ensures that the thread local variable gets cleaned up
|
||||
/// once deserialization is done (once the guard gets dropped).
|
||||
static LINT_REGISTRY: RefCell<Option<LintRegistry>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
type LintRegistry = fn(&str) -> Option<LintName>;
|
||||
|
||||
/// Guard to safely change the lint registry for the current thread.
|
||||
#[must_use]
|
||||
pub struct LintRegistryGuard {
|
||||
prev_value: Option<LintRegistry>,
|
||||
}
|
||||
|
||||
impl LintRegistryGuard {
|
||||
pub fn new(registry: LintRegistry) -> Self {
|
||||
let prev = LINT_REGISTRY.replace(Some(registry));
|
||||
Self { prev_value: prev }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LintRegistryGuard {
|
||||
fn drop(&mut self) {
|
||||
LINT_REGISTRY.set(self.prev_value.take());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for LintName {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let name: &str = serde::Deserialize::deserialize(deserializer)?;
|
||||
|
||||
LINT_REGISTRY.with_borrow(|registry| {
|
||||
let registry = registry
|
||||
.expect("must set the `LintRegistryGuard` when deserializing a `LintName`");
|
||||
|
||||
registry(name).ok_or(serde::de::Error::custom(format!(
|
||||
"invalid `LintName` {name}"
|
||||
)))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for LintName {
|
||||
type Target = str;
|
||||
|
||||
@@ -976,7 +914,6 @@ impl PartialEq<&str> for LintName {
|
||||
|
||||
/// Uniquely identifies the kind of a diagnostic.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum DiagnosticId {
|
||||
Panic,
|
||||
|
||||
@@ -1165,30 +1102,6 @@ impl UnifiedFile {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for UnifiedFile {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
UnifiedFile::Ty(file) => serde::Serialize::serialize(file, serializer),
|
||||
// Persistent caching is only used in ty.
|
||||
UnifiedFile::Ruff(..) => panic!("Ruff files are not persistable"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for UnifiedFile {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
serde::Deserialize::deserialize(deserializer).map(UnifiedFile::Ty)
|
||||
}
|
||||
}
|
||||
|
||||
/// A unified wrapper for types that can be converted to a [`SourceCode`].
|
||||
///
|
||||
/// As with [`UnifiedFile`], ruff and ty use slightly different representations for source code.
|
||||
@@ -1220,7 +1133,6 @@ impl DiagnosticSource {
|
||||
/// range isn't present, it semantically implies that the diagnostic refers to
|
||||
/// the entire file. For example, when the file should be executable but isn't.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Span {
|
||||
file: UnifiedFile,
|
||||
range: Option<TextRange>,
|
||||
@@ -1299,7 +1211,6 @@ impl From<crate::files::FileRange> for Span {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum Severity {
|
||||
Info,
|
||||
Warning,
|
||||
@@ -1335,7 +1246,6 @@ impl Severity {
|
||||
/// used for main diagnostics. If we want to add `Severity::Help` in the future, this type could be
|
||||
/// deleted and the two combined again.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum SubDiagnosticSeverity {
|
||||
Help,
|
||||
Info,
|
||||
@@ -1584,7 +1494,6 @@ impl std::fmt::Display for ConciseMessage<'_> {
|
||||
/// a blanket trait implementation for `IntoDiagnosticMessage` for
|
||||
/// anything that implements `std::fmt::Display`.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct DiagnosticMessage(Box<str>);
|
||||
|
||||
impl DiagnosticMessage {
|
||||
@@ -1648,11 +1557,7 @@ impl<T: std::fmt::Display> IntoDiagnosticMessage for T {
|
||||
///
|
||||
/// For Ruff rules this means the noqa code.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash, get_size2::GetSize)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Serialize, serde::Deserialize),
|
||||
serde(transparent)
|
||||
)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))]
|
||||
pub struct SecondaryCode(String);
|
||||
|
||||
impl SecondaryCode {
|
||||
|
||||
@@ -2622,6 +2622,13 @@ watermelon
|
||||
self.config = config;
|
||||
}
|
||||
|
||||
/// Show a diff for the fix when rendering.
|
||||
pub(super) fn show_fix_diff(&mut self, yes: bool) {
|
||||
let mut config = std::mem::take(&mut self.config);
|
||||
config = config.show_fix_diff(yes);
|
||||
self.config = config;
|
||||
}
|
||||
|
||||
/// The lowest fix applicability to show when rendering.
|
||||
pub(super) fn fix_applicability(&mut self, applicability: Applicability) {
|
||||
let mut config = std::mem::take(&mut self.config);
|
||||
|
||||
@@ -2,12 +2,13 @@ use std::borrow::Cow;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use anstyle::Style;
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
|
||||
use ruff_annotate_snippets::Renderer as AnnotateRenderer;
|
||||
use ruff_diagnostics::{Applicability, Fix};
|
||||
use ruff_source_file::OneIndexed;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::diagnostic::render::{FileResolver, Resolved};
|
||||
use crate::diagnostic::stylesheet::{DiagnosticStylesheet, fmt_styled};
|
||||
@@ -81,6 +82,7 @@ impl<'a> FullRenderer<'a> {
|
||||
struct Diff<'a> {
|
||||
fix: &'a Fix,
|
||||
diagnostic_source: DiagnosticSource,
|
||||
notebook_index: Option<NotebookIndex>,
|
||||
stylesheet: &'a DiagnosticStylesheet,
|
||||
}
|
||||
|
||||
@@ -90,12 +92,11 @@ impl<'a> Diff<'a> {
|
||||
stylesheet: &'a DiagnosticStylesheet,
|
||||
resolver: &'a dyn FileResolver,
|
||||
) -> Option<Diff<'a>> {
|
||||
let file = &diagnostic.primary_span_ref()?.file;
|
||||
Some(Diff {
|
||||
fix: diagnostic.fix()?,
|
||||
diagnostic_source: diagnostic
|
||||
.primary_span_ref()?
|
||||
.file
|
||||
.diagnostic_source(resolver),
|
||||
diagnostic_source: file.diagnostic_source(resolver),
|
||||
notebook_index: resolver.notebook_index(file),
|
||||
stylesheet,
|
||||
})
|
||||
}
|
||||
@@ -106,19 +107,24 @@ impl std::fmt::Display for Diff<'_> {
|
||||
let source_code = self.diagnostic_source.as_source_code();
|
||||
let source_text = source_code.text();
|
||||
|
||||
// TODO(dhruvmanila): Add support for Notebook cells once it's user-facing
|
||||
let mut output = String::with_capacity(source_text.len());
|
||||
let mut last_end = TextSize::default();
|
||||
|
||||
for edit in self.fix.edits() {
|
||||
output.push_str(source_code.slice(TextRange::new(last_end, edit.start())));
|
||||
output.push_str(edit.content().unwrap_or_default());
|
||||
last_end = edit.end();
|
||||
}
|
||||
|
||||
output.push_str(&source_text[usize::from(last_end)..]);
|
||||
|
||||
let diff = TextDiff::from_lines(source_text, &output);
|
||||
// Partition the source code into end offsets for each cell. If `self.notebook_index` is
|
||||
// `None`, indicating a regular script file, all the lines will be in one "cell" under the
|
||||
// `None` key.
|
||||
let cells = if let Some(notebook_index) = &self.notebook_index {
|
||||
let mut last_cell = OneIndexed::MIN;
|
||||
let mut cells: Vec<(Option<OneIndexed>, TextSize)> = Vec::new();
|
||||
for (row, cell) in notebook_index.iter() {
|
||||
if cell != last_cell {
|
||||
let offset = source_code.line_start(row);
|
||||
cells.push((Some(last_cell), offset));
|
||||
last_cell = cell;
|
||||
}
|
||||
}
|
||||
cells.push((Some(last_cell), source_text.text_len()));
|
||||
cells
|
||||
} else {
|
||||
vec![(None, source_text.text_len())]
|
||||
};
|
||||
|
||||
let message = match self.fix.applicability() {
|
||||
// TODO(zanieb): Adjust this messaging once it's user-facing
|
||||
@@ -133,59 +139,97 @@ impl std::fmt::Display for Diff<'_> {
|
||||
// tests, which is the only place these are currently used.
|
||||
writeln!(f, "ℹ {}", fmt_styled(message, self.stylesheet.separator))?;
|
||||
|
||||
let (largest_old, largest_new) = diff
|
||||
.ops()
|
||||
.last()
|
||||
.map(|op| (op.old_range().start, op.new_range().start))
|
||||
.unwrap_or_default();
|
||||
let mut last_end = TextSize::ZERO;
|
||||
for (cell, offset) in cells {
|
||||
let range = TextRange::new(last_end, offset);
|
||||
last_end = offset;
|
||||
let input = source_code.slice(range);
|
||||
|
||||
let digit_with = OneIndexed::from_zero_indexed(largest_new.max(largest_old)).digits();
|
||||
let mut output = String::with_capacity(input.len());
|
||||
let mut last_end = range.start();
|
||||
|
||||
for (idx, group) in diff.grouped_ops(3).iter().enumerate() {
|
||||
if idx > 0 {
|
||||
writeln!(f, "{:-^1$}", "-", 80)?;
|
||||
let mut applied = 0;
|
||||
for edit in self.fix.edits() {
|
||||
if range.contains_range(edit.range()) {
|
||||
output.push_str(source_code.slice(TextRange::new(last_end, edit.start())));
|
||||
output.push_str(edit.content().unwrap_or_default());
|
||||
last_end = edit.end();
|
||||
applied += 1;
|
||||
}
|
||||
}
|
||||
for op in group {
|
||||
for change in diff.iter_inline_changes(op) {
|
||||
let sign = match change.tag() {
|
||||
ChangeTag::Delete => "-",
|
||||
ChangeTag::Insert => "+",
|
||||
ChangeTag::Equal => " ",
|
||||
};
|
||||
|
||||
let line_style = LineStyle::from(change.tag(), self.stylesheet);
|
||||
// No edits were applied, so there's no need to diff.
|
||||
if applied == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let old_index = change.old_index().map(OneIndexed::from_zero_indexed);
|
||||
let new_index = change.new_index().map(OneIndexed::from_zero_indexed);
|
||||
output.push_str(&source_text[usize::from(last_end)..usize::from(range.end())]);
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{} {} |{}",
|
||||
Line {
|
||||
index: old_index,
|
||||
width: digit_with
|
||||
},
|
||||
Line {
|
||||
index: new_index,
|
||||
width: digit_with
|
||||
},
|
||||
fmt_styled(line_style.apply_to(sign), self.stylesheet.emphasis),
|
||||
)?;
|
||||
let diff = TextDiff::from_lines(input, &output);
|
||||
|
||||
for (emphasized, value) in change.iter_strings_lossy() {
|
||||
let value = show_nonprinting(&value);
|
||||
if emphasized {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
fmt_styled(line_style.apply_to(&value), self.stylesheet.underline)
|
||||
)?;
|
||||
} else {
|
||||
write!(f, "{}", line_style.apply_to(&value))?;
|
||||
let (largest_old, largest_new) = diff
|
||||
.ops()
|
||||
.last()
|
||||
.map(|op| (op.old_range().start, op.new_range().start))
|
||||
.unwrap_or_default();
|
||||
|
||||
let digit_with = OneIndexed::from_zero_indexed(largest_new.max(largest_old)).digits();
|
||||
|
||||
if let Some(cell) = cell {
|
||||
// Room for 2 digits, 2 x 1 space before each digit, 1 space, and 1 `|`. This
|
||||
// centers the three colons on the pipe.
|
||||
writeln!(f, "{:>1$} cell {cell}", ":::", 2 * digit_with.get() + 4)?;
|
||||
}
|
||||
|
||||
for (idx, group) in diff.grouped_ops(3).iter().enumerate() {
|
||||
if idx > 0 {
|
||||
writeln!(f, "{:-^1$}", "-", 80)?;
|
||||
}
|
||||
for op in group {
|
||||
for change in diff.iter_inline_changes(op) {
|
||||
let sign = match change.tag() {
|
||||
ChangeTag::Delete => "-",
|
||||
ChangeTag::Insert => "+",
|
||||
ChangeTag::Equal => " ",
|
||||
};
|
||||
|
||||
let line_style = LineStyle::from(change.tag(), self.stylesheet);
|
||||
|
||||
let old_index = change.old_index().map(OneIndexed::from_zero_indexed);
|
||||
let new_index = change.new_index().map(OneIndexed::from_zero_indexed);
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{} {} |{}",
|
||||
Line {
|
||||
index: old_index,
|
||||
width: digit_with,
|
||||
},
|
||||
Line {
|
||||
index: new_index,
|
||||
width: digit_with,
|
||||
},
|
||||
fmt_styled(line_style.apply_to(sign), self.stylesheet.emphasis),
|
||||
)?;
|
||||
|
||||
for (emphasized, value) in change.iter_strings_lossy() {
|
||||
let value = show_nonprinting(&value);
|
||||
if emphasized {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
fmt_styled(
|
||||
line_style.apply_to(&value),
|
||||
self.stylesheet.underline
|
||||
)
|
||||
)?;
|
||||
} else {
|
||||
write!(f, "{}", line_style.apply_to(&value))?;
|
||||
}
|
||||
}
|
||||
if change.missing_newline() {
|
||||
writeln!(f)?;
|
||||
}
|
||||
}
|
||||
if change.missing_newline() {
|
||||
writeln!(f)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,7 +297,7 @@ fn show_nonprinting(s: &str) -> Cow<'_, str> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruff_diagnostics::Applicability;
|
||||
use ruff_diagnostics::{Applicability, Fix};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::diagnostic::{
|
||||
@@ -654,6 +698,107 @@ print()
|
||||
");
|
||||
}
|
||||
|
||||
/// Test that we remap notebook cell line numbers in the diff as well as the main diagnostic.
|
||||
#[test]
|
||||
fn notebook_output_with_diff() {
|
||||
let (mut env, diagnostics) = create_notebook_diagnostics(DiagnosticFormat::Full);
|
||||
env.show_fix_diff(true);
|
||||
insta::assert_snapshot!(env.render_diagnostics(&diagnostics), @r"
|
||||
error[unused-import][*]: `os` imported but unused
|
||||
--> notebook.ipynb:cell 1:2:8
|
||||
|
|
||||
1 | # cell 1
|
||||
2 | import os
|
||||
| ^^
|
||||
|
|
||||
help: Remove unused import: `os`
|
||||
|
||||
ℹ Safe fix
|
||||
::: cell 1
|
||||
1 1 | # cell 1
|
||||
2 |-import os
|
||||
|
||||
error[unused-import][*]: `math` imported but unused
|
||||
--> notebook.ipynb:cell 2:2:8
|
||||
|
|
||||
1 | # cell 2
|
||||
2 | import math
|
||||
| ^^^^
|
||||
3 |
|
||||
4 | print('hello world')
|
||||
|
|
||||
help: Remove unused import: `math`
|
||||
|
||||
ℹ Safe fix
|
||||
::: cell 2
|
||||
1 1 | # cell 2
|
||||
2 |-import math
|
||||
3 2 |
|
||||
4 3 | print('hello world')
|
||||
|
||||
error[unused-variable]: Local variable `x` is assigned to but never used
|
||||
--> notebook.ipynb:cell 3:4:5
|
||||
|
|
||||
2 | def foo():
|
||||
3 | print()
|
||||
4 | x = 1
|
||||
| ^
|
||||
|
|
||||
help: Remove assignment to unused variable `x`
|
||||
|
||||
ℹ Unsafe fix
|
||||
::: cell 3
|
||||
1 1 | # cell 3
|
||||
2 2 | def foo():
|
||||
3 3 | print()
|
||||
4 |- x = 1
|
||||
5 4 |
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notebook_output_with_diff_spanning_cells() {
|
||||
let (mut env, mut diagnostics) = create_notebook_diagnostics(DiagnosticFormat::Full);
|
||||
env.show_fix_diff(true);
|
||||
|
||||
// Move all of the edits from the later diagnostics to the first diagnostic to simulate a
|
||||
// single diagnostic with edits in different cells.
|
||||
let mut diagnostic = diagnostics.swap_remove(0);
|
||||
let fix = diagnostic.fix_mut().unwrap();
|
||||
let mut edits = fix.edits().to_vec();
|
||||
for diag in diagnostics {
|
||||
edits.extend_from_slice(diag.fix().unwrap().edits());
|
||||
}
|
||||
*fix = Fix::unsafe_edits(edits.remove(0), edits);
|
||||
|
||||
insta::assert_snapshot!(env.render(&diagnostic), @r"
|
||||
error[unused-import]: `os` imported but unused
|
||||
--> notebook.ipynb:cell 1:2:8
|
||||
|
|
||||
1 | # cell 1
|
||||
2 | import os
|
||||
| ^^
|
||||
|
|
||||
help: Remove unused import: `os`
|
||||
|
||||
ℹ Unsafe fix
|
||||
::: cell 1
|
||||
1 1 | # cell 1
|
||||
2 |-import os
|
||||
::: cell 2
|
||||
1 1 | # cell 2
|
||||
2 |-import math
|
||||
3 2 |
|
||||
4 3 | print('hello world')
|
||||
::: cell 3
|
||||
1 1 | # cell 3
|
||||
2 2 | def foo():
|
||||
3 3 | print()
|
||||
4 |- x = 1
|
||||
5 4 |
|
||||
");
|
||||
}
|
||||
|
||||
/// Carriage return (`\r`) is a valid line-ending in Python, so we should normalize this to a
|
||||
/// line feed (`\n`) for rendering. Otherwise we report a single long line for this case.
|
||||
#[test]
|
||||
|
||||
@@ -9,17 +9,7 @@ use crate::system::file_time_now;
|
||||
/// * The last modification time of the file.
|
||||
/// * The hash of the file's content.
|
||||
/// * The revision as it comes from an external system, for example the LSP.
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Default,
|
||||
get_size2::GetSize,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, get_size2::GetSize)]
|
||||
pub struct FileRevision(u128);
|
||||
|
||||
impl FileRevision {
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::diagnostic::{Span, UnifiedFile};
|
||||
use crate::file_revision::FileRevision;
|
||||
use crate::files::file_root::FileRoots;
|
||||
use crate::files::private::FileStatus;
|
||||
use crate::system::{FileType, SystemPath, SystemPathBuf, SystemVirtualPath, SystemVirtualPathBuf};
|
||||
use crate::system::{SystemPath, SystemPathBuf, SystemVirtualPath, SystemVirtualPathBuf};
|
||||
use crate::vendored::{VendoredPath, VendoredPathBuf};
|
||||
use crate::{Db, FxDashMap, vendored};
|
||||
|
||||
@@ -139,7 +139,6 @@ impl Files {
|
||||
};
|
||||
|
||||
tracing::trace!("Adding vendored file `{}`", path);
|
||||
|
||||
let file = File::builder(FilePath::Vendored(path.to_path_buf()))
|
||||
.permissions(Some(0o444))
|
||||
.revision(metadata.revision())
|
||||
@@ -201,15 +200,7 @@ impl Files {
|
||||
let mut roots = self.inner.roots.write().unwrap();
|
||||
|
||||
let absolute = SystemPath::absolute(path, db.system().current_directory());
|
||||
|
||||
let (Ok(root) | Err(root)) = roots.try_add(db, absolute, |absolute| {
|
||||
FileRoot::builder(absolute, kind, FileRevision::now())
|
||||
.durability(Durability::HIGH)
|
||||
.revision_durability(kind.durability())
|
||||
.new(db)
|
||||
});
|
||||
|
||||
root
|
||||
roots.try_add(db, absolute, kind)
|
||||
}
|
||||
|
||||
/// Updates the revision of the root for `path`.
|
||||
@@ -268,51 +259,6 @@ impl Files {
|
||||
root.set_revision(db).to(FileRevision::now());
|
||||
}
|
||||
}
|
||||
|
||||
/// Seed the files with an existing [`File`] instance.
|
||||
pub fn seed(&self, file: File, db: &dyn Db) {
|
||||
let seeded = match file.path(db) {
|
||||
FilePath::System(path) => self
|
||||
.inner
|
||||
.system_by_path
|
||||
.insert(path.clone(), file)
|
||||
.is_none(),
|
||||
FilePath::SystemVirtual(path) => self
|
||||
.inner
|
||||
.system_virtual_by_path
|
||||
.insert(path.clone(), VirtualFile(file))
|
||||
.is_none(),
|
||||
FilePath::Vendored(path) => self
|
||||
.inner
|
||||
.vendored_by_path
|
||||
.insert(path.clone(), file)
|
||||
.is_none(),
|
||||
};
|
||||
|
||||
// Recreating a `File` input means the persisted queries depending on that file
|
||||
// will be invalidated.
|
||||
assert!(
|
||||
seeded,
|
||||
"unexpected `File` input recreated for path `{}`",
|
||||
file.path(db)
|
||||
);
|
||||
}
|
||||
|
||||
/// Seed the files with an existing [`FileRoot`] instance.
|
||||
pub fn seed_root(&self, root: FileRoot, db: &dyn Db) {
|
||||
let mut roots = self.inner.roots.write().unwrap();
|
||||
let seeded = roots
|
||||
.try_add(db, root.path(db).to_path_buf(), |_| root)
|
||||
.is_ok();
|
||||
|
||||
// Recreating a `FileRoot` input means the persisted queries depending on that file
|
||||
// root will be invalidated.
|
||||
assert!(
|
||||
seeded,
|
||||
"unexpected `FileRoot` input recreated for path `{}`",
|
||||
root.path(db)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Files {
|
||||
@@ -344,7 +290,7 @@ impl std::panic::RefUnwindSafe for Files {}
|
||||
/// # Ordering
|
||||
/// Ordering is based on the file's salsa-assigned id and not on its values.
|
||||
/// The id may change between runs.
|
||||
#[salsa::input(persist, heap_size=ruff_memory_usage::heap_size)]
|
||||
#[salsa::input(heap_size=ruff_memory_usage::heap_size)]
|
||||
#[derive(PartialOrd, Ord)]
|
||||
pub struct File {
|
||||
/// The path of the file (immutable).
|
||||
@@ -468,15 +414,6 @@ impl File {
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads all existing [`File`]s in the database.
|
||||
pub fn load_all(db: &dyn Db) -> Vec<File> {
|
||||
// TODO: Prune deleted paths.
|
||||
File::ingredient(db)
|
||||
.entries(db.zalsa())
|
||||
.map(|entry| entry.as_struct())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Private method providing the implementation for [`Self::sync_path`] and [`Self::sync`] for
|
||||
/// system paths.
|
||||
fn sync_system_path(db: &mut dyn Db, path: &SystemPath, file: Option<File>) {
|
||||
@@ -585,17 +522,7 @@ impl VirtualFile {
|
||||
// The types in here need to be public because they're salsa ingredients but we
|
||||
// don't want them to be publicly accessible. That's why we put them into a private module.
|
||||
mod private {
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Default,
|
||||
get_size2::GetSize,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, get_size2::GetSize)]
|
||||
pub enum FileStatus {
|
||||
/// The file exists.
|
||||
#[default]
|
||||
@@ -609,16 +536,6 @@ mod private {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FileType> for FileStatus {
|
||||
fn from(value: FileType) -> Self {
|
||||
match value {
|
||||
FileType::File => FileStatus::Exists,
|
||||
FileType::Symlink => FileStatus::Exists,
|
||||
FileType::Directory => FileStatus::IsADirectory,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum FileError {
|
||||
IsADirectory,
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::system::{SystemPath, SystemPathBuf};
|
||||
/// The main usage of file roots is to determine a file's durability. But it can also be used
|
||||
/// to make a salsa query dependent on whether a file in a root has changed without writing any
|
||||
/// manual invalidation logic.
|
||||
#[salsa::input(persist, debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
#[salsa::input(debug, heap_size=ruff_memory_usage::heap_size)]
|
||||
pub struct FileRoot {
|
||||
/// The path of a root is guaranteed to never change.
|
||||
#[returns(deref)]
|
||||
@@ -35,20 +35,9 @@ impl FileRoot {
|
||||
pub fn durability(self, db: &dyn Db) -> salsa::Durability {
|
||||
self.kind_at_time_of_creation(db).durability()
|
||||
}
|
||||
|
||||
/// Loads all existing [`FileRoot`]s in the database.
|
||||
pub fn load_all(db: &dyn Db) -> Vec<FileRoot> {
|
||||
// TODO: Prune deleted paths.
|
||||
FileRoot::ingredient(db)
|
||||
.entries(db.zalsa())
|
||||
.map(|entry| entry.as_struct())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Eq, PartialEq, get_size2::GetSize, serde::Serialize, serde::Deserialize,
|
||||
)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, get_size2::GetSize)]
|
||||
pub enum FileRootKind {
|
||||
/// The root of a project.
|
||||
Project,
|
||||
@@ -58,7 +47,7 @@ pub enum FileRootKind {
|
||||
}
|
||||
|
||||
impl FileRootKind {
|
||||
pub const fn durability(self) -> Durability {
|
||||
const fn durability(self) -> Durability {
|
||||
match self {
|
||||
FileRootKind::Project => Durability::LOW,
|
||||
FileRootKind::LibrarySearchPath => Durability::HIGH,
|
||||
@@ -73,34 +62,34 @@ pub(super) struct FileRoots {
|
||||
}
|
||||
|
||||
impl FileRoots {
|
||||
/// Tries to add a new root for `path`.
|
||||
/// Tries to add a new root for `path` and returns the root.
|
||||
///
|
||||
/// The root isn't added nor is the file root's kind updated if a root for `path` already exists.
|
||||
///
|
||||
/// Returns `Ok(root)` if the `FileRoot` was successfully added, and returns `Err(root)` with
|
||||
/// the previous root if one already existed at that path.
|
||||
pub(super) fn try_add(
|
||||
&mut self,
|
||||
db: &dyn Db,
|
||||
path: SystemPathBuf,
|
||||
create_root: impl FnOnce(SystemPathBuf) -> FileRoot,
|
||||
) -> Result<FileRoot, FileRoot> {
|
||||
kind: FileRootKind,
|
||||
) -> FileRoot {
|
||||
// SAFETY: Guaranteed to succeed because `path` is a UTF-8 that only contains Unicode characters.
|
||||
let normalized_path = path.as_std_path().to_slash().unwrap();
|
||||
|
||||
if let Ok(existing) = self.by_path.at(&normalized_path) {
|
||||
// Only if it is an exact match
|
||||
if existing.value.path(db) == &*path {
|
||||
return Err(*existing.value);
|
||||
return *existing.value;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize the path to use `/` separators and escape the '{' and '}' characters,
|
||||
// which `matchit` uses for routing parameters.
|
||||
// normalize the path to use `/` separators and escape the '{' and '}' characters,
|
||||
// which matchit uses for routing parameters
|
||||
let mut route = normalized_path.replace('{', "{{").replace('}', "}}");
|
||||
|
||||
// Insert a new source root
|
||||
let root = create_root(path);
|
||||
let root = FileRoot::builder(path, kind, FileRevision::now())
|
||||
.durability(Durability::HIGH)
|
||||
.revision_durability(kind.durability())
|
||||
.new(db);
|
||||
|
||||
// Insert a path that matches the root itself
|
||||
self.by_path.insert(route.clone(), root).unwrap();
|
||||
@@ -111,7 +100,7 @@ impl FileRoots {
|
||||
self.by_path.insert(route, root).unwrap();
|
||||
self.roots.push(root);
|
||||
|
||||
Ok(root)
|
||||
root
|
||||
}
|
||||
|
||||
/// Returns the closest root for `path` or `None` if no root contains `path`.
|
||||
|
||||
@@ -11,9 +11,7 @@ use std::fmt::{Display, Formatter};
|
||||
/// * a file stored on the [host system](crate::system::System).
|
||||
/// * a virtual file stored on the [host system](crate::system::System).
|
||||
/// * a vendored file stored in the [vendored file system](crate::vendored::VendoredFileSystem).
|
||||
#[derive(
|
||||
Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize, serde::Serialize, serde::Deserialize,
|
||||
)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||
pub enum FilePath {
|
||||
/// Path to a file on the [host system](crate::system::System).
|
||||
System(SystemPathBuf),
|
||||
|
||||
@@ -148,16 +148,7 @@ impl From<Notebook> for SourceTextKind {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
thiserror::Error,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
get_size2::GetSize,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
)]
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone, get_size2::GetSize)]
|
||||
pub enum SourceTextError {
|
||||
#[error("Failed to read notebook: {0}`")]
|
||||
FailedToReadNotebook(String),
|
||||
|
||||
@@ -66,9 +66,6 @@ pub trait System: Debug + Sync + Send {
|
||||
/// See [dunce::canonicalize] for more information.
|
||||
fn canonicalize_path(&self, path: &SystemPath) -> Result<SystemPathBuf>;
|
||||
|
||||
/// Reads the content of the file at `path` into a bytes buffer.
|
||||
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>>;
|
||||
|
||||
/// Reads the content of the file at `path` into a [`String`].
|
||||
fn read_to_string(&self, path: &SystemPath) -> Result<String>;
|
||||
|
||||
@@ -245,7 +242,7 @@ pub trait WritableSystem: System {
|
||||
fn create_new_file(&self, path: &SystemPath) -> Result<()>;
|
||||
|
||||
/// Writes the given content to the file at the given path.
|
||||
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()>;
|
||||
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()>;
|
||||
|
||||
/// Creates a directory at `path` as well as any intermediate directories.
|
||||
fn create_directory_all(&self, path: &SystemPath) -> Result<()>;
|
||||
@@ -281,7 +278,7 @@ pub trait WritableSystem: System {
|
||||
// ensures that only one thread/process ever attempts to write to it to avoid corrupting
|
||||
// the cache.
|
||||
self.create_new_file(&cache_path)?;
|
||||
self.write_file(&cache_path, contents.as_bytes())?;
|
||||
self.write_file(&cache_path, &contents)?;
|
||||
|
||||
Ok(Some(cache_path))
|
||||
}
|
||||
|
||||
@@ -114,8 +114,8 @@ impl MemoryFileSystem {
|
||||
matches!(by_path.get(&normalized), Some(Entry::Directory(_)))
|
||||
}
|
||||
|
||||
pub fn read_to_end(&self, path: impl AsRef<SystemPath>) -> Result<Vec<u8>> {
|
||||
fn read_to_end(fs: &MemoryFileSystem, path: &SystemPath) -> Result<Vec<u8>> {
|
||||
pub fn read_to_string(&self, path: impl AsRef<SystemPath>) -> Result<String> {
|
||||
fn read_to_string(fs: &MemoryFileSystem, path: &SystemPath) -> Result<String> {
|
||||
let by_path = fs.inner.by_path.read().unwrap();
|
||||
let normalized = fs.normalize_path(path);
|
||||
|
||||
@@ -127,32 +127,19 @@ impl MemoryFileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
read_to_end(self, path.as_ref())
|
||||
}
|
||||
|
||||
pub fn read_to_string(&self, path: impl AsRef<SystemPath>) -> Result<String> {
|
||||
self.read_to_end(path)
|
||||
.and_then(|bytes| String::from_utf8(bytes).map_err(io::Error::other))
|
||||
}
|
||||
|
||||
pub(crate) fn read_virtual_path_to_end(
|
||||
&self,
|
||||
path: impl AsRef<SystemVirtualPath>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let virtual_files = self.inner.virtual_files.read().unwrap();
|
||||
let file = virtual_files
|
||||
.get(&path.as_ref().to_path_buf())
|
||||
.ok_or_else(not_found)?;
|
||||
|
||||
Ok(file.content.clone())
|
||||
read_to_string(self, path.as_ref())
|
||||
}
|
||||
|
||||
pub(crate) fn read_virtual_path_to_string(
|
||||
&self,
|
||||
path: impl AsRef<SystemVirtualPath>,
|
||||
) -> Result<String> {
|
||||
self.read_virtual_path_to_end(path)
|
||||
.and_then(|bytes| String::from_utf8(bytes).map_err(io::Error::other))
|
||||
let virtual_files = self.inner.virtual_files.read().unwrap();
|
||||
let file = virtual_files
|
||||
.get(&path.as_ref().to_path_buf())
|
||||
.ok_or_else(not_found)?;
|
||||
|
||||
Ok(file.content.clone())
|
||||
}
|
||||
|
||||
pub fn exists(&self, path: &SystemPath) -> bool {
|
||||
@@ -174,7 +161,7 @@ impl MemoryFileSystem {
|
||||
match by_path.entry(normalized) {
|
||||
btree_map::Entry::Vacant(entry) => {
|
||||
entry.insert(Entry::File(File {
|
||||
content: Vec::new(),
|
||||
content: String::new(),
|
||||
last_modified: file_time_now(),
|
||||
}));
|
||||
|
||||
@@ -190,17 +177,13 @@ impl MemoryFileSystem {
|
||||
/// Stores a new file in the file system.
|
||||
///
|
||||
/// The operation overrides the content for an existing file with the same normalized `path`.
|
||||
pub fn write_file(
|
||||
&self,
|
||||
path: impl AsRef<SystemPath>,
|
||||
content: impl Into<Vec<u8>>,
|
||||
) -> Result<()> {
|
||||
pub fn write_file(&self, path: impl AsRef<SystemPath>, content: impl ToString) -> Result<()> {
|
||||
let mut by_path = self.inner.by_path.write().unwrap();
|
||||
|
||||
let normalized = self.normalize_path(path.as_ref());
|
||||
|
||||
let file = get_or_create_file(&mut by_path, &normalized)?;
|
||||
file.content = content.into();
|
||||
file.content = content.to_string();
|
||||
file.last_modified = file_time_now();
|
||||
|
||||
Ok(())
|
||||
@@ -231,7 +214,7 @@ impl MemoryFileSystem {
|
||||
pub fn write_file_all(
|
||||
&self,
|
||||
path: impl AsRef<SystemPath>,
|
||||
content: impl Into<Vec<u8>>,
|
||||
content: impl ToString,
|
||||
) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
|
||||
@@ -245,23 +228,19 @@ impl MemoryFileSystem {
|
||||
/// Stores a new virtual file in the file system.
|
||||
///
|
||||
/// The operation overrides the content for an existing virtual file with the same `path`.
|
||||
pub fn write_virtual_file(
|
||||
&self,
|
||||
path: impl AsRef<SystemVirtualPath>,
|
||||
content: impl Into<Vec<u8>>,
|
||||
) {
|
||||
pub fn write_virtual_file(&self, path: impl AsRef<SystemVirtualPath>, content: impl ToString) {
|
||||
let path = path.as_ref();
|
||||
let mut virtual_files = self.inner.virtual_files.write().unwrap();
|
||||
|
||||
match virtual_files.entry(path.to_path_buf()) {
|
||||
std::collections::hash_map::Entry::Vacant(entry) => {
|
||||
entry.insert(File {
|
||||
content: content.into(),
|
||||
content: content.to_string(),
|
||||
last_modified: file_time_now(),
|
||||
});
|
||||
}
|
||||
std::collections::hash_map::Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().content = content.into();
|
||||
entry.get_mut().content = content.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -489,7 +468,7 @@ impl Entry {
|
||||
|
||||
#[derive(Debug)]
|
||||
struct File {
|
||||
content: Vec<u8>,
|
||||
content: String,
|
||||
last_modified: FileTime,
|
||||
}
|
||||
|
||||
@@ -554,7 +533,7 @@ fn get_or_create_file<'a>(
|
||||
|
||||
let entry = paths.entry(normalized.to_path_buf()).or_insert_with(|| {
|
||||
Entry::File(File {
|
||||
content: Vec::new(),
|
||||
content: String::new(),
|
||||
last_modified: file_time_now(),
|
||||
})
|
||||
});
|
||||
|
||||
@@ -93,10 +93,6 @@ impl System for OsSystem {
|
||||
})
|
||||
}
|
||||
|
||||
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>> {
|
||||
std::fs::read(path.as_std_path())
|
||||
}
|
||||
|
||||
fn read_to_string(&self, path: &SystemPath) -> Result<String> {
|
||||
std::fs::read_to_string(path.as_std_path())
|
||||
}
|
||||
@@ -361,7 +357,7 @@ impl WritableSystem for OsSystem {
|
||||
std::fs::File::create_new(path).map(drop)
|
||||
}
|
||||
|
||||
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
|
||||
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
|
||||
std::fs::write(path.as_std_path(), content)
|
||||
}
|
||||
|
||||
|
||||
@@ -762,17 +762,7 @@ impl SystemVirtualPath {
|
||||
}
|
||||
|
||||
/// An owned, virtual path on [`System`](`super::System`) (akin to [`String`]).
|
||||
#[derive(
|
||||
Eq,
|
||||
PartialEq,
|
||||
Clone,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
get_size2::GetSize,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
)]
|
||||
#[derive(Eq, PartialEq, Clone, Hash, PartialOrd, Ord, get_size2::GetSize)]
|
||||
pub struct SystemVirtualPathBuf(String);
|
||||
|
||||
impl SystemVirtualPathBuf {
|
||||
|
||||
@@ -75,10 +75,6 @@ impl System for TestSystem {
|
||||
self.system().canonicalize_path(path)
|
||||
}
|
||||
|
||||
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>> {
|
||||
self.system().read_to_end(path)
|
||||
}
|
||||
|
||||
fn read_to_string(&self, path: &SystemPath) -> Result<String> {
|
||||
self.system().read_to_string(path)
|
||||
}
|
||||
@@ -169,7 +165,7 @@ impl WritableSystem for TestSystem {
|
||||
self.system().create_new_file(path)
|
||||
}
|
||||
|
||||
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
|
||||
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
|
||||
self.system().write_file(path, content)
|
||||
}
|
||||
|
||||
@@ -189,9 +185,7 @@ pub trait DbWithWritableSystem: Db + Sized {
|
||||
/// Writes the content of the given file and notifies the Db about the change.
|
||||
fn write_file(&mut self, path: impl AsRef<SystemPath>, content: impl AsRef<str>) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
let content = content.as_ref();
|
||||
|
||||
match self.writable_system().write_file(path, content.as_bytes()) {
|
||||
match self.writable_system().write_file(path, content.as_ref()) {
|
||||
Ok(()) => {
|
||||
File::sync_path(self, path);
|
||||
Ok(())
|
||||
@@ -204,8 +198,7 @@ pub trait DbWithWritableSystem: Db + Sized {
|
||||
File::sync_path(self, ancestor);
|
||||
}
|
||||
|
||||
self.writable_system()
|
||||
.write_file(path, content.as_bytes())?;
|
||||
self.writable_system().write_file(path, content.as_ref())?;
|
||||
File::sync_path(self, path);
|
||||
|
||||
Ok(())
|
||||
@@ -250,14 +243,8 @@ pub trait DbWithTestSystem: Db + Sized {
|
||||
///
|
||||
/// ## Panics
|
||||
/// If the db isn't using the [`InMemorySystem`].
|
||||
fn write_virtual_file(
|
||||
&mut self,
|
||||
path: impl AsRef<SystemVirtualPath>,
|
||||
content: impl Into<Vec<u8>>,
|
||||
) {
|
||||
fn write_virtual_file(&mut self, path: impl AsRef<SystemVirtualPath>, content: impl ToString) {
|
||||
let path = path.as_ref();
|
||||
let content = content.into();
|
||||
|
||||
self.test_system()
|
||||
.memory_file_system()
|
||||
.write_virtual_file(path, content);
|
||||
@@ -335,10 +322,6 @@ impl System for InMemorySystem {
|
||||
self.memory_fs.canonicalize(path)
|
||||
}
|
||||
|
||||
fn read_to_end(&self, path: &SystemPath) -> Result<Vec<u8>> {
|
||||
self.memory_fs.read_to_end(path)
|
||||
}
|
||||
|
||||
fn read_to_string(&self, path: &SystemPath) -> Result<String> {
|
||||
self.memory_fs.read_to_string(path)
|
||||
}
|
||||
@@ -429,7 +412,7 @@ impl WritableSystem for InMemorySystem {
|
||||
self.memory_fs.create_new_file(path)
|
||||
}
|
||||
|
||||
fn write_file(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
|
||||
fn write_file(&self, path: &SystemPath, content: &str) -> Result<()> {
|
||||
self.memory_fs.write_file(path, content)
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ impl ToOwned for VendoredPath {
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
pub struct VendoredPathBuf(Utf8PathBuf);
|
||||
|
||||
impl get_size2::GetSize for VendoredPathBuf {
|
||||
|
||||
@@ -13,6 +13,7 @@ license = { workspace = true }
|
||||
[dependencies]
|
||||
ty = { workspace = true }
|
||||
ty_project = { workspace = true, features = ["schemars"] }
|
||||
ty_python_semantic = { workspace = true }
|
||||
ty_static = { workspace = true }
|
||||
ruff = { workspace = true }
|
||||
ruff_formatter = { workspace = true }
|
||||
|
||||
@@ -52,7 +52,7 @@ pub(crate) fn main(args: &Args) -> Result<()> {
|
||||
}
|
||||
|
||||
fn generate_markdown() -> String {
|
||||
let registry = &*ty_project::DEFAULT_LINT_REGISTRY;
|
||||
let registry = ty_python_semantic::default_lint_registry();
|
||||
|
||||
let mut output = String::new();
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ task_group()
|
||||
setup()
|
||||
from airflow.decorators import teardown
|
||||
from airflow.io.path import ObjectStoragePath
|
||||
from airflow.io.storage import attach
|
||||
from airflow.io.store import attach
|
||||
from airflow.models import DAG as DAGFromModel
|
||||
from airflow.models import (
|
||||
Connection,
|
||||
@@ -74,3 +74,36 @@ DatasetOrTimeSchedule()
|
||||
|
||||
# airflow.utils.dag_parsing_context
|
||||
get_parsing_context()
|
||||
|
||||
from airflow.decorators.base import (
|
||||
DecoratedMappedOperator,
|
||||
DecoratedOperator,
|
||||
TaskDecorator,
|
||||
get_unique_task_id,
|
||||
task_decorator_factory,
|
||||
)
|
||||
|
||||
# airflow.decorators.base
|
||||
DecoratedMappedOperator()
|
||||
DecoratedOperator()
|
||||
TaskDecorator()
|
||||
get_unique_task_id()
|
||||
task_decorator_factory()
|
||||
|
||||
|
||||
from airflow.models import Param
|
||||
|
||||
# airflow.models
|
||||
Param()
|
||||
|
||||
|
||||
from airflow.sensors.base import (
|
||||
BaseSensorOperator,
|
||||
PokeReturnValue,
|
||||
poke_mode_only,
|
||||
)
|
||||
|
||||
# airflow.sensors.base
|
||||
BaseSensorOperator()
|
||||
PokeReturnValue()
|
||||
poke_mode_only()
|
||||
|
||||
@@ -9,7 +9,6 @@ from airflow.operators.empty import EmptyOperator
|
||||
from airflow.operators.latest_only import LatestOnlyOperator
|
||||
from airflow.operators.trigger_dagrun import TriggerDagRunOperator
|
||||
from airflow.operators.weekday import BranchDayOfWeekOperator
|
||||
from airflow.sensors.date_time import DateTimeSensor
|
||||
|
||||
FSHook()
|
||||
PackageIndexHook()
|
||||
@@ -22,7 +21,6 @@ EmptyOperator()
|
||||
|
||||
LatestOnlyOperator()
|
||||
BranchDayOfWeekOperator()
|
||||
DateTimeSensor()
|
||||
|
||||
from airflow.operators.python import (
|
||||
BranchPythonOperator,
|
||||
@@ -30,16 +28,23 @@ from airflow.operators.python import (
|
||||
PythonVirtualenvOperator,
|
||||
ShortCircuitOperator,
|
||||
)
|
||||
from airflow.sensors.bash import BashSensor
|
||||
from airflow.sensors.date_time import DateTimeSensor
|
||||
|
||||
BranchPythonOperator()
|
||||
PythonOperator()
|
||||
PythonVirtualenvOperator()
|
||||
ShortCircuitOperator()
|
||||
|
||||
BashSensor()
|
||||
DateTimeSensor()
|
||||
from airflow.sensors.date_time import DateTimeSensorAsync
|
||||
from airflow.sensors.external_task import (
|
||||
ExternalTaskMarker,
|
||||
ExternalTaskSensor,
|
||||
)
|
||||
from airflow.sensors.time_sensor import (
|
||||
TimeSensor,
|
||||
TimeSensorAsync,
|
||||
)
|
||||
from airflow.sensors.filesystem import FileSensor
|
||||
from airflow.sensors.python import PythonSensor
|
||||
|
||||
BranchPythonOperator()
|
||||
PythonOperator()
|
||||
@@ -49,6 +54,13 @@ DateTimeSensorAsync()
|
||||
ExternalTaskMarker()
|
||||
ExternalTaskSensor()
|
||||
FileSensor()
|
||||
PythonSensor()
|
||||
|
||||
from airflow.sensors.time_sensor import (
|
||||
TimeSensor,
|
||||
TimeSensorAsync,
|
||||
)
|
||||
|
||||
TimeSensor()
|
||||
TimeSensorAsync()
|
||||
|
||||
|
||||
75
crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC212.py
vendored
Normal file
75
crates/ruff_linter/resources/test/fixtures/flake8_async/ASYNC212.py
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
from typing import Optional
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
def foo():
|
||||
client = httpx.Client()
|
||||
client.close() # Ok
|
||||
client.delete() # Ok
|
||||
client.get() # Ok
|
||||
client.head() # Ok
|
||||
client.options() # Ok
|
||||
client.patch() # Ok
|
||||
client.post() # Ok
|
||||
client.put() # Ok
|
||||
client.request() # Ok
|
||||
client.send() # Ok
|
||||
client.stream() # Ok
|
||||
|
||||
client.anything() # Ok
|
||||
client.build_request() # Ok
|
||||
client.is_closed # Ok
|
||||
|
||||
|
||||
async def foo():
|
||||
client = httpx.Client()
|
||||
client.close() # ASYNC212
|
||||
client.delete() # ASYNC212
|
||||
client.get() # ASYNC212
|
||||
client.head() # ASYNC212
|
||||
client.options() # ASYNC212
|
||||
client.patch() # ASYNC212
|
||||
client.post() # ASYNC212
|
||||
client.put() # ASYNC212
|
||||
client.request() # ASYNC212
|
||||
client.send() # ASYNC212
|
||||
client.stream() # ASYNC212
|
||||
|
||||
client.anything() # Ok
|
||||
client.build_request() # Ok
|
||||
client.is_closed # Ok
|
||||
|
||||
|
||||
async def foo(client: httpx.Client):
|
||||
client.request() # ASYNC212
|
||||
client.anything() # Ok
|
||||
|
||||
|
||||
async def foo(client: httpx.Client | None):
|
||||
client.request() # ASYNC212
|
||||
client.anything() # Ok
|
||||
|
||||
|
||||
async def foo(client: Optional[httpx.Client]):
|
||||
client.request() # ASYNC212
|
||||
client.anything() # Ok
|
||||
|
||||
|
||||
async def foo():
|
||||
client: httpx.Client = ...
|
||||
client.request() # ASYNC212
|
||||
client.anything() # Ok
|
||||
|
||||
|
||||
global_client = httpx.Client()
|
||||
|
||||
|
||||
async def foo():
|
||||
global_client.request() # ASYNC212
|
||||
global_client.anything() # Ok
|
||||
|
||||
|
||||
async def foo():
|
||||
async with httpx.AsyncClient() as client:
|
||||
await client.get() # Ok
|
||||
@@ -17,3 +17,50 @@ info(f"{__name__}")
|
||||
# Don't trigger for t-strings
|
||||
info(t"{name}")
|
||||
info(t"{__name__}")
|
||||
|
||||
count = 5
|
||||
total = 9
|
||||
directory_path = "/home/hamir/ruff/crates/ruff_linter/resources/test/"
|
||||
logging.info(f"{count} out of {total} files in {directory_path} checked")
|
||||
|
||||
|
||||
|
||||
x = 99
|
||||
fmt = "08d"
|
||||
logger.info(f"{x:{'08d'}}")
|
||||
logger.info(f"{x:>10} {x:{fmt}}")
|
||||
|
||||
logging.info(f"")
|
||||
logging.info(f"This message doesn't have any variables.")
|
||||
|
||||
obj = {"key": "value"}
|
||||
logging.info(f"Object: {obj!r}")
|
||||
|
||||
items_count = 3
|
||||
logging.warning(f"Items: {items_count:d}")
|
||||
|
||||
data = {"status": "active"}
|
||||
logging.info(f"Processing {len(data)} items")
|
||||
logging.info(f"Status: {data.get('status', 'unknown').upper()}")
|
||||
|
||||
|
||||
result = 123
|
||||
logging.info(f"Calculated result: {result + 100}")
|
||||
|
||||
temperature = 123
|
||||
logging.info(f"Temperature: {temperature:.1f}°C")
|
||||
|
||||
class FilePath:
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
|
||||
logging.info(f"No changes made to {file_path.name}.")
|
||||
|
||||
user = "tron"
|
||||
balance = 123.45
|
||||
logging.error(f"Error {404}: User {user} has insufficient balance ${balance:.2f}")
|
||||
|
||||
import logging
|
||||
|
||||
x = 1
|
||||
logging.error(f"{x} -> %s", x)
|
||||
|
||||
10
crates/ruff_linter/resources/test/fixtures/flake8_logging_format/G004_arg_order.py
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/flake8_logging_format/G004_arg_order.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
"""Test f-string argument order."""
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
X = 1
|
||||
Y = 2
|
||||
logger.error(f"{X} -> %s", Y)
|
||||
logger.error(f"{Y} -> %s", X)
|
||||
@@ -151,3 +151,39 @@ def f():
|
||||
pass
|
||||
except Exception as _:
|
||||
pass
|
||||
|
||||
|
||||
# OK, `__class__` in this case is not the special `__class__` cell, so we don't
|
||||
# emit a diagnostic. (It has its own special semantics -- see
|
||||
# https://github.com/astral-sh/ruff/pull/20048#discussion_r2298338048 -- but
|
||||
# those aren't relevant here.)
|
||||
class A:
|
||||
__class__ = 1
|
||||
|
||||
|
||||
# The following three cases are flagged because they declare local `__class__`
|
||||
# variables that don't refer to the special `__class__` cell.
|
||||
class A:
|
||||
def set_class(self, cls):
|
||||
__class__ = cls # F841
|
||||
|
||||
|
||||
class A:
|
||||
class B:
|
||||
def set_class(self, cls):
|
||||
__class__ = cls # F841
|
||||
|
||||
|
||||
class A:
|
||||
def foo():
|
||||
class B:
|
||||
print(__class__)
|
||||
def set_class(self, cls):
|
||||
__class__ = cls # F841
|
||||
|
||||
|
||||
# OK, the `__class__` cell is nonlocal and declared as such.
|
||||
class NonlocalDunderClass:
|
||||
def foo():
|
||||
nonlocal __class__
|
||||
__class__ = 1
|
||||
|
||||
@@ -44,3 +44,8 @@ def f():
|
||||
def g():
|
||||
nonlocal x
|
||||
x = 2
|
||||
|
||||
# OK
|
||||
class A:
|
||||
def method(self):
|
||||
nonlocal __class__
|
||||
|
||||
@@ -124,3 +124,19 @@ def fun_with_python_syntax():
|
||||
...
|
||||
|
||||
return Foo
|
||||
|
||||
|
||||
@dataclass
|
||||
class C:
|
||||
def __post_init__(self, x: tuple[int, ...] = (
|
||||
1,
|
||||
2,
|
||||
)) -> None:
|
||||
self.x = x
|
||||
|
||||
|
||||
@dataclass
|
||||
class D:
|
||||
def __post_init__(self, x: int = """
|
||||
""") -> None:
|
||||
self.x = x
|
||||
|
||||
@@ -660,6 +660,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
if checker.is_rule_enabled(Rule::BlockingHttpCallInAsyncFunction) {
|
||||
flake8_async::rules::blocking_http_call(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::BlockingHttpCallHttpxInAsyncFunction) {
|
||||
flake8_async::rules::blocking_http_call_httpx(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::BlockingOpenCallInAsyncFunction) {
|
||||
flake8_async::rules::blocking_open_call(checker, call);
|
||||
}
|
||||
@@ -1046,7 +1049,6 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
Rule::PyPath,
|
||||
Rule::Glob,
|
||||
Rule::OsListdir,
|
||||
Rule::OsSymlink,
|
||||
]) {
|
||||
flake8_use_pathlib::rules::replaceable_by_pathlib(checker, call);
|
||||
}
|
||||
|
||||
@@ -703,7 +703,10 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
match scope.kind {
|
||||
ScopeKind::Class(_) | ScopeKind::Lambda(_) => return false,
|
||||
ScopeKind::Function(ast::StmtFunctionDef { is_async, .. }) => return *is_async,
|
||||
ScopeKind::Generator { .. } | ScopeKind::Module | ScopeKind::Type => {}
|
||||
ScopeKind::Generator { .. }
|
||||
| ScopeKind::Module
|
||||
| ScopeKind::Type
|
||||
| ScopeKind::DunderClassCell => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
@@ -714,7 +717,10 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
match scope.kind {
|
||||
ScopeKind::Class(_) => return false,
|
||||
ScopeKind::Function(_) | ScopeKind::Lambda(_) => return true,
|
||||
ScopeKind::Generator { .. } | ScopeKind::Module | ScopeKind::Type => {}
|
||||
ScopeKind::Generator { .. }
|
||||
| ScopeKind::Module
|
||||
| ScopeKind::Type
|
||||
| ScopeKind::DunderClassCell => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
@@ -725,7 +731,7 @@ impl SemanticSyntaxContext for Checker<'_> {
|
||||
match scope.kind {
|
||||
ScopeKind::Class(_) | ScopeKind::Generator { .. } => return false,
|
||||
ScopeKind::Function(_) | ScopeKind::Lambda(_) => return true,
|
||||
ScopeKind::Module | ScopeKind::Type => {}
|
||||
ScopeKind::Module | ScopeKind::Type | ScopeKind::DunderClassCell => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
@@ -1092,6 +1098,24 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// Here we add the implicit scope surrounding a method which allows code in the
|
||||
// method to access `__class__` at runtime. See the `ScopeKind::DunderClassCell`
|
||||
// docs for more information.
|
||||
let added_dunder_class_scope = if self.semantic.current_scope().kind.is_class() {
|
||||
self.semantic.push_scope(ScopeKind::DunderClassCell);
|
||||
let binding_id = self.semantic.push_binding(
|
||||
TextRange::default(),
|
||||
BindingKind::DunderClassCell,
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
self.semantic
|
||||
.current_scope_mut()
|
||||
.add("__class__", binding_id);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
self.semantic.push_scope(ScopeKind::Type);
|
||||
|
||||
if let Some(type_params) = type_params {
|
||||
@@ -1155,6 +1179,9 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||
self.semantic.pop_scope(); // Function scope
|
||||
self.semantic.pop_definition();
|
||||
self.semantic.pop_scope(); // Type parameter scope
|
||||
if added_dunder_class_scope {
|
||||
self.semantic.pop_scope(); // `__class__` cell closure scope
|
||||
}
|
||||
self.add_binding(
|
||||
name,
|
||||
stmt.identifier(),
|
||||
|
||||
@@ -336,6 +336,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Async, "115") => (RuleGroup::Stable, rules::flake8_async::rules::AsyncZeroSleep),
|
||||
(Flake8Async, "116") => (RuleGroup::Preview, rules::flake8_async::rules::LongSleepNotForever),
|
||||
(Flake8Async, "210") => (RuleGroup::Stable, rules::flake8_async::rules::BlockingHttpCallInAsyncFunction),
|
||||
(Flake8Async, "212") => (RuleGroup::Preview, rules::flake8_async::rules::BlockingHttpCallHttpxInAsyncFunction),
|
||||
(Flake8Async, "220") => (RuleGroup::Stable, rules::flake8_async::rules::CreateSubprocessInAsyncFunction),
|
||||
(Flake8Async, "221") => (RuleGroup::Stable, rules::flake8_async::rules::RunProcessInAsyncFunction),
|
||||
(Flake8Async, "222") => (RuleGroup::Stable, rules::flake8_async::rules::WaitForProcessInAsyncFunction),
|
||||
|
||||
@@ -40,6 +40,11 @@ pub(crate) const fn is_bad_version_info_in_non_stub_enabled(settings: &LinterSet
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
/// <https://github.com/astral-sh/ruff/pull/19303>
|
||||
pub(crate) const fn is_fix_f_string_logging_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/16719
|
||||
pub(crate) const fn is_fix_manual_dict_comprehension_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
|
||||
@@ -354,7 +354,10 @@ impl Renamer {
|
||||
))
|
||||
}
|
||||
// Avoid renaming builtins and other "special" bindings.
|
||||
BindingKind::FutureImport | BindingKind::Builtin | BindingKind::Export(_) => None,
|
||||
BindingKind::FutureImport
|
||||
| BindingKind::Builtin
|
||||
| BindingKind::Export(_)
|
||||
| BindingKind::DunderClassCell => None,
|
||||
// By default, replace the binding's name with the target name.
|
||||
BindingKind::Annotation
|
||||
| BindingKind::Argument
|
||||
|
||||
@@ -215,6 +215,12 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
|
||||
version: "0.0.1",
|
||||
}
|
||||
}
|
||||
["airflow", "sensors", "bash", "BashSensor"] => ProviderReplacement::AutoImport {
|
||||
module: "airflow.providers.standard.sensor.bash",
|
||||
name: "BashSensor",
|
||||
provider: "standard",
|
||||
version: "0.0.1",
|
||||
},
|
||||
[
|
||||
"airflow",
|
||||
"sensors",
|
||||
@@ -243,6 +249,12 @@ fn check_names_moved_to_provider(checker: &Checker, expr: &Expr, ranged: TextRan
|
||||
provider: "standard",
|
||||
version: "0.0.2",
|
||||
},
|
||||
["airflow", "sensors", "python", "PythonSensor"] => ProviderReplacement::AutoImport {
|
||||
module: "airflow.providers.standard.sensors.python",
|
||||
name: "PythonSensor",
|
||||
provider: "standard",
|
||||
version: "0.0.1",
|
||||
},
|
||||
[
|
||||
"airflow",
|
||||
"sensors",
|
||||
|
||||
@@ -227,13 +227,26 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
module: "airflow.sdk",
|
||||
name: (*rest).to_string(),
|
||||
},
|
||||
[
|
||||
"airflow",
|
||||
"decorators",
|
||||
"base",
|
||||
rest @ ("DecoratedMappedOperator"
|
||||
| "DecoratedOperator"
|
||||
| "TaskDecorator"
|
||||
| "get_unique_task_id"
|
||||
| "task_decorator_factory"),
|
||||
] => Replacement::SourceModuleMoved {
|
||||
module: "airflow.sdk.bases.decorator",
|
||||
name: (*rest).to_string(),
|
||||
},
|
||||
|
||||
// airflow.io
|
||||
["airflow", "io", "path", "ObjectStoragePath"] => Replacement::SourceModuleMoved {
|
||||
module: "airflow.sdk",
|
||||
name: "ObjectStoragePath".to_string(),
|
||||
},
|
||||
["airflow", "io", "storage", "attach"] => Replacement::SourceModuleMoved {
|
||||
["airflow", "io", "store", "attach"] => Replacement::SourceModuleMoved {
|
||||
module: "airflow.sdk.io",
|
||||
name: "attach".to_string(),
|
||||
},
|
||||
@@ -245,6 +258,10 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
name: (*rest).to_string(),
|
||||
}
|
||||
}
|
||||
["airflow", "models", "Param"] => Replacement::AutoImport {
|
||||
module: "airflow.sdk.definitions.param",
|
||||
name: "Param",
|
||||
},
|
||||
|
||||
// airflow.models.baseoperator
|
||||
[
|
||||
@@ -260,16 +277,30 @@ fn check_name(checker: &Checker, expr: &Expr, range: TextRange) {
|
||||
module: "airflow.sdk",
|
||||
name: "BaseOperatorLink",
|
||||
},
|
||||
|
||||
// airflow.model..DAG
|
||||
["airflow", "models", .., "DAG"] => Replacement::SourceModuleMoved {
|
||||
module: "airflow.sdk",
|
||||
name: "DAG".to_string(),
|
||||
},
|
||||
|
||||
// airflow.sensors.base
|
||||
[
|
||||
"airflow",
|
||||
"sensors",
|
||||
"base",
|
||||
rest @ ("BaseSensorOperator" | "PokeReturnValue" | "poke_mode_only"),
|
||||
] => Replacement::SourceModuleMoved {
|
||||
module: "airflow.sdk",
|
||||
name: (*rest).to_string(),
|
||||
},
|
||||
|
||||
// airflow.timetables
|
||||
["airflow", "timetables", "datasets", "DatasetOrTimeSchedule"] => Replacement::AutoImport {
|
||||
module: "airflow.timetables.assets",
|
||||
name: "AssetOrTimeSchedule",
|
||||
},
|
||||
|
||||
// airflow.utils
|
||||
[
|
||||
"airflow",
|
||||
|
||||
@@ -312,7 +312,7 @@ help: Use `teardown` from `airflow.sdk` instead.
|
||||
34 34 | setup()
|
||||
35 |-from airflow.decorators import teardown
|
||||
36 35 | from airflow.io.path import ObjectStoragePath
|
||||
37 36 | from airflow.io.storage import attach
|
||||
37 36 | from airflow.io.store import attach
|
||||
38 37 | from airflow.models import DAG as DAGFromModel
|
||||
--------------------------------------------------------------------------------
|
||||
43 42 | from airflow.models.baseoperator import chain, chain_linear, cross_downstream
|
||||
@@ -338,7 +338,7 @@ help: Use `ObjectStoragePath` from `airflow.sdk` instead.
|
||||
34 34 | setup()
|
||||
35 35 | from airflow.decorators import teardown
|
||||
36 |-from airflow.io.path import ObjectStoragePath
|
||||
37 36 | from airflow.io.storage import attach
|
||||
37 36 | from airflow.io.store import attach
|
||||
38 37 | from airflow.models import DAG as DAGFromModel
|
||||
39 38 | from airflow.models import (
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -350,7 +350,7 @@ help: Use `ObjectStoragePath` from `airflow.sdk` instead.
|
||||
47 47 | # airflow.decorators
|
||||
48 48 | teardown()
|
||||
|
||||
AIR311 [*] `airflow.io.storage.attach` is removed in Airflow 3.0; It still works in Airflow 3.0 but is expected to be removed in a future version.
|
||||
AIR311 [*] `airflow.io.store.attach` 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:52:1
|
||||
|
|
||||
50 | # # airflow.io
|
||||
@@ -366,7 +366,7 @@ help: Use `attach` from `airflow.sdk.io` instead.
|
||||
34 34 | setup()
|
||||
35 35 | from airflow.decorators import teardown
|
||||
36 36 | from airflow.io.path import ObjectStoragePath
|
||||
37 |-from airflow.io.storage import attach
|
||||
37 |-from airflow.io.store import attach
|
||||
38 37 | from airflow.models import DAG as DAGFromModel
|
||||
39 38 | from airflow.models import (
|
||||
40 39 | Connection,
|
||||
@@ -391,7 +391,7 @@ AIR311 [*] `airflow.models.Connection` is removed in Airflow 3.0; It still works
|
||||
help: Use `Connection` from `airflow.sdk` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
37 37 | from airflow.io.storage import attach
|
||||
37 37 | from airflow.io.store import attach
|
||||
38 38 | from airflow.models import DAG as DAGFromModel
|
||||
39 39 | from airflow.models import (
|
||||
40 |- Connection,
|
||||
@@ -614,6 +614,8 @@ AIR311 [*] `airflow.utils.dag_parsing_context.get_parsing_context` is removed in
|
||||
75 | # airflow.utils.dag_parsing_context
|
||||
76 | get_parsing_context()
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
77 |
|
||||
78 | from airflow.decorators.base import (
|
||||
|
|
||||
help: Use `get_parsing_context` from `airflow.sdk` instead.
|
||||
|
||||
@@ -626,3 +628,211 @@ help: Use `get_parsing_context` from `airflow.sdk` instead.
|
||||
71 71 |
|
||||
72 72 | # airflow.timetables.datasets
|
||||
73 73 | DatasetOrTimeSchedule()
|
||||
|
||||
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.
|
||||
--> AIR311_names.py:87:1
|
||||
|
|
||||
86 | # airflow.decorators.base
|
||||
87 | DecoratedMappedOperator()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
88 | DecoratedOperator()
|
||||
89 | TaskDecorator()
|
||||
|
|
||||
help: Use `DecoratedMappedOperator` from `airflow.sdk.bases.decorator` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
76 76 | get_parsing_context()
|
||||
77 77 |
|
||||
78 78 | from airflow.decorators.base import (
|
||||
79 |- DecoratedMappedOperator,
|
||||
80 79 | DecoratedOperator,
|
||||
81 80 | TaskDecorator,
|
||||
82 81 | get_unique_task_id,
|
||||
83 82 | task_decorator_factory,
|
||||
84 83 | )
|
||||
84 |+from airflow.sdk.bases.decorator import DecoratedMappedOperator
|
||||
85 85 |
|
||||
86 86 | # airflow.decorators.base
|
||||
87 87 | DecoratedMappedOperator()
|
||||
|
||||
AIR311 [*] `airflow.decorators.base.DecoratedOperator` 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:88:1
|
||||
|
|
||||
86 | # airflow.decorators.base
|
||||
87 | DecoratedMappedOperator()
|
||||
88 | DecoratedOperator()
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
89 | TaskDecorator()
|
||||
90 | get_unique_task_id()
|
||||
|
|
||||
help: Use `DecoratedOperator` from `airflow.sdk.bases.decorator` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
77 77 |
|
||||
78 78 | from airflow.decorators.base import (
|
||||
79 79 | DecoratedMappedOperator,
|
||||
80 |- DecoratedOperator,
|
||||
81 80 | TaskDecorator,
|
||||
82 81 | get_unique_task_id,
|
||||
83 82 | task_decorator_factory,
|
||||
84 83 | )
|
||||
84 |+from airflow.sdk.bases.decorator import DecoratedOperator
|
||||
85 85 |
|
||||
86 86 | # airflow.decorators.base
|
||||
87 87 | DecoratedMappedOperator()
|
||||
|
||||
AIR311 [*] `airflow.decorators.base.TaskDecorator` 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:89:1
|
||||
|
|
||||
87 | DecoratedMappedOperator()
|
||||
88 | DecoratedOperator()
|
||||
89 | TaskDecorator()
|
||||
| ^^^^^^^^^^^^^
|
||||
90 | get_unique_task_id()
|
||||
91 | task_decorator_factory()
|
||||
|
|
||||
help: Use `TaskDecorator` from `airflow.sdk.bases.decorator` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
78 78 | from airflow.decorators.base import (
|
||||
79 79 | DecoratedMappedOperator,
|
||||
80 80 | DecoratedOperator,
|
||||
81 |- TaskDecorator,
|
||||
82 81 | get_unique_task_id,
|
||||
83 82 | task_decorator_factory,
|
||||
84 83 | )
|
||||
84 |+from airflow.sdk.bases.decorator import TaskDecorator
|
||||
85 85 |
|
||||
86 86 | # airflow.decorators.base
|
||||
87 87 | DecoratedMappedOperator()
|
||||
|
||||
AIR311 [*] `airflow.decorators.base.get_unique_task_id` 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:90:1
|
||||
|
|
||||
88 | DecoratedOperator()
|
||||
89 | TaskDecorator()
|
||||
90 | get_unique_task_id()
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
91 | task_decorator_factory()
|
||||
|
|
||||
help: Use `get_unique_task_id` from `airflow.sdk.bases.decorator` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
79 79 | DecoratedMappedOperator,
|
||||
80 80 | DecoratedOperator,
|
||||
81 81 | TaskDecorator,
|
||||
82 |- get_unique_task_id,
|
||||
83 82 | task_decorator_factory,
|
||||
84 83 | )
|
||||
84 |+from airflow.sdk.bases.decorator import get_unique_task_id
|
||||
85 85 |
|
||||
86 86 | # airflow.decorators.base
|
||||
87 87 | DecoratedMappedOperator()
|
||||
|
||||
AIR311 [*] `airflow.decorators.base.task_decorator_factory` 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:91:1
|
||||
|
|
||||
89 | TaskDecorator()
|
||||
90 | get_unique_task_id()
|
||||
91 | task_decorator_factory()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Use `task_decorator_factory` from `airflow.sdk.bases.decorator` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
80 80 | DecoratedOperator,
|
||||
81 81 | TaskDecorator,
|
||||
82 82 | get_unique_task_id,
|
||||
83 |- task_decorator_factory,
|
||||
84 83 | )
|
||||
84 |+from airflow.sdk.bases.decorator import task_decorator_factory
|
||||
85 85 |
|
||||
86 86 | # airflow.decorators.base
|
||||
87 87 | DecoratedMappedOperator()
|
||||
|
||||
AIR311 [*] `airflow.models.Param` 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:97:1
|
||||
|
|
||||
96 | # airflow.models
|
||||
97 | Param()
|
||||
| ^^^^^
|
||||
|
|
||||
help: Use `Param` from `airflow.sdk.definitions.param` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
91 91 | task_decorator_factory()
|
||||
92 92 |
|
||||
93 93 |
|
||||
94 |-from airflow.models import Param
|
||||
94 |+from airflow.sdk.definitions.param import Param
|
||||
95 95 |
|
||||
96 96 | # airflow.models
|
||||
97 97 | Param()
|
||||
|
||||
AIR311 [*] `airflow.sensors.base.BaseSensorOperator` 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:107:1
|
||||
|
|
||||
106 | # airflow.sensors.base
|
||||
107 | BaseSensorOperator()
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
108 | PokeReturnValue()
|
||||
109 | poke_mode_only()
|
||||
|
|
||||
help: Use `BaseSensorOperator` from `airflow.sdk` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
98 98 |
|
||||
99 99 |
|
||||
100 100 | from airflow.sensors.base import (
|
||||
101 |- BaseSensorOperator,
|
||||
102 101 | PokeReturnValue,
|
||||
103 102 | poke_mode_only,
|
||||
104 103 | )
|
||||
104 |+from airflow.sdk import BaseSensorOperator
|
||||
105 105 |
|
||||
106 106 | # airflow.sensors.base
|
||||
107 107 | BaseSensorOperator()
|
||||
|
||||
AIR311 [*] `airflow.sensors.base.PokeReturnValue` 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:108:1
|
||||
|
|
||||
106 | # airflow.sensors.base
|
||||
107 | BaseSensorOperator()
|
||||
108 | PokeReturnValue()
|
||||
| ^^^^^^^^^^^^^^^
|
||||
109 | poke_mode_only()
|
||||
|
|
||||
help: Use `PokeReturnValue` from `airflow.sdk` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
99 99 |
|
||||
100 100 | from airflow.sensors.base import (
|
||||
101 101 | BaseSensorOperator,
|
||||
102 |- PokeReturnValue,
|
||||
103 102 | poke_mode_only,
|
||||
104 103 | )
|
||||
104 |+from airflow.sdk import PokeReturnValue
|
||||
105 105 |
|
||||
106 106 | # airflow.sensors.base
|
||||
107 107 | BaseSensorOperator()
|
||||
|
||||
AIR311 [*] `airflow.sensors.base.poke_mode_only` 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:109:1
|
||||
|
|
||||
107 | BaseSensorOperator()
|
||||
108 | PokeReturnValue()
|
||||
109 | poke_mode_only()
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Use `poke_mode_only` from `airflow.sdk` instead.
|
||||
|
||||
ℹ Unsafe fix
|
||||
100 100 | from airflow.sensors.base import (
|
||||
101 101 | BaseSensorOperator,
|
||||
102 102 | PokeReturnValue,
|
||||
103 |- poke_mode_only,
|
||||
104 103 | )
|
||||
104 |+from airflow.sdk import poke_mode_only
|
||||
105 105 |
|
||||
106 106 | # airflow.sensors.base
|
||||
107 107 | BaseSensorOperator()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,7 @@ mod tests {
|
||||
#[test_case(Rule::AsyncZeroSleep, Path::new("ASYNC115.py"))]
|
||||
#[test_case(Rule::LongSleepNotForever, Path::new("ASYNC116.py"))]
|
||||
#[test_case(Rule::BlockingHttpCallInAsyncFunction, Path::new("ASYNC210.py"))]
|
||||
#[test_case(Rule::BlockingHttpCallHttpxInAsyncFunction, Path::new("ASYNC212.py"))]
|
||||
#[test_case(Rule::CreateSubprocessInAsyncFunction, Path::new("ASYNC22x.py"))]
|
||||
#[test_case(Rule::RunProcessInAsyncFunction, Path::new("ASYNC22x.py"))]
|
||||
#[test_case(Rule::WaitForProcessInAsyncFunction, Path::new("ASYNC22x.py"))]
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
use ruff_python_ast::{self as ast, Expr, ExprCall};
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_semantic::analyze::typing::{TypeChecker, check_type, traverse_union_and_optional};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Violation;
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks that async functions do not use blocking httpx clients.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Blocking an async function via a blocking HTTP call will block the entire
|
||||
/// event loop, preventing it from executing other tasks while waiting for the
|
||||
/// HTTP response, negating the benefits of asynchronous programming.
|
||||
///
|
||||
/// Instead of using the blocking `httpx` client, use the asynchronous client.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import httpx
|
||||
///
|
||||
///
|
||||
/// async def fetch():
|
||||
/// client = httpx.Client()
|
||||
/// response = client.get(...)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import httpx
|
||||
///
|
||||
///
|
||||
/// async def fetch():
|
||||
/// async with httpx.AsyncClient() as client:
|
||||
/// response = await client.get(...)
|
||||
/// ```
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct BlockingHttpCallHttpxInAsyncFunction {
|
||||
name: String,
|
||||
call: String,
|
||||
}
|
||||
|
||||
impl Violation for BlockingHttpCallHttpxInAsyncFunction {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"Blocking httpx method {name}.{call}() in async context, use httpx.AsyncClient",
|
||||
name = self.name,
|
||||
call = self.call,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct HttpxClientChecker;
|
||||
|
||||
impl TypeChecker for HttpxClientChecker {
|
||||
fn match_annotation(
|
||||
annotation: &ruff_python_ast::Expr,
|
||||
semantic: &ruff_python_semantic::SemanticModel,
|
||||
) -> bool {
|
||||
// match base annotation directly
|
||||
if semantic
|
||||
.resolve_qualified_name(annotation)
|
||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["httpx", "Client"]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// otherwise traverse any union or optional annotation
|
||||
let mut found = false;
|
||||
traverse_union_and_optional(
|
||||
&mut |inner_expr, _| {
|
||||
if semantic
|
||||
.resolve_qualified_name(inner_expr)
|
||||
.is_some_and(|qualified_name| {
|
||||
matches!(qualified_name.segments(), ["httpx", "Client"])
|
||||
})
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
},
|
||||
semantic,
|
||||
annotation,
|
||||
);
|
||||
found
|
||||
}
|
||||
|
||||
fn match_initializer(
|
||||
initializer: &ruff_python_ast::Expr,
|
||||
semantic: &ruff_python_semantic::SemanticModel,
|
||||
) -> bool {
|
||||
let Expr::Call(ExprCall { func, .. }) = initializer else {
|
||||
return false;
|
||||
};
|
||||
|
||||
semantic
|
||||
.resolve_qualified_name(func)
|
||||
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["httpx", "Client"]))
|
||||
}
|
||||
}
|
||||
|
||||
/// ASYNC212
|
||||
pub(crate) fn blocking_http_call_httpx(checker: &Checker, call: &ExprCall) {
|
||||
let semantic = checker.semantic();
|
||||
if !semantic.in_async_context() {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(ast::ExprAttribute { value, attr, .. }) = call.func.as_attribute_expr() else {
|
||||
return;
|
||||
};
|
||||
let Some(name) = value.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
let Some(binding) = semantic.only_binding(name).map(|id| semantic.binding(id)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
if check_type::<HttpxClientChecker>(binding, semantic) {
|
||||
if matches!(
|
||||
attr.id.as_str(),
|
||||
"close"
|
||||
| "delete"
|
||||
| "get"
|
||||
| "head"
|
||||
| "options"
|
||||
| "patch"
|
||||
| "post"
|
||||
| "put"
|
||||
| "request"
|
||||
| "send"
|
||||
| "stream"
|
||||
) {
|
||||
checker.report_diagnostic(
|
||||
BlockingHttpCallHttpxInAsyncFunction {
|
||||
name: name.id.to_string(),
|
||||
call: attr.id.to_string(),
|
||||
},
|
||||
call.func.range(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ pub(crate) use async_busy_wait::*;
|
||||
pub(crate) use async_function_with_timeout::*;
|
||||
pub(crate) use async_zero_sleep::*;
|
||||
pub(crate) use blocking_http_call::*;
|
||||
pub(crate) use blocking_http_call_httpx::*;
|
||||
pub(crate) use blocking_open_call::*;
|
||||
pub(crate) use blocking_process_invocation::*;
|
||||
pub(crate) use blocking_sleep::*;
|
||||
@@ -13,6 +14,7 @@ mod async_busy_wait;
|
||||
mod async_function_with_timeout;
|
||||
mod async_zero_sleep;
|
||||
mod blocking_http_call;
|
||||
mod blocking_http_call_httpx;
|
||||
mod blocking_open_call;
|
||||
mod blocking_process_invocation;
|
||||
mod blocking_sleep;
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_async/mod.rs
|
||||
---
|
||||
ASYNC212 Blocking httpx method client.close() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:27:5
|
||||
|
|
||||
25 | async def foo():
|
||||
26 | client = httpx.Client()
|
||||
27 | client.close() # ASYNC212
|
||||
| ^^^^^^^^^^^^
|
||||
28 | client.delete() # ASYNC212
|
||||
29 | client.get() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.delete() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:28:5
|
||||
|
|
||||
26 | client = httpx.Client()
|
||||
27 | client.close() # ASYNC212
|
||||
28 | client.delete() # ASYNC212
|
||||
| ^^^^^^^^^^^^^
|
||||
29 | client.get() # ASYNC212
|
||||
30 | client.head() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.get() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:29:5
|
||||
|
|
||||
27 | client.close() # ASYNC212
|
||||
28 | client.delete() # ASYNC212
|
||||
29 | client.get() # ASYNC212
|
||||
| ^^^^^^^^^^
|
||||
30 | client.head() # ASYNC212
|
||||
31 | client.options() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.head() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:30:5
|
||||
|
|
||||
28 | client.delete() # ASYNC212
|
||||
29 | client.get() # ASYNC212
|
||||
30 | client.head() # ASYNC212
|
||||
| ^^^^^^^^^^^
|
||||
31 | client.options() # ASYNC212
|
||||
32 | client.patch() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.options() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:31:5
|
||||
|
|
||||
29 | client.get() # ASYNC212
|
||||
30 | client.head() # ASYNC212
|
||||
31 | client.options() # ASYNC212
|
||||
| ^^^^^^^^^^^^^^
|
||||
32 | client.patch() # ASYNC212
|
||||
33 | client.post() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.patch() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:32:5
|
||||
|
|
||||
30 | client.head() # ASYNC212
|
||||
31 | client.options() # ASYNC212
|
||||
32 | client.patch() # ASYNC212
|
||||
| ^^^^^^^^^^^^
|
||||
33 | client.post() # ASYNC212
|
||||
34 | client.put() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.post() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:33:5
|
||||
|
|
||||
31 | client.options() # ASYNC212
|
||||
32 | client.patch() # ASYNC212
|
||||
33 | client.post() # ASYNC212
|
||||
| ^^^^^^^^^^^
|
||||
34 | client.put() # ASYNC212
|
||||
35 | client.request() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.put() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:34:5
|
||||
|
|
||||
32 | client.patch() # ASYNC212
|
||||
33 | client.post() # ASYNC212
|
||||
34 | client.put() # ASYNC212
|
||||
| ^^^^^^^^^^
|
||||
35 | client.request() # ASYNC212
|
||||
36 | client.send() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.request() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:35:5
|
||||
|
|
||||
33 | client.post() # ASYNC212
|
||||
34 | client.put() # ASYNC212
|
||||
35 | client.request() # ASYNC212
|
||||
| ^^^^^^^^^^^^^^
|
||||
36 | client.send() # ASYNC212
|
||||
37 | client.stream() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.send() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:36:5
|
||||
|
|
||||
34 | client.put() # ASYNC212
|
||||
35 | client.request() # ASYNC212
|
||||
36 | client.send() # ASYNC212
|
||||
| ^^^^^^^^^^^
|
||||
37 | client.stream() # ASYNC212
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.stream() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:37:5
|
||||
|
|
||||
35 | client.request() # ASYNC212
|
||||
36 | client.send() # ASYNC212
|
||||
37 | client.stream() # ASYNC212
|
||||
| ^^^^^^^^^^^^^
|
||||
38 |
|
||||
39 | client.anything() # Ok
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.request() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:45:5
|
||||
|
|
||||
44 | async def foo(client: httpx.Client):
|
||||
45 | client.request() # ASYNC212
|
||||
| ^^^^^^^^^^^^^^
|
||||
46 | client.anything() # Ok
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.request() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:50:5
|
||||
|
|
||||
49 | async def foo(client: httpx.Client | None):
|
||||
50 | client.request() # ASYNC212
|
||||
| ^^^^^^^^^^^^^^
|
||||
51 | client.anything() # Ok
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.request() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:55:5
|
||||
|
|
||||
54 | async def foo(client: Optional[httpx.Client]):
|
||||
55 | client.request() # ASYNC212
|
||||
| ^^^^^^^^^^^^^^
|
||||
56 | client.anything() # Ok
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method client.request() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:61:5
|
||||
|
|
||||
59 | async def foo():
|
||||
60 | client: httpx.Client = ...
|
||||
61 | client.request() # ASYNC212
|
||||
| ^^^^^^^^^^^^^^
|
||||
62 | client.anything() # Ok
|
||||
|
|
||||
|
||||
ASYNC212 Blocking httpx method global_client.request() in async context, use httpx.AsyncClient
|
||||
--> ASYNC212.py:69:5
|
||||
|
|
||||
68 | async def foo():
|
||||
69 | global_client.request() # ASYNC212
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
70 | global_client.anything() # Ok
|
||||
|
|
||||
@@ -22,6 +22,7 @@ mod tests {
|
||||
#[test_case(Path::new("G002.py"))]
|
||||
#[test_case(Path::new("G003.py"))]
|
||||
#[test_case(Path::new("G004.py"))]
|
||||
#[test_case(Path::new("G004_arg_order.py"))]
|
||||
#[test_case(Path::new("G010.py"))]
|
||||
#[test_case(Path::new("G101_1.py"))]
|
||||
#[test_case(Path::new("G101_2.py"))]
|
||||
@@ -48,4 +49,24 @@ mod tests {
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(Rule::LoggingFString, Path::new("G004.py"))]
|
||||
#[test_case(Rule::LoggingFString, Path::new("G004_arg_order.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_logging_format").join(path).as_path(),
|
||||
&settings::LinterSettings {
|
||||
logger_objects: vec!["logging_setup.logger".to_string()],
|
||||
preview: settings::types::PreviewMode::Enabled,
|
||||
..settings::LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
)?;
|
||||
assert_diagnostics!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Operator};
|
||||
use ruff_python_ast::InterpolatedStringElement;
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Operator, StringFlags};
|
||||
use ruff_python_semantic::analyze::logging;
|
||||
use ruff_python_stdlib::logging::LoggingLevel;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_f_string_logging_enabled;
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::flake8_logging_format::violations::{
|
||||
LoggingExcInfo, LoggingExtraAttrClash, LoggingFString, LoggingPercentFormat,
|
||||
@@ -11,6 +13,87 @@ use crate::rules::flake8_logging_format::violations::{
|
||||
};
|
||||
use crate::{Edit, Fix};
|
||||
|
||||
fn logging_f_string(
|
||||
checker: &Checker,
|
||||
msg: &Expr,
|
||||
f_string: &ast::ExprFString,
|
||||
arguments: &Arguments,
|
||||
msg_pos: usize,
|
||||
) {
|
||||
// Report the diagnostic up-front so we can attach a fix later only when preview is enabled.
|
||||
let mut diagnostic = checker.report_diagnostic(LoggingFString, msg.range());
|
||||
|
||||
// Preview gate for the automatic fix.
|
||||
if !is_fix_f_string_logging_enabled(checker.settings()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are existing positional arguments after the message, bail out.
|
||||
// This could indicate a mistake or complex usage we shouldn't try to fix.
|
||||
if arguments.args.len() > msg_pos + 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut format_string = String::new();
|
||||
let mut args: Vec<&str> = Vec::new();
|
||||
|
||||
// Try to reuse the first part's quote style when building the replacement.
|
||||
// Default to double quotes if we can't determine it.
|
||||
let quote_str = f_string
|
||||
.value
|
||||
.f_strings()
|
||||
.next()
|
||||
.map(|f| f.flags.quote_str())
|
||||
.unwrap_or("\"");
|
||||
|
||||
for f in f_string.value.f_strings() {
|
||||
for element in &f.elements {
|
||||
match element {
|
||||
InterpolatedStringElement::Literal(lit) => {
|
||||
// If the literal text contains a '%' placeholder, bail out: mixing
|
||||
// f-string interpolation with '%' placeholders is ambiguous for our
|
||||
// automatic conversion, so don't offer a fix for this case.
|
||||
if lit.value.as_ref().contains('%') {
|
||||
return;
|
||||
}
|
||||
format_string.push_str(lit.value.as_ref());
|
||||
}
|
||||
InterpolatedStringElement::Interpolation(interpolated) => {
|
||||
if interpolated.format_spec.is_some()
|
||||
|| !matches!(
|
||||
interpolated.conversion,
|
||||
ruff_python_ast::ConversionFlag::None
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
match interpolated.expression.as_ref() {
|
||||
Expr::Name(name) => {
|
||||
format_string.push_str("%s");
|
||||
args.push(name.id.as_str());
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if args.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let replacement = format!(
|
||||
"{q}{format_string}{q}, {args}",
|
||||
q = quote_str,
|
||||
format_string = format_string,
|
||||
args = args.join(", ")
|
||||
);
|
||||
|
||||
let fix = Fix::safe_edit(Edit::range_replacement(replacement, msg.range()));
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
|
||||
/// Returns `true` if the attribute is a reserved attribute on the `logging` module's `LogRecord`
|
||||
/// class.
|
||||
fn is_reserved_attr(attr: &str) -> bool {
|
||||
@@ -42,7 +125,7 @@ fn is_reserved_attr(attr: &str) -> bool {
|
||||
}
|
||||
|
||||
/// Check logging messages for violations.
|
||||
fn check_msg(checker: &Checker, msg: &Expr) {
|
||||
fn check_msg(checker: &Checker, msg: &Expr, arguments: &Arguments, msg_pos: usize) {
|
||||
match msg {
|
||||
// Check for string concatenation and percent format.
|
||||
Expr::BinOp(ast::ExprBinOp { op, .. }) => match op {
|
||||
@@ -55,8 +138,10 @@ fn check_msg(checker: &Checker, msg: &Expr) {
|
||||
_ => {}
|
||||
},
|
||||
// Check for f-strings.
|
||||
Expr::FString(_) => {
|
||||
checker.report_diagnostic_if_enabled(LoggingFString, msg.range());
|
||||
Expr::FString(f_string) => {
|
||||
if checker.is_rule_enabled(Rule::LoggingFString) {
|
||||
logging_f_string(checker, msg, f_string, arguments, msg_pos);
|
||||
}
|
||||
}
|
||||
// Check for .format() calls.
|
||||
Expr::Call(ast::ExprCall { func, .. }) => {
|
||||
@@ -168,7 +253,7 @@ pub(crate) fn logging_call(checker: &Checker, call: &ast::ExprCall) {
|
||||
// G001, G002, G003, G004
|
||||
let msg_pos = usize::from(matches!(logging_call_type, LoggingCallType::LogCall));
|
||||
if let Some(format_arg) = call.arguments.find_argument_value("msg", msg_pos) {
|
||||
check_msg(checker, format_arg);
|
||||
check_msg(checker, format_arg, &call.arguments, msg_pos);
|
||||
}
|
||||
|
||||
// G010
|
||||
|
||||
@@ -9,6 +9,7 @@ G004 Logging statement uses f-string
|
||||
| ^^^^^^^^^^^^^^^
|
||||
5 | logging.log(logging.INFO, f"Hello {name}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:5:27
|
||||
@@ -20,6 +21,7 @@ G004 Logging statement uses f-string
|
||||
6 |
|
||||
7 | _LOGGER = logging.getLogger()
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:8:14
|
||||
@@ -30,6 +32,7 @@ G004 Logging statement uses f-string
|
||||
9 |
|
||||
10 | logging.getLogger().info(f"{name}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:10:26
|
||||
@@ -41,6 +44,7 @@ G004 Logging statement uses f-string
|
||||
11 |
|
||||
12 | from logging import info
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:14:6
|
||||
@@ -51,6 +55,7 @@ G004 Logging statement uses f-string
|
||||
| ^^^^^^^^^
|
||||
15 | info(f"{__name__}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:15:6
|
||||
@@ -61,3 +66,156 @@ G004 Logging statement uses f-string
|
||||
16 |
|
||||
17 | # Don't trigger for t-strings
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:24:14
|
||||
|
|
||||
22 | total = 9
|
||||
23 | directory_path = "/home/hamir/ruff/crates/ruff_linter/resources/test/"
|
||||
24 | logging.info(f"{count} out of {total} files in {directory_path} checked")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:30:13
|
||||
|
|
||||
28 | x = 99
|
||||
29 | fmt = "08d"
|
||||
30 | logger.info(f"{x:{'08d'}}")
|
||||
| ^^^^^^^^^^^^^^
|
||||
31 | logger.info(f"{x:>10} {x:{fmt}}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:31:13
|
||||
|
|
||||
29 | fmt = "08d"
|
||||
30 | logger.info(f"{x:{'08d'}}")
|
||||
31 | logger.info(f"{x:>10} {x:{fmt}}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
32 |
|
||||
33 | logging.info(f"")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:33:14
|
||||
|
|
||||
31 | logger.info(f"{x:>10} {x:{fmt}}")
|
||||
32 |
|
||||
33 | logging.info(f"")
|
||||
| ^^^
|
||||
34 | logging.info(f"This message doesn't have any variables.")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:34:14
|
||||
|
|
||||
33 | logging.info(f"")
|
||||
34 | logging.info(f"This message doesn't have any variables.")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
35 |
|
||||
36 | obj = {"key": "value"}
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:37:14
|
||||
|
|
||||
36 | obj = {"key": "value"}
|
||||
37 | logging.info(f"Object: {obj!r}")
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
38 |
|
||||
39 | items_count = 3
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:40:17
|
||||
|
|
||||
39 | items_count = 3
|
||||
40 | logging.warning(f"Items: {items_count:d}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
41 |
|
||||
42 | data = {"status": "active"}
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:43:14
|
||||
|
|
||||
42 | data = {"status": "active"}
|
||||
43 | logging.info(f"Processing {len(data)} items")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
44 | logging.info(f"Status: {data.get('status', 'unknown').upper()}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:44:14
|
||||
|
|
||||
42 | data = {"status": "active"}
|
||||
43 | logging.info(f"Processing {len(data)} items")
|
||||
44 | logging.info(f"Status: {data.get('status', 'unknown').upper()}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:48:14
|
||||
|
|
||||
47 | result = 123
|
||||
48 | logging.info(f"Calculated result: {result + 100}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
49 |
|
||||
50 | temperature = 123
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:51:14
|
||||
|
|
||||
50 | temperature = 123
|
||||
51 | logging.info(f"Temperature: {temperature:.1f}°C")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
52 |
|
||||
53 | class FilePath:
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:57:14
|
||||
|
|
||||
55 | self.name = name
|
||||
56 |
|
||||
57 | logging.info(f"No changes made to {file_path.name}.")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
58 |
|
||||
59 | user = "tron"
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:61:15
|
||||
|
|
||||
59 | user = "tron"
|
||||
60 | balance = 123.45
|
||||
61 | logging.error(f"Error {404}: User {user} has insufficient balance ${balance:.2f}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
62 |
|
||||
63 | import logging
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:66:15
|
||||
|
|
||||
65 | x = 1
|
||||
66 | logging.error(f"{x} -> %s", x)
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
|
||||
---
|
||||
G004 Logging statement uses f-string
|
||||
--> G004_arg_order.py:9:14
|
||||
|
|
||||
7 | X = 1
|
||||
8 | Y = 2
|
||||
9 | logger.error(f"{X} -> %s", Y)
|
||||
| ^^^^^^^^^^^^
|
||||
10 | logger.error(f"{Y} -> %s", X)
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004_arg_order.py:10:14
|
||||
|
|
||||
8 | Y = 2
|
||||
9 | logger.error(f"{X} -> %s", Y)
|
||||
10 | logger.error(f"{Y} -> %s", X)
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
@@ -0,0 +1,291 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
|
||||
---
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004.py:4:14
|
||||
|
|
||||
3 | name = "world"
|
||||
4 | logging.info(f"Hello {name}")
|
||||
| ^^^^^^^^^^^^^^^
|
||||
5 | logging.log(logging.INFO, f"Hello {name}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | import logging
|
||||
2 2 |
|
||||
3 3 | name = "world"
|
||||
4 |-logging.info(f"Hello {name}")
|
||||
4 |+logging.info("Hello %s", name)
|
||||
5 5 | logging.log(logging.INFO, f"Hello {name}")
|
||||
6 6 |
|
||||
7 7 | _LOGGER = logging.getLogger()
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004.py:5:27
|
||||
|
|
||||
3 | name = "world"
|
||||
4 | logging.info(f"Hello {name}")
|
||||
5 | logging.log(logging.INFO, f"Hello {name}")
|
||||
| ^^^^^^^^^^^^^^^
|
||||
6 |
|
||||
7 | _LOGGER = logging.getLogger()
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 |
|
||||
3 3 | name = "world"
|
||||
4 4 | logging.info(f"Hello {name}")
|
||||
5 |-logging.log(logging.INFO, f"Hello {name}")
|
||||
5 |+logging.log(logging.INFO, "Hello %s", name)
|
||||
6 6 |
|
||||
7 7 | _LOGGER = logging.getLogger()
|
||||
8 8 | _LOGGER.info(f"{__name__}")
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004.py:8:14
|
||||
|
|
||||
7 | _LOGGER = logging.getLogger()
|
||||
8 | _LOGGER.info(f"{__name__}")
|
||||
| ^^^^^^^^^^^^^
|
||||
9 |
|
||||
10 | logging.getLogger().info(f"{name}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
ℹ Safe fix
|
||||
5 5 | logging.log(logging.INFO, f"Hello {name}")
|
||||
6 6 |
|
||||
7 7 | _LOGGER = logging.getLogger()
|
||||
8 |-_LOGGER.info(f"{__name__}")
|
||||
8 |+_LOGGER.info("%s", __name__)
|
||||
9 9 |
|
||||
10 10 | logging.getLogger().info(f"{name}")
|
||||
11 11 |
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004.py:10:26
|
||||
|
|
||||
8 | _LOGGER.info(f"{__name__}")
|
||||
9 |
|
||||
10 | logging.getLogger().info(f"{name}")
|
||||
| ^^^^^^^^^
|
||||
11 |
|
||||
12 | from logging import info
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
ℹ Safe fix
|
||||
7 7 | _LOGGER = logging.getLogger()
|
||||
8 8 | _LOGGER.info(f"{__name__}")
|
||||
9 9 |
|
||||
10 |-logging.getLogger().info(f"{name}")
|
||||
10 |+logging.getLogger().info("%s", name)
|
||||
11 11 |
|
||||
12 12 | from logging import info
|
||||
13 13 |
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004.py:14:6
|
||||
|
|
||||
12 | from logging import info
|
||||
13 |
|
||||
14 | info(f"{name}")
|
||||
| ^^^^^^^^^
|
||||
15 | info(f"{__name__}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
ℹ Safe fix
|
||||
11 11 |
|
||||
12 12 | from logging import info
|
||||
13 13 |
|
||||
14 |-info(f"{name}")
|
||||
14 |+info("%s", name)
|
||||
15 15 | info(f"{__name__}")
|
||||
16 16 |
|
||||
17 17 | # Don't trigger for t-strings
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004.py:15:6
|
||||
|
|
||||
14 | info(f"{name}")
|
||||
15 | info(f"{__name__}")
|
||||
| ^^^^^^^^^^^^^
|
||||
16 |
|
||||
17 | # Don't trigger for t-strings
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
ℹ Safe fix
|
||||
12 12 | from logging import info
|
||||
13 13 |
|
||||
14 14 | info(f"{name}")
|
||||
15 |-info(f"{__name__}")
|
||||
15 |+info("%s", __name__)
|
||||
16 16 |
|
||||
17 17 | # Don't trigger for t-strings
|
||||
18 18 | info(t"{name}")
|
||||
|
||||
G004 [*] Logging statement uses f-string
|
||||
--> G004.py:24:14
|
||||
|
|
||||
22 | total = 9
|
||||
23 | directory_path = "/home/hamir/ruff/crates/ruff_linter/resources/test/"
|
||||
24 | logging.info(f"{count} out of {total} files in {directory_path} checked")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
ℹ Safe fix
|
||||
21 21 | count = 5
|
||||
22 22 | total = 9
|
||||
23 23 | directory_path = "/home/hamir/ruff/crates/ruff_linter/resources/test/"
|
||||
24 |-logging.info(f"{count} out of {total} files in {directory_path} checked")
|
||||
24 |+logging.info("%s out of %s files in %s checked", count, total, directory_path)
|
||||
25 25 |
|
||||
26 26 |
|
||||
27 27 |
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:30:13
|
||||
|
|
||||
28 | x = 99
|
||||
29 | fmt = "08d"
|
||||
30 | logger.info(f"{x:{'08d'}}")
|
||||
| ^^^^^^^^^^^^^^
|
||||
31 | logger.info(f"{x:>10} {x:{fmt}}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:31:13
|
||||
|
|
||||
29 | fmt = "08d"
|
||||
30 | logger.info(f"{x:{'08d'}}")
|
||||
31 | logger.info(f"{x:>10} {x:{fmt}}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
32 |
|
||||
33 | logging.info(f"")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:33:14
|
||||
|
|
||||
31 | logger.info(f"{x:>10} {x:{fmt}}")
|
||||
32 |
|
||||
33 | logging.info(f"")
|
||||
| ^^^
|
||||
34 | logging.info(f"This message doesn't have any variables.")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:34:14
|
||||
|
|
||||
33 | logging.info(f"")
|
||||
34 | logging.info(f"This message doesn't have any variables.")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
35 |
|
||||
36 | obj = {"key": "value"}
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:37:14
|
||||
|
|
||||
36 | obj = {"key": "value"}
|
||||
37 | logging.info(f"Object: {obj!r}")
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
38 |
|
||||
39 | items_count = 3
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:40:17
|
||||
|
|
||||
39 | items_count = 3
|
||||
40 | logging.warning(f"Items: {items_count:d}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
41 |
|
||||
42 | data = {"status": "active"}
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:43:14
|
||||
|
|
||||
42 | data = {"status": "active"}
|
||||
43 | logging.info(f"Processing {len(data)} items")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
44 | logging.info(f"Status: {data.get('status', 'unknown').upper()}")
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:44:14
|
||||
|
|
||||
42 | data = {"status": "active"}
|
||||
43 | logging.info(f"Processing {len(data)} items")
|
||||
44 | logging.info(f"Status: {data.get('status', 'unknown').upper()}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:48:14
|
||||
|
|
||||
47 | result = 123
|
||||
48 | logging.info(f"Calculated result: {result + 100}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
49 |
|
||||
50 | temperature = 123
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:51:14
|
||||
|
|
||||
50 | temperature = 123
|
||||
51 | logging.info(f"Temperature: {temperature:.1f}°C")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
52 |
|
||||
53 | class FilePath:
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:57:14
|
||||
|
|
||||
55 | self.name = name
|
||||
56 |
|
||||
57 | logging.info(f"No changes made to {file_path.name}.")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
58 |
|
||||
59 | user = "tron"
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:61:15
|
||||
|
|
||||
59 | user = "tron"
|
||||
60 | balance = 123.45
|
||||
61 | logging.error(f"Error {404}: User {user} has insufficient balance ${balance:.2f}")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
62 |
|
||||
63 | import logging
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004.py:66:15
|
||||
|
|
||||
65 | x = 1
|
||||
66 | logging.error(f"{x} -> %s", x)
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
|
||||
---
|
||||
G004 Logging statement uses f-string
|
||||
--> G004_arg_order.py:9:14
|
||||
|
|
||||
7 | X = 1
|
||||
8 | Y = 2
|
||||
9 | logger.error(f"{X} -> %s", Y)
|
||||
| ^^^^^^^^^^^^
|
||||
10 | logger.error(f"{Y} -> %s", X)
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
|
||||
G004 Logging statement uses f-string
|
||||
--> G004_arg_order.py:10:14
|
||||
|
|
||||
8 | Y = 2
|
||||
9 | logger.error(f"{X} -> %s", Y)
|
||||
10 | logger.error(f"{Y} -> %s", X)
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to lazy `%` formatting
|
||||
@@ -327,10 +327,16 @@ impl Violation for LoggingStringConcat {
|
||||
pub(crate) struct LoggingFString;
|
||||
|
||||
impl Violation for LoggingFString {
|
||||
const FIX_AVAILABILITY: crate::FixAvailability = crate::FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"Logging statement uses f-string".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Convert to lazy `%` formatting".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.chmod`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.chmod)
|
||||
/// - [Python documentation: `os.chmod`](https://docs.python.org/3/library/os.html#os.chmod)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -43,7 +43,7 @@ use ruff_text_size::Ranged;
|
||||
/// - [Python documentation: `os.getcwd`](https://docs.python.org/3/library/os.html#os.getcwd)
|
||||
/// - [Python documentation: `os.getcwdb`](https://docs.python.org/3/library/os.html#os.getcwdb)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -46,7 +46,7 @@ use crate::{FixAvailability, Violation};
|
||||
/// - [Python documentation: `Path.mkdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir)
|
||||
/// - [Python documentation: `os.makedirs`](https://docs.python.org/3/library/os.html#os.makedirs)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -47,7 +47,7 @@ use crate::{FixAvailability, Violation};
|
||||
/// - [Python documentation: `Path.mkdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir)
|
||||
/// - [Python documentation: `os.mkdir`](https://docs.python.org/3/library/os.html#os.mkdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_fix_os_path_abspath_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::check_os_pathlib_single_arg_calls;
|
||||
use crate::{FixAvailability, Violation};
|
||||
use ruff_diagnostics::{Edit, Fix};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::ExprCall;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
use crate::preview::is_fix_os_path_abspath_enabled;
|
||||
use crate::rules::flake8_use_pathlib::helpers::{
|
||||
has_unknown_keywords_or_starred_expr, is_pathlib_path_call,
|
||||
};
|
||||
use crate::{FixAvailability, Violation};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `os.path.abspath`.
|
||||
@@ -34,13 +40,18 @@ 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 `Path.resolve()` resolves symlinks, while
|
||||
/// `os.path.abspath()` does not. If resolving symlinks is important, you may need to use
|
||||
/// `Path.absolute()`. However, `Path.absolute()` also does not remove any `..` components in a
|
||||
/// path, unlike `os.path.abspath()` and `Path.resolve()`, so if that specific combination of
|
||||
/// behaviors is required, there's no existing `pathlib` alternative. See CPython issue
|
||||
/// [#69200](https://github.com/python/cpython/issues/69200).
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.resolve`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.resolve)
|
||||
/// - [Python documentation: `os.path.abspath`](https://docs.python.org/3/library/os.path.html#os.path.abspath)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
@@ -63,12 +74,44 @@ pub(crate) fn os_path_abspath(checker: &Checker, call: &ExprCall, segments: &[&s
|
||||
if segments != ["os", "path", "abspath"] {
|
||||
return;
|
||||
}
|
||||
check_os_pathlib_single_arg_calls(
|
||||
checker,
|
||||
call,
|
||||
"resolve()",
|
||||
"path",
|
||||
is_fix_os_path_abspath_enabled(checker.settings()),
|
||||
OsPathAbspath,
|
||||
);
|
||||
|
||||
if call.arguments.len() != 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(arg) = call.arguments.find_argument_value("path", 0) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let arg_code = checker.locator().slice(arg.range());
|
||||
let range = call.range();
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(OsPathAbspath, call.func.range());
|
||||
|
||||
if has_unknown_keywords_or_starred_expr(&call.arguments, &["path"]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if !is_fix_os_path_abspath_enabled(checker.settings()) {
|
||||
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}.resolve()")
|
||||
} else {
|
||||
format!("{binding}({arg_code}).resolve()")
|
||||
};
|
||||
|
||||
Ok(Fix::unsafe_edits(
|
||||
Edit::range_replacement(replacement, range),
|
||||
[import_edit],
|
||||
))
|
||||
});
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `PurePath.name`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.name)
|
||||
/// - [Python documentation: `os.path.basename`](https://docs.python.org/3/library/os.path.html#os.path.basename)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `PurePath.parent`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parent)
|
||||
/// - [Python documentation: `os.path.dirname`](https://docs.python.org/3/library/os.path.html#os.path.dirname)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.exists`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.exists)
|
||||
/// - [Python documentation: `os.path.exists`](https://docs.python.org/3/library/os.path.html#os.path.exists)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.expanduser`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.expanduser)
|
||||
/// - [Python documentation: `os.path.expanduser`](https://docs.python.org/3/library/os.path.html#os.path.expanduser)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getatime`](https://docs.python.org/3/library/os.path.html#os.path.getatime)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getctime`](https://docs.python.org/3/library/os.path.html#os.path.getctime)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getmtime`](https://docs.python.org/3/library/os.path.html#os.path.getmtime)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getsize`](https://docs.python.org/3/library/os.path.html#os.path.getsize)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -39,7 +39,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `PurePath.is_absolute`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_absolute)
|
||||
/// - [Python documentation: `os.path.isabs`](https://docs.python.org/3/library/os.path.html#os.path.isabs)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.is_dir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_dir)
|
||||
/// - [Python documentation: `os.path.isdir`](https://docs.python.org/3/library/os.path.html#os.path.isdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.is_file`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_file)
|
||||
/// - [Python documentation: `os.path.isfile`](https://docs.python.org/3/library/os.path.html#os.path.isfile)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.is_symlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_symlink)
|
||||
/// - [Python documentation: `os.path.islink`](https://docs.python.org/3/library/os.path.html#os.path.islink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -40,7 +40,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.samefile`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.samefile)
|
||||
/// - [Python documentation: `os.path.samefile`](https://docs.python.org/3/library/os.path.html#os.path.samefile)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::{ExprCall, PythonVersion};
|
||||
/// - [Python documentation: `Path.readlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.readline)
|
||||
/// - [Python documentation: `os.readlink`](https://docs.python.org/3/library/os.html#os.readlink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.unlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink)
|
||||
/// - [Python documentation: `os.remove`](https://docs.python.org/3/library/os.html#os.remove)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.rename`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.rename)
|
||||
/// - [Python documentation: `os.rename`](https://docs.python.org/3/library/os.html#os.rename)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -45,7 +45,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.replace`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.replace)
|
||||
/// - [Python documentation: `os.replace`](https://docs.python.org/3/library/os.html#os.replace)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.rmdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.rmdir)
|
||||
/// - [Python documentation: `os.rmdir`](https://docs.python.org/3/library/os.html#os.rmdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -44,7 +44,7 @@ use crate::{FixAvailability, Violation};
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.symlink_to`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.symlink_to)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -42,7 +42,7 @@ use ruff_python_ast::ExprCall;
|
||||
/// - [Python documentation: `Path.unlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink)
|
||||
/// - [Python documentation: `os.unlink`](https://docs.python.org/3/library/os.html#os.unlink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -13,7 +13,7 @@ PTH100 [*] `os.path.abspath()` should be replaced by `Path.resolve()`
|
||||
|
|
||||
help: Replace with `Path(...).resolve()`
|
||||
|
||||
ℹ Safe fix
|
||||
ℹ Unsafe fix
|
||||
1 1 | import os
|
||||
2 2 | import os.path
|
||||
3 |+import pathlib
|
||||
|
||||
@@ -13,7 +13,7 @@ PTH100 [*] `os.path.abspath()` should be replaced by `Path.resolve()`
|
||||
|
|
||||
help: Replace with `Path(...).resolve()`
|
||||
|
||||
ℹ Safe fix
|
||||
ℹ Unsafe fix
|
||||
1 1 | import os as foo
|
||||
2 2 | import os.path as foo_p
|
||||
3 |+import pathlib
|
||||
|
||||
@@ -13,7 +13,7 @@ PTH100 [*] `os.path.abspath()` should be replaced by `Path.resolve()`
|
||||
|
|
||||
help: Replace with `Path(...).resolve()`
|
||||
|
||||
ℹ Safe fix
|
||||
ℹ Unsafe fix
|
||||
2 2 | from os import remove, unlink, getcwd, readlink, stat
|
||||
3 3 | from os.path import abspath, exists, expanduser, isdir, isfile, islink
|
||||
4 4 | from os.path import isabs, join, basename, dirname, samefile, splitext
|
||||
|
||||
@@ -13,7 +13,7 @@ PTH100 [*] `os.path.abspath()` should be replaced by `Path.resolve()`
|
||||
|
|
||||
help: Replace with `Path(...).resolve()`
|
||||
|
||||
ℹ Safe fix
|
||||
ℹ Unsafe fix
|
||||
7 7 | from os.path import isfile as xisfile, islink as xislink, isabs as xisabs
|
||||
8 8 | from os.path import join as xjoin, basename as xbasename, dirname as xdirname
|
||||
9 9 | from os.path import samefile as xsamefile, splitext as xsplitext
|
||||
|
||||
@@ -43,7 +43,7 @@ use crate::Violation;
|
||||
/// - [Python documentation: `Path.owner`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.owner)
|
||||
/// - [Python documentation: `os.stat`](https://docs.python.org/3/library/os.html#os.stat)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
@@ -89,7 +89,7 @@ impl Violation for OsStat {
|
||||
/// - [Python documentation: `PurePath.joinpath`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.joinpath)
|
||||
/// - [Python documentation: `os.path.join`](https://docs.python.org/3/library/os.path.html#os.path.join)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
@@ -160,7 +160,7 @@ pub(crate) enum Joiner {
|
||||
/// - [Python documentation: `Path.suffixes`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.suffixes)
|
||||
/// - [Python documentation: `os.path.splitext`](https://docs.python.org/3/library/os.path.html#os.path.splitext)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
@@ -205,7 +205,7 @@ impl Violation for OsPathSplitext {
|
||||
/// - [Python documentation: `Path.open`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.open)
|
||||
/// - [Python documentation: `open`](https://docs.python.org/3/library/functions.html#open)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
@@ -298,7 +298,7 @@ impl Violation for PyPath {
|
||||
/// - [Python documentation: `Path.iterdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.iterdir)
|
||||
/// - [Python documentation: `os.listdir`](https://docs.python.org/3/library/os.html#os.listdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#corresponding-tools)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
||||
@@ -737,6 +737,7 @@ mod tests {
|
||||
|
||||
/// A re-implementation of the Pyflakes test runner.
|
||||
/// Note that all tests marked with `#[ignore]` should be considered TODOs.
|
||||
#[track_caller]
|
||||
fn flakes(contents: &str, expected: &[Rule]) {
|
||||
let contents = dedent(contents);
|
||||
let source_type = PySourceType::default();
|
||||
|
||||
@@ -251,3 +251,63 @@ F841 Local variable `value` is assigned to but never used
|
||||
128 | print(key)
|
||||
|
|
||||
help: Remove assignment to unused variable `value`
|
||||
|
||||
F841 [*] Local variable `__class__` is assigned to but never used
|
||||
--> F841_0.py:168:9
|
||||
|
|
||||
166 | class A:
|
||||
167 | def set_class(self, cls):
|
||||
168 | __class__ = cls # F841
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
help: Remove assignment to unused variable `__class__`
|
||||
|
||||
ℹ Unsafe fix
|
||||
165 165 | # variables that don't refer to the special `__class__` cell.
|
||||
166 166 | class A:
|
||||
167 167 | def set_class(self, cls):
|
||||
168 |- __class__ = cls # F841
|
||||
168 |+ pass # F841
|
||||
169 169 |
|
||||
170 170 |
|
||||
171 171 | class A:
|
||||
|
||||
F841 [*] Local variable `__class__` is assigned to but never used
|
||||
--> F841_0.py:174:13
|
||||
|
|
||||
172 | class B:
|
||||
173 | def set_class(self, cls):
|
||||
174 | __class__ = cls # F841
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
help: Remove assignment to unused variable `__class__`
|
||||
|
||||
ℹ Unsafe fix
|
||||
171 171 | class A:
|
||||
172 172 | class B:
|
||||
173 173 | def set_class(self, cls):
|
||||
174 |- __class__ = cls # F841
|
||||
174 |+ pass # F841
|
||||
175 175 |
|
||||
176 176 |
|
||||
177 177 | class A:
|
||||
|
||||
F841 [*] Local variable `__class__` is assigned to but never used
|
||||
--> F841_0.py:182:17
|
||||
|
|
||||
180 | print(__class__)
|
||||
181 | def set_class(self, cls):
|
||||
182 | __class__ = cls # F841
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
help: Remove assignment to unused variable `__class__`
|
||||
|
||||
ℹ Unsafe fix
|
||||
179 179 | class B:
|
||||
180 180 | print(__class__)
|
||||
181 181 | def set_class(self, cls):
|
||||
182 |- __class__ = cls # F841
|
||||
182 |+ pass # F841
|
||||
183 183 |
|
||||
184 184 |
|
||||
185 185 | # OK, the `__class__` cell is nonlocal and declared as such.
|
||||
|
||||
@@ -289,3 +289,65 @@ help: Remove assignment to unused variable `_`
|
||||
152 |- except Exception as _:
|
||||
152 |+ except Exception:
|
||||
153 153 | pass
|
||||
154 154 |
|
||||
155 155 |
|
||||
|
||||
F841 [*] Local variable `__class__` is assigned to but never used
|
||||
--> F841_0.py:168:9
|
||||
|
|
||||
166 | class A:
|
||||
167 | def set_class(self, cls):
|
||||
168 | __class__ = cls # F841
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
help: Remove assignment to unused variable `__class__`
|
||||
|
||||
ℹ Unsafe fix
|
||||
165 165 | # variables that don't refer to the special `__class__` cell.
|
||||
166 166 | class A:
|
||||
167 167 | def set_class(self, cls):
|
||||
168 |- __class__ = cls # F841
|
||||
168 |+ pass # F841
|
||||
169 169 |
|
||||
170 170 |
|
||||
171 171 | class A:
|
||||
|
||||
F841 [*] Local variable `__class__` is assigned to but never used
|
||||
--> F841_0.py:174:13
|
||||
|
|
||||
172 | class B:
|
||||
173 | def set_class(self, cls):
|
||||
174 | __class__ = cls # F841
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
help: Remove assignment to unused variable `__class__`
|
||||
|
||||
ℹ Unsafe fix
|
||||
171 171 | class A:
|
||||
172 172 | class B:
|
||||
173 173 | def set_class(self, cls):
|
||||
174 |- __class__ = cls # F841
|
||||
174 |+ pass # F841
|
||||
175 175 |
|
||||
176 176 |
|
||||
177 177 | class A:
|
||||
|
||||
F841 [*] Local variable `__class__` is assigned to but never used
|
||||
--> F841_0.py:182:17
|
||||
|
|
||||
180 | print(__class__)
|
||||
181 | def set_class(self, cls):
|
||||
182 | __class__ = cls # F841
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
help: Remove assignment to unused variable `__class__`
|
||||
|
||||
ℹ Unsafe fix
|
||||
179 179 | class B:
|
||||
180 180 | print(__class__)
|
||||
181 181 | def set_class(self, cls):
|
||||
182 |- __class__ = cls # F841
|
||||
182 |+ pass # F841
|
||||
183 183 |
|
||||
184 184 |
|
||||
185 185 | # OK, the `__class__` cell is nonlocal and declared as such.
|
||||
|
||||
@@ -73,7 +73,8 @@ pub(crate) fn non_ascii_name(checker: &Checker, binding: &Binding) {
|
||||
| BindingKind::SubmoduleImport(_)
|
||||
| BindingKind::Deletion
|
||||
| BindingKind::ConditionalDeletion(_)
|
||||
| BindingKind::UnboundException(_) => {
|
||||
| BindingKind::UnboundException(_)
|
||||
| BindingKind::DunderClassCell => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -186,7 +186,7 @@ fn use_initvar(
|
||||
|
||||
let indentation = indentation_at_offset(post_init_def.start(), checker.source())
|
||||
.context("Failed to calculate leading indentation of `__post_init__` method")?;
|
||||
let content = textwrap::indent(&content, indentation);
|
||||
let content = textwrap::indent_first_line(&content, indentation);
|
||||
|
||||
let initvar_edit = Edit::insertion(
|
||||
content.into_owned(),
|
||||
|
||||
@@ -455,3 +455,57 @@ help: Use `dataclasses.InitVar` instead
|
||||
122 123 | ,
|
||||
123 124 | ) -> None:
|
||||
124 125 | ...
|
||||
|
||||
RUF033 [*] `__post_init__` method with argument defaults
|
||||
--> RUF033.py:131:50
|
||||
|
|
||||
129 | @dataclass
|
||||
130 | class C:
|
||||
131 | def __post_init__(self, x: tuple[int, ...] = (
|
||||
| __________________________________________________^
|
||||
132 | | 1,
|
||||
133 | | 2,
|
||||
134 | | )) -> None:
|
||||
| |_____^
|
||||
135 | self.x = x
|
||||
|
|
||||
help: Use `dataclasses.InitVar` instead
|
||||
|
||||
ℹ Unsafe fix
|
||||
128 128 |
|
||||
129 129 | @dataclass
|
||||
130 130 | class C:
|
||||
131 |- def __post_init__(self, x: tuple[int, ...] = (
|
||||
131 |+ x: InitVar[tuple[int, ...]] = (
|
||||
132 132 | 1,
|
||||
133 133 | 2,
|
||||
134 |- )) -> None:
|
||||
134 |+ )
|
||||
135 |+ def __post_init__(self, x: tuple[int, ...]) -> None:
|
||||
135 136 | self.x = x
|
||||
136 137 |
|
||||
137 138 |
|
||||
|
||||
RUF033 [*] `__post_init__` method with argument defaults
|
||||
--> RUF033.py:140:38
|
||||
|
|
||||
138 | @dataclass
|
||||
139 | class D:
|
||||
140 | def __post_init__(self, x: int = """
|
||||
| ______________________________________^
|
||||
141 | | """) -> None:
|
||||
| |_______^
|
||||
142 | self.x = x
|
||||
|
|
||||
help: Use `dataclasses.InitVar` instead
|
||||
|
||||
ℹ Unsafe fix
|
||||
137 137 |
|
||||
138 138 | @dataclass
|
||||
139 139 | class D:
|
||||
140 |- def __post_init__(self, x: int = """
|
||||
141 |- """) -> None:
|
||||
140 |+ x: InitVar[int] = """
|
||||
141 |+ """
|
||||
142 |+ def __post_init__(self, x: int) -> None:
|
||||
142 143 | self.x = x
|
||||
|
||||
@@ -12,14 +12,12 @@ I001 [*] Import block is un-sorted or un-formatted
|
||||
help: Organize imports
|
||||
|
||||
ℹ Safe fix
|
||||
::: cell 1
|
||||
1 |+import math
|
||||
2 |+import random
|
||||
1 3 | from pathlib import Path
|
||||
2 |-import random
|
||||
3 |-import math
|
||||
4 4 | from typing import Any
|
||||
5 5 | import collections
|
||||
6 6 | # Newline should be added here
|
||||
|
||||
I001 [*] Import block is un-sorted or un-formatted
|
||||
--> isort.ipynb:cell 2:1:1
|
||||
@@ -33,17 +31,15 @@ I001 [*] Import block is un-sorted or un-formatted
|
||||
help: Organize imports
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 | from pathlib import Path
|
||||
2 2 | import random
|
||||
3 3 | import math
|
||||
4 |+import collections
|
||||
4 5 | from typing import Any
|
||||
5 |-import collections
|
||||
6 |+
|
||||
7 |+
|
||||
6 8 | # Newline should be added here
|
||||
7 9 | def foo():
|
||||
8 10 | pass
|
||||
::: cell 2
|
||||
1 |+import collections
|
||||
1 2 | from typing import Any
|
||||
2 |-import collections
|
||||
3 |+
|
||||
4 |+
|
||||
3 5 | # Newline should be added here
|
||||
4 6 | def foo():
|
||||
5 7 | pass
|
||||
|
||||
I001 [*] Import block is un-sorted or un-formatted
|
||||
--> isort.ipynb:cell 3:1:1
|
||||
@@ -57,15 +53,13 @@ I001 [*] Import block is un-sorted or un-formatted
|
||||
help: Organize imports
|
||||
|
||||
ℹ Safe fix
|
||||
6 6 | # Newline should be added here
|
||||
7 7 | def foo():
|
||||
8 8 | pass
|
||||
9 |+import sys
|
||||
9 10 | from pathlib import Path
|
||||
10 |-import sys
|
||||
11 11 |
|
||||
12 12 | %matplotlib \
|
||||
13 13 | --inline
|
||||
::: cell 3
|
||||
1 |+import sys
|
||||
1 2 | from pathlib import Path
|
||||
2 |-import sys
|
||||
3 3 |
|
||||
4 4 | %matplotlib \
|
||||
5 5 | --inline
|
||||
|
||||
I001 [*] Import block is un-sorted or un-formatted
|
||||
--> isort.ipynb:cell 3:7:1
|
||||
@@ -79,9 +73,10 @@ I001 [*] Import block is un-sorted or un-formatted
|
||||
help: Organize imports
|
||||
|
||||
ℹ Safe fix
|
||||
12 12 | %matplotlib \
|
||||
13 13 | --inline
|
||||
14 14 |
|
||||
15 |+import abc
|
||||
15 16 | import math
|
||||
16 |-import abc
|
||||
::: cell 3
|
||||
4 4 | %matplotlib \
|
||||
5 5 | --inline
|
||||
6 6 |
|
||||
7 |+import abc
|
||||
7 8 | import math
|
||||
8 |-import abc
|
||||
|
||||
@@ -14,13 +14,13 @@ F401 [*] `os` imported but unused
|
||||
help: Remove unused import: `os`
|
||||
|
||||
ℹ Safe fix
|
||||
::: cell 1
|
||||
2 2 |
|
||||
3 3 | %matplotlib inline
|
||||
4 4 |
|
||||
5 |-import os
|
||||
6 5 |
|
||||
7 6 | _ = math.pi
|
||||
8 7 | %%timeit
|
||||
|
||||
F401 [*] `sys` imported but unused
|
||||
--> ipy_escape_command.ipynb:cell 2:2:8
|
||||
@@ -32,7 +32,6 @@ F401 [*] `sys` imported but unused
|
||||
help: Remove unused import: `sys`
|
||||
|
||||
ℹ Safe fix
|
||||
6 6 |
|
||||
7 7 | _ = math.pi
|
||||
8 8 | %%timeit
|
||||
9 |-import sys
|
||||
::: cell 2
|
||||
1 1 | %%timeit
|
||||
2 |-import sys
|
||||
|
||||
@@ -12,12 +12,11 @@ F841 [*] Local variable `foo1` is assigned to but never used
|
||||
help: Remove assignment to unused variable `foo1`
|
||||
|
||||
ℹ Unsafe fix
|
||||
::: cell 1
|
||||
1 1 | def f():
|
||||
2 |- foo1 = %matplotlib --list
|
||||
2 |+ %matplotlib --list
|
||||
3 3 | foo2: list[str] = %matplotlib --list
|
||||
4 4 | def f():
|
||||
5 5 | bar1 = !pwd
|
||||
|
||||
F841 [*] Local variable `foo2` is assigned to but never used
|
||||
--> unused_variable.ipynb:cell 1:3:5
|
||||
@@ -30,13 +29,11 @@ F841 [*] Local variable `foo2` is assigned to but never used
|
||||
help: Remove assignment to unused variable `foo2`
|
||||
|
||||
ℹ Unsafe fix
|
||||
::: cell 1
|
||||
1 1 | def f():
|
||||
2 2 | foo1 = %matplotlib --list
|
||||
3 |- foo2: list[str] = %matplotlib --list
|
||||
3 |+ %matplotlib --list
|
||||
4 4 | def f():
|
||||
5 5 | bar1 = !pwd
|
||||
6 6 | bar2: str = !pwd
|
||||
|
||||
F841 [*] Local variable `bar1` is assigned to but never used
|
||||
--> unused_variable.ipynb:cell 2:2:5
|
||||
@@ -49,12 +46,11 @@ F841 [*] Local variable `bar1` is assigned to but never used
|
||||
help: Remove assignment to unused variable `bar1`
|
||||
|
||||
ℹ Unsafe fix
|
||||
2 2 | foo1 = %matplotlib --list
|
||||
3 3 | foo2: list[str] = %matplotlib --list
|
||||
4 4 | def f():
|
||||
5 |- bar1 = !pwd
|
||||
5 |+ !pwd
|
||||
6 6 | bar2: str = !pwd
|
||||
::: cell 2
|
||||
1 1 | def f():
|
||||
2 |- bar1 = !pwd
|
||||
2 |+ !pwd
|
||||
3 3 | bar2: str = !pwd
|
||||
|
||||
F841 [*] Local variable `bar2` is assigned to but never used
|
||||
--> unused_variable.ipynb:cell 2:3:5
|
||||
@@ -67,8 +63,8 @@ F841 [*] Local variable `bar2` is assigned to but never used
|
||||
help: Remove assignment to unused variable `bar2`
|
||||
|
||||
ℹ Unsafe fix
|
||||
3 3 | foo2: list[str] = %matplotlib --list
|
||||
4 4 | def f():
|
||||
5 5 | bar1 = !pwd
|
||||
6 |- bar2: str = !pwd
|
||||
6 |+ !pwd
|
||||
::: cell 2
|
||||
1 1 | def f():
|
||||
2 2 | bar1 = !pwd
|
||||
3 |- bar2: str = !pwd
|
||||
3 |+ !pwd
|
||||
|
||||
@@ -12,6 +12,7 @@ F401 [*] `os` imported but unused
|
||||
help: Remove unused import: `os`
|
||||
|
||||
ℹ Safe fix
|
||||
::: cell 3
|
||||
1 |-import os
|
||||
2 1 |
|
||||
3 2 | print("hello world")
|
||||
|
||||
@@ -33,6 +33,14 @@ impl NotebookIndex {
|
||||
self.row_to_row_in_cell.get(row.to_zero_indexed()).copied()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the row:cell-number pairs (both 1-based).
|
||||
pub fn iter(&self) -> impl Iterator<Item = (OneIndexed, OneIndexed)> {
|
||||
self.row_to_cell
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(row, cell)| (OneIndexed::from_zero_indexed(row), *cell))
|
||||
}
|
||||
|
||||
/// Translates the given [`LineColumn`] based on the indexing table.
|
||||
///
|
||||
/// This will translate the row/column in the concatenated source code
|
||||
|
||||
@@ -19,7 +19,6 @@ where
|
||||
/// A unique index for a node within an AST.
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)]
|
||||
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct NodeIndex(NonZeroU32);
|
||||
|
||||
impl NodeIndex {
|
||||
|
||||
@@ -446,7 +446,7 @@ impl Ranged for Binding<'_> {
|
||||
/// ID uniquely identifying a [Binding] in a program.
|
||||
///
|
||||
/// Using a `u32` to identify [Binding]s should be sufficient because Ruff only supports documents with a
|
||||
/// size smaller than or equal to `u32::max`. A document with the size of `u32::max` must have fewer than `u32::max`
|
||||
/// size smaller than or equal to `u32::MAX`. A document with the size of `u32::MAX` must have fewer than `u32::MAX`
|
||||
/// bindings because bindings must be separated by whitespace (and have an assignment).
|
||||
#[newtype_index]
|
||||
pub struct BindingId;
|
||||
@@ -672,6 +672,24 @@ pub enum BindingKind<'a> {
|
||||
/// Stores the ID of the binding that was shadowed in the enclosing
|
||||
/// scope, if any.
|
||||
UnboundException(Option<BindingId>),
|
||||
|
||||
/// A binding to `__class__` in the implicit closure created around every method in a class
|
||||
/// body, if any method refers to either `__class__` or `super`.
|
||||
///
|
||||
/// ```python
|
||||
/// class C:
|
||||
/// __class__ # NameError: name '__class__' is not defined
|
||||
///
|
||||
/// def f():
|
||||
/// print(__class__) # allowed
|
||||
///
|
||||
/// def g():
|
||||
/// nonlocal __class__ # also allowed because the scope is *not* the function scope
|
||||
/// ```
|
||||
///
|
||||
/// See <https://docs.python.org/3/reference/datamodel.html#creating-the-class-object> for more
|
||||
/// details.
|
||||
DunderClassCell,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
|
||||
@@ -404,22 +404,11 @@ impl<'a> SemanticModel<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut seen_function = false;
|
||||
let mut import_starred = false;
|
||||
let mut class_variables_visible = true;
|
||||
for (index, scope_id) in self.scopes.ancestor_ids(self.scope_id).enumerate() {
|
||||
let scope = &self.scopes[scope_id];
|
||||
if scope.kind.is_class() {
|
||||
// Allow usages of `__class__` within methods, e.g.:
|
||||
//
|
||||
// ```python
|
||||
// class Foo:
|
||||
// def __init__(self):
|
||||
// print(__class__)
|
||||
// ```
|
||||
if seen_function && matches!(name.id.as_str(), "__class__") {
|
||||
return ReadResult::ImplicitGlobal;
|
||||
}
|
||||
// Do not allow usages of class symbols unless it is the immediate parent
|
||||
// (excluding type scopes), e.g.:
|
||||
//
|
||||
@@ -442,7 +431,13 @@ impl<'a> SemanticModel<'a> {
|
||||
// Allow class variables to be visible for an additional scope level
|
||||
// when a type scope is seen — this covers the type scope present between
|
||||
// function and class definitions and their parent class scope.
|
||||
class_variables_visible = scope.kind.is_type() && index == 0;
|
||||
//
|
||||
// Also allow an additional level beyond that to cover the implicit
|
||||
// `__class__` closure created around methods and enclosing the type scope.
|
||||
class_variables_visible = matches!(
|
||||
(scope.kind, index),
|
||||
(ScopeKind::Type, 0) | (ScopeKind::DunderClassCell, 1)
|
||||
);
|
||||
|
||||
if let Some(binding_id) = scope.get(name.id.as_str()) {
|
||||
// Mark the binding as used.
|
||||
@@ -614,7 +609,6 @@ impl<'a> SemanticModel<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
seen_function |= scope.kind.is_function();
|
||||
import_starred = import_starred || scope.uses_star_imports();
|
||||
}
|
||||
|
||||
@@ -658,21 +652,19 @@ impl<'a> SemanticModel<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
let mut seen_function = false;
|
||||
let mut class_variables_visible = true;
|
||||
for (index, scope_id) in self.scopes.ancestor_ids(scope_id).enumerate() {
|
||||
let scope = &self.scopes[scope_id];
|
||||
if scope.kind.is_class() {
|
||||
if seen_function && matches!(symbol, "__class__") {
|
||||
return None;
|
||||
}
|
||||
if !class_variables_visible {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
class_variables_visible = scope.kind.is_type() && index == 0;
|
||||
seen_function |= scope.kind.is_function();
|
||||
class_variables_visible = matches!(
|
||||
(scope.kind, index),
|
||||
(ScopeKind::Type, 0) | (ScopeKind::DunderClassCell, 1)
|
||||
);
|
||||
|
||||
if let Some(binding_id) = scope.get(symbol) {
|
||||
match self.bindings[binding_id].kind {
|
||||
@@ -786,15 +778,15 @@ impl<'a> SemanticModel<'a> {
|
||||
}
|
||||
|
||||
if scope.kind.is_class() {
|
||||
if seen_function && matches!(symbol, "__class__") {
|
||||
return None;
|
||||
}
|
||||
if !class_variables_visible {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
class_variables_visible = scope.kind.is_type() && index == 0;
|
||||
class_variables_visible = matches!(
|
||||
(scope.kind, index),
|
||||
(ScopeKind::Type, 0) | (ScopeKind::DunderClassCell, 1)
|
||||
);
|
||||
seen_function |= scope.kind.is_function();
|
||||
|
||||
if let Some(binding_id) = scope.get(symbol) {
|
||||
@@ -1353,11 +1345,12 @@ impl<'a> SemanticModel<'a> {
|
||||
self.scopes[scope_id].parent
|
||||
}
|
||||
|
||||
/// Returns the first parent of the given [`Scope`] that is not of [`ScopeKind::Type`], if any.
|
||||
/// Returns the first parent of the given [`Scope`] that is not of [`ScopeKind::Type`] or
|
||||
/// [`ScopeKind::DunderClassCell`], if any.
|
||||
pub fn first_non_type_parent_scope(&self, scope: &Scope) -> Option<&Scope<'a>> {
|
||||
let mut current_scope = scope;
|
||||
while let Some(parent) = self.parent_scope(current_scope) {
|
||||
if parent.kind.is_type() {
|
||||
if matches!(parent.kind, ScopeKind::Type | ScopeKind::DunderClassCell) {
|
||||
current_scope = parent;
|
||||
} else {
|
||||
return Some(parent);
|
||||
@@ -1366,11 +1359,15 @@ impl<'a> SemanticModel<'a> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns the first parent of the given [`ScopeId`] that is not of [`ScopeKind::Type`], if any.
|
||||
/// Returns the first parent of the given [`ScopeId`] that is not of [`ScopeKind::Type`] or
|
||||
/// [`ScopeKind::DunderClassCell`], if any.
|
||||
pub fn first_non_type_parent_scope_id(&self, scope_id: ScopeId) -> Option<ScopeId> {
|
||||
let mut current_scope_id = scope_id;
|
||||
while let Some(parent_id) = self.parent_scope_id(current_scope_id) {
|
||||
if self.scopes[parent_id].kind.is_type() {
|
||||
if matches!(
|
||||
self.scopes[parent_id].kind,
|
||||
ScopeKind::Type | ScopeKind::DunderClassCell
|
||||
) {
|
||||
current_scope_id = parent_id;
|
||||
} else {
|
||||
return Some(parent_id);
|
||||
@@ -2649,16 +2646,16 @@ pub enum ReadResult {
|
||||
/// The `x` in `print(x)` is resolved to the binding of `x` in `x = 1`.
|
||||
Resolved(BindingId),
|
||||
|
||||
/// The read reference is resolved to a context-specific, implicit global (e.g., `__class__`
|
||||
/// The read reference is resolved to a context-specific, implicit global (e.g., `__qualname__`
|
||||
/// within a class scope).
|
||||
///
|
||||
/// For example, given:
|
||||
/// ```python
|
||||
/// class C:
|
||||
/// print(__class__)
|
||||
/// print(__qualname__)
|
||||
/// ```
|
||||
///
|
||||
/// The `__class__` in `print(__class__)` is resolved to the implicit global `__class__`.
|
||||
/// The `__qualname__` in `print(__qualname__)` is resolved to the implicit global `__qualname__`.
|
||||
ImplicitGlobal,
|
||||
|
||||
/// The read reference is unresolved, but at least one of the containing scopes contains a
|
||||
|
||||
@@ -166,9 +166,49 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, is_macro::Is)]
|
||||
#[derive(Clone, Copy, Debug, is_macro::Is)]
|
||||
pub enum ScopeKind<'a> {
|
||||
Class(&'a ast::StmtClassDef),
|
||||
/// The implicit `__class__` scope surrounding a method which allows code in the
|
||||
/// method to access `__class__` at runtime. The closure sits in between the class
|
||||
/// scope and the function scope.
|
||||
///
|
||||
/// Parameter defaults in methods cannot access `__class__`:
|
||||
///
|
||||
/// ```pycon
|
||||
/// >>> class Bar:
|
||||
/// ... def method(self, x=__class__): ...
|
||||
/// ...
|
||||
/// Traceback (most recent call last):
|
||||
/// File "<python-input-6>", line 1, in <module>
|
||||
/// class Bar:
|
||||
/// def method(self, x=__class__): ...
|
||||
/// File "<python-input-6>", line 2, in Bar
|
||||
/// def method(self, x=__class__): ...
|
||||
/// ^^^^^^^^^
|
||||
/// NameError: name '__class__' is not defined
|
||||
/// ```
|
||||
///
|
||||
/// However, type parameters in methods *can* access `__class__`:
|
||||
///
|
||||
/// ```pycon
|
||||
/// >>> class Foo:
|
||||
/// ... def bar[T: __class__](): ...
|
||||
/// ...
|
||||
/// >>> Foo.bar.__type_params__[0].__bound__
|
||||
/// <class '__main__.Foo'>
|
||||
/// ```
|
||||
///
|
||||
/// Note that this is still not 100% accurate! At runtime, the implicit `__class__`
|
||||
/// closure is only added if the name `super` (has to be a name -- `builtins.super`
|
||||
/// and similar don't count!) or the name `__class__` is used in any method of the
|
||||
/// class. However, accurately emulating that would be both complex and probably
|
||||
/// quite expensive unless we moved to a double-traversal of each scope similar to
|
||||
/// ty. It would also only matter in extreme and unlikely edge cases. So we ignore
|
||||
/// that subtlety for now.
|
||||
///
|
||||
/// See <https://docs.python.org/3/reference/datamodel.html#creating-the-class-object>.
|
||||
DunderClassCell,
|
||||
Function(&'a ast::StmtFunctionDef),
|
||||
Generator {
|
||||
kind: GeneratorKind,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user