Compare commits
46 Commits
zanie/rule
...
zanie/debu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab6f173bc1 | ||
|
|
500d1b854b | ||
|
|
1c0c00d22d | ||
|
|
675cc216cb | ||
|
|
3fcfa50212 | ||
|
|
bf0d645692 | ||
|
|
f392791b69 | ||
|
|
b015a06966 | ||
|
|
8cf0708b96 | ||
|
|
3bc593ad7f | ||
|
|
0c030b5bf3 | ||
|
|
1b082ce67e | ||
|
|
f936d319cc | ||
|
|
85d8b6228f | ||
|
|
7594dadc1d | ||
|
|
de37fbfac9 | ||
|
|
4e2769a16c | ||
|
|
75b5c314e3 | ||
|
|
450fb9b99a | ||
|
|
6163c99551 | ||
|
|
3112202a5b | ||
|
|
64ea00048b | ||
|
|
067a4acd54 | ||
|
|
c88376f468 | ||
|
|
9b7c29853d | ||
|
|
3e21d32b79 | ||
|
|
f9e3ea23ba | ||
|
|
6856d0b44b | ||
|
|
21539f1663 | ||
|
|
b9bb6bf780 | ||
|
|
d39eae2713 | ||
|
|
5d21b9c22e | ||
|
|
ec2f229a45 | ||
|
|
34c1cb7d11 | ||
|
|
2ddea7c657 | ||
|
|
45eabdd2c3 | ||
|
|
675c86c175 | ||
|
|
1f8e2b8f14 | ||
|
|
58b3040342 | ||
|
|
6a9b8aede1 | ||
|
|
f48126ad00 | ||
|
|
11287f944f | ||
|
|
a65efcf459 | ||
|
|
04183b0299 | ||
|
|
36fa1fe359 | ||
|
|
6e625bd93d |
28
.github/workflows/ci.yaml
vendored
28
.github/workflows/ci.yaml
vendored
@@ -366,3 +366,31 @@ jobs:
|
||||
with:
|
||||
run: cargo codspeed run
|
||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
||||
tmp:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main # We checkout the main branch to check for the commit
|
||||
- name: Check main branch
|
||||
run: |
|
||||
# Fetch the main branch since a shallow checkout is used by default
|
||||
git fetch origin main --unshallow
|
||||
if ! git branch --contains 0c030b5bf31e425cb6070db7386243eca6dbd8f1 | grep -E '(^|\s)main$'; then
|
||||
echo "The specified sha is not on the main branch" >&2
|
||||
exit 1
|
||||
fi
|
||||
- name: Check tag consistency
|
||||
run: |
|
||||
# Switch to the commit we want to release
|
||||
git checkout 0c030b5bf31e425cb6070db7386243eca6dbd8f1
|
||||
version=$(grep "version = " pyproject.toml | sed -e 's/version = "\(.*\)"/\1/g')
|
||||
if [ "0.0.290" != "${version}" ]; then
|
||||
echo "The input tag does not match the version from pyproject.toml:" >&2
|
||||
echo "0.0.290" >&2
|
||||
echo "${version}" >&2
|
||||
exit 1
|
||||
else
|
||||
echo "Releasing ${version}"
|
||||
fi
|
||||
|
||||
134
Cargo.lock
generated
134
Cargo.lock
generated
@@ -221,7 +221,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata 0.3.7",
|
||||
"regex-automata 0.3.8",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -272,15 +272,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.28"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95ed24df0632f708f5f6d8082675bef2596f7084dee3dd55f632290bf35bfe0f"
|
||||
checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"time",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
@@ -314,20 +313,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.1"
|
||||
version = "4.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27"
|
||||
checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.1"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d"
|
||||
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -378,14 +376,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.0"
|
||||
version = "4.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a"
|
||||
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -610,7 +608,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -621,7 +619,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -812,7 +810,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.289"
|
||||
version = "0.0.290"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -883,7 +881,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@@ -1122,7 +1120,7 @@ dependencies = [
|
||||
"pmutil 0.6.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1277,9 +1275,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libmimalloc-sys"
|
||||
version = "0.1.34"
|
||||
version = "0.1.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25d058a81af0d1c22d7a1c948576bee6d673f7af3c0f35564abd6c81122f513d"
|
||||
checksum = "3979b5c37ece694f1f5e51e7ecc871fdb0f517ed04ee45f88d15d6d553cb9664"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -1345,9 +1343,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mimalloc"
|
||||
version = "0.1.38"
|
||||
version = "0.1.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "972e5f23f6716f62665760b0f4cbf592576a80c7b879ba9beaafc0e558894127"
|
||||
checksum = "fa01922b5ea280a911e323e4d2fd24b7fe5cc4042e0d2cda3c40775cdc4bdc9c"
|
||||
dependencies = [
|
||||
"libmimalloc-sys",
|
||||
]
|
||||
@@ -1375,7 +1373,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasi",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@@ -1420,20 +1418,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "5.2.0"
|
||||
version = "6.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486"
|
||||
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bitflags 2.4.0",
|
||||
"crossbeam-channel",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio",
|
||||
"walkdir",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1721,7 +1720,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1806,9 +1805,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
version = "1.0.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -1941,13 +1940,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.4"
|
||||
version = "1.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
|
||||
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.3.7",
|
||||
"regex-automata 0.3.8",
|
||||
"regex-syntax 0.7.5",
|
||||
]
|
||||
|
||||
@@ -1962,9 +1961,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
|
||||
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -2022,7 +2021,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.289"
|
||||
version = "0.0.290"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2120,7 +2119,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.289"
|
||||
version = "0.0.290"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2260,7 +2259,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"ruff_python_trivia",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2674,9 +2673,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde-wasm-bindgen"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e"
|
||||
checksum = "30c9933e5689bd420dc6c87b7a1835701810cbc10cd86a26e4da45b73e6b1d78"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
@@ -2691,7 +2690,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2753,7 +2752,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2776,9 +2775,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
|
||||
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
@@ -2848,7 +2847,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2864,9 +2863,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.29"
|
||||
version = "2.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
|
||||
checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2971,22 +2970,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.47"
|
||||
version = "1.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
|
||||
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.47"
|
||||
version = "1.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
|
||||
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3019,17 +3018,6 @@ dependencies = [
|
||||
"tikv-jemalloc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
@@ -3066,9 +3054,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.6"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
|
||||
checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
@@ -3087,9 +3075,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.14"
|
||||
version = "0.19.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
|
||||
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -3119,7 +3107,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3322,7 +3310,7 @@ checksum = "f7e1ba1f333bd65ce3c9f27de592fcbc256dafe3af2717f56d7c87761fbaccf4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3389,12 +3377,6 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
@@ -3422,7 +3404,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -3456,7 +3438,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.29",
|
||||
"syn 2.0.33",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
14
Cargo.toml
14
Cargo.toml
@@ -14,8 +14,8 @@ license = "MIT"
|
||||
[workspace.dependencies]
|
||||
anyhow = { version = "1.0.69" }
|
||||
bitflags = { version = "2.3.1" }
|
||||
chrono = { version = "0.4.23", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.1.8", features = ["derive"] }
|
||||
chrono = { version = "0.4.30", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.4.3", features = ["derive"] }
|
||||
colored = { version = "2.0.0" }
|
||||
filetime = { version = "0.2.20" }
|
||||
glob = { version = "0.3.1" }
|
||||
@@ -30,9 +30,9 @@ num-bigint = { version = "0.4.3" }
|
||||
num-traits = { version = "0.2.15" }
|
||||
once_cell = { version = "1.17.1" }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
proc-macro2 = { version = "1.0.51" }
|
||||
proc-macro2 = { version = "1.0.67" }
|
||||
quote = { version = "1.0.23" }
|
||||
regex = { version = "1.7.1" }
|
||||
regex = { version = "1.9.5" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
schemars = { version = "0.8.12" }
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
@@ -43,10 +43,10 @@ smallvec = { version = "1.10.0" }
|
||||
static_assertions = "1.1.0"
|
||||
strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.25.2" }
|
||||
syn = { version = "2.0.15" }
|
||||
syn = { version = "2.0.33" }
|
||||
test-case = { version = "3.0.0" }
|
||||
thiserror = { version = "1.0.43" }
|
||||
toml = { version = "0.7.2" }
|
||||
thiserror = { version = "1.0.48" }
|
||||
toml = { version = "0.7.8" }
|
||||
tracing = "0.1.37"
|
||||
tracing-indicatif = "0.3.4"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
|
||||
25
LICENSE
25
LICENSE
@@ -1224,6 +1224,31 @@ are:
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- flake8-logging, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Adam Johnson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- Pyright, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
@@ -140,7 +140,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.289
|
||||
rev: v0.0.290
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -274,6 +274,7 @@ quality tools, including:
|
||||
- [flake8-gettext](https://pypi.org/project/flake8-gettext/)
|
||||
- [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/)
|
||||
- [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions)
|
||||
- [flake8-logging](https://pypi.org/project/flake8-logging/)
|
||||
- [flake8-logging-format](https://pypi.org/project/flake8-logging-format/)
|
||||
- [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420)
|
||||
- [flake8-pie](https://pypi.org/project/flake8-pie/)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.289"
|
||||
version = "0.0.290"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.289"
|
||||
version = "0.0.290"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -7,6 +7,8 @@ reversed(sorted(x, reverse=True))
|
||||
reversed(sorted(x, key=lambda e: e, reverse=True))
|
||||
reversed(sorted(x, reverse=True, key=lambda e: e))
|
||||
reversed(sorted(x, reverse=False))
|
||||
reversed(sorted(x, reverse=x))
|
||||
reversed(sorted(x, reverse=not x))
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
||||
reversed(sorted(i for i in range(42)))
|
||||
|
||||
@@ -7,6 +7,8 @@ d = {"a": 1, "b": 2, "c": 3}
|
||||
{i for i in x}
|
||||
{k: v for k, v in y}
|
||||
{k: v for k, v in d.items()}
|
||||
[(k, v) for k, v in d.items()]
|
||||
{k: (a, b) for k, (a, b) in d.items()}
|
||||
|
||||
[i for i, in z]
|
||||
[i for i, j in y]
|
||||
|
||||
5
crates/ruff/resources/test/fixtures/flake8_logging/LOG001.py
vendored
Normal file
5
crates/ruff/resources/test/fixtures/flake8_logging/LOG001.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import logging
|
||||
|
||||
logging.Logger(__name__)
|
||||
logging.Logger()
|
||||
logging.getLogger(__name__)
|
||||
9
crates/ruff/resources/test/fixtures/flake8_logging/LOG009.py
vendored
Normal file
9
crates/ruff/resources/test/fixtures/flake8_logging/LOG009.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import logging
|
||||
|
||||
logging.WARN # LOG009
|
||||
logging.WARNING # OK
|
||||
|
||||
from logging import WARN, WARNING
|
||||
|
||||
WARN # LOG009
|
||||
WARNING # OK
|
||||
83
crates/ruff/resources/test/fixtures/perflint/PERF403.py
vendored
Normal file
83
crates/ruff/resources/test/fixtures/perflint/PERF403.py
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
result[idx] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # Ok (false negative: edge case where `else` is same as `if`)
|
||||
else:
|
||||
result[idx] = name
|
||||
|
||||
|
||||
def foo():
|
||||
result = {}
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = []
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # OK (result is not a dictionary)
|
||||
else:
|
||||
result[idx] = name
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # OK (if/elif/else isn't replaceable)
|
||||
elif idx % 3:
|
||||
result[idx] = name
|
||||
else:
|
||||
result[idx] = name
|
||||
|
||||
|
||||
def foo():
|
||||
result = {1: "banana"}
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx % 2:
|
||||
result[idx] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
if idx in result:
|
||||
result[idx] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for name in fruit:
|
||||
result[name] = name # PERF403
|
||||
|
||||
|
||||
def foo():
|
||||
fruit = ["apple", "pear", "orange"]
|
||||
result = {}
|
||||
for idx, name in enumerate(fruit):
|
||||
result[name] = idx # PERF403
|
||||
@@ -658,3 +658,8 @@ class CommentAfterDocstring:
|
||||
"After this docstring there's a comment." # priorities=1
|
||||
def sort_services(self):
|
||||
pass
|
||||
|
||||
|
||||
def newline_after_closing_quote(self):
|
||||
"We enforce a newline after the closing quote for a multi-line docstring \
|
||||
but continuations shouldn't be considered multi-line"
|
||||
|
||||
60
crates/ruff/resources/test/fixtures/pylint/too_many_public_methods.py
vendored
Normal file
60
crates/ruff/resources/test/fixtures/pylint/too_many_public_methods.py
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
class Everything:
|
||||
foo = 1
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _private(self):
|
||||
pass
|
||||
|
||||
def method1(self):
|
||||
pass
|
||||
|
||||
def method2(self):
|
||||
pass
|
||||
|
||||
def method3(self):
|
||||
pass
|
||||
|
||||
def method4(self):
|
||||
pass
|
||||
|
||||
def method5(self):
|
||||
pass
|
||||
|
||||
def method6(self):
|
||||
pass
|
||||
|
||||
def method7(self):
|
||||
pass
|
||||
|
||||
def method8(self):
|
||||
pass
|
||||
|
||||
def method9(self):
|
||||
pass
|
||||
|
||||
class Small:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _private(self):
|
||||
pass
|
||||
|
||||
def method1(self):
|
||||
pass
|
||||
|
||||
def method2(self):
|
||||
pass
|
||||
|
||||
def method3(self):
|
||||
pass
|
||||
|
||||
def method4(self):
|
||||
pass
|
||||
|
||||
def method5(self):
|
||||
pass
|
||||
|
||||
def method6(self):
|
||||
pass
|
||||
@@ -13,18 +13,19 @@ x: typing.TypeAlias = list[T]
|
||||
T = typing.TypeVar("T")
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 bounded generic (todo)
|
||||
# UP040 bounded generic
|
||||
T = typing.TypeVar("T", bound=int)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 constrained generic
|
||||
T = typing.TypeVar("T", int, str)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 contravariant generic (todo)
|
||||
# UP040 contravariant generic
|
||||
T = typing.TypeVar("T", contravariant=True)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
# UP040 covariant generic (todo)
|
||||
# UP040 covariant generic
|
||||
T = typing.TypeVar("T", covariant=True)
|
||||
x: typing.TypeAlias = list[T]
|
||||
|
||||
|
||||
@@ -13,10 +13,10 @@ use crate::registry::Rule;
|
||||
use crate::rules::{
|
||||
flake8_2020, flake8_async, flake8_bandit, flake8_boolean_trap, flake8_bugbear, flake8_builtins,
|
||||
flake8_comprehensions, flake8_datetimez, flake8_debugger, flake8_django,
|
||||
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging_format,
|
||||
flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self, flake8_simplify,
|
||||
flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet, pep8_naming, pycodestyle,
|
||||
pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
|
||||
flake8_future_annotations, flake8_gettext, flake8_implicit_str_concat, flake8_logging,
|
||||
flake8_logging_format, flake8_pie, flake8_print, flake8_pyi, flake8_pytest_style, flake8_self,
|
||||
flake8_simplify, flake8_tidy_imports, flake8_use_pathlib, flynt, numpy, pandas_vet,
|
||||
pep8_naming, pycodestyle, pyflakes, pygrep_hooks, pylint, pyupgrade, refurb, ruff,
|
||||
};
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
@@ -260,6 +260,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::SixPY3) {
|
||||
flake8_2020::rules::name_or_attribute(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UndocumentedWarn) {
|
||||
flake8_logging::rules::undocumented_warn(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::LoadBeforeGlobalDeclaration) {
|
||||
pylint::rules::load_before_global_declaration(checker, id, expr);
|
||||
}
|
||||
@@ -326,6 +329,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::CollectionsNamedTuple) {
|
||||
flake8_pyi::rules::collections_named_tuple(checker, expr);
|
||||
}
|
||||
if checker.enabled(Rule::UndocumentedWarn) {
|
||||
flake8_logging::rules::undocumented_warn(checker, expr);
|
||||
}
|
||||
pandas_vet::rules::attr(checker, attribute);
|
||||
}
|
||||
Expr::Call(
|
||||
@@ -886,6 +892,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::QuadraticListSummation) {
|
||||
ruff::rules::quadratic_list_summation(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::DirectLoggerInstantiation) {
|
||||
flake8_logging::rules::direct_logger_instantiation(checker, call);
|
||||
}
|
||||
}
|
||||
Expr::Dict(ast::ExprDict {
|
||||
keys,
|
||||
|
||||
@@ -411,6 +411,13 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::EqWithoutHash) {
|
||||
pylint::rules::object_without_hash_method(checker, class_def);
|
||||
}
|
||||
if checker.enabled(Rule::TooManyPublicMethods) {
|
||||
pylint::rules::too_many_public_methods(
|
||||
checker,
|
||||
class_def,
|
||||
checker.settings.pylint.max_public_methods,
|
||||
);
|
||||
}
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
pylint::rules::global_statement(checker, name);
|
||||
}
|
||||
@@ -1177,7 +1184,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
iter,
|
||||
orelse,
|
||||
is_async,
|
||||
..
|
||||
range: _,
|
||||
},
|
||||
) => {
|
||||
if checker.any_enabled(&[
|
||||
@@ -1211,6 +1218,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::ManualListCopy) {
|
||||
perflint::rules::manual_list_copy(checker, target, body);
|
||||
}
|
||||
if checker.enabled(Rule::ManualDictComprehension) {
|
||||
perflint::rules::manual_dict_comprehension(checker, target, body);
|
||||
}
|
||||
if checker.enabled(Rule::UnnecessaryListCast) {
|
||||
perflint::rules::unnecessary_list_cast(checker, iter);
|
||||
}
|
||||
|
||||
@@ -269,6 +269,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "W1510") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessRunWithoutCheck),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash),
|
||||
(Pylint, "R0904") => (RuleGroup::Preview, rules::pylint::rules::TooManyPublicMethods),
|
||||
(Pylint, "W2901") => (RuleGroup::Unspecified, rules::pylint::rules::RedefinedLoopName),
|
||||
#[allow(deprecated)]
|
||||
(Pylint, "W3201") => (RuleGroup::Nursery, rules::pylint::rules::BadDunderMethodName),
|
||||
@@ -897,6 +898,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Perflint, "203") => (RuleGroup::Unspecified, rules::perflint::rules::TryExceptInLoop),
|
||||
(Perflint, "401") => (RuleGroup::Unspecified, rules::perflint::rules::ManualListComprehension),
|
||||
(Perflint, "402") => (RuleGroup::Unspecified, rules::perflint::rules::ManualListCopy),
|
||||
(Perflint, "403") => (RuleGroup::Preview, rules::perflint::rules::ManualDictComprehension),
|
||||
|
||||
// flake8-fixme
|
||||
(Flake8Fixme, "001") => (RuleGroup::Unspecified, rules::flake8_fixme::rules::LineContainsFixme),
|
||||
@@ -918,6 +920,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Refurb, "132") => (RuleGroup::Nursery, rules::refurb::rules::CheckAndRemoveFromSet),
|
||||
(Refurb, "145") => (RuleGroup::Preview, rules::refurb::rules::SliceCopy),
|
||||
|
||||
// flake8-logging
|
||||
(Flake8Logging, "001") => (RuleGroup::Preview, rules::flake8_logging::rules::DirectLoggerInstantiation),
|
||||
(Flake8Logging, "009") => (RuleGroup::Preview, rules::flake8_logging::rules::UndocumentedWarn),
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use libcst_native::{Expression, NameOrAttribute, ParenthesizableWhitespace, SimpleWhitespace};
|
||||
use libcst_native::{
|
||||
Expression, Name, NameOrAttribute, ParenthesizableWhitespace, SimpleWhitespace, UnaryOperation,
|
||||
};
|
||||
|
||||
fn compose_call_path_inner<'a>(expr: &'a Expression, parts: &mut Vec<&'a str>) {
|
||||
match expr {
|
||||
@@ -50,3 +52,41 @@ pub(crate) fn or_space(whitespace: ParenthesizableWhitespace) -> Parenthesizable
|
||||
whitespace
|
||||
}
|
||||
}
|
||||
|
||||
/// Negate a condition, i.e., `a` => `not a` and `not a` => `a`.
|
||||
pub(crate) fn negate<'a>(expression: &Expression<'a>) -> Expression<'a> {
|
||||
if let Expression::UnaryOperation(ref expression) = expression {
|
||||
if matches!(expression.operator, libcst_native::UnaryOp::Not { .. }) {
|
||||
return *expression.expression.clone();
|
||||
}
|
||||
}
|
||||
|
||||
if let Expression::Name(ref expression) = expression {
|
||||
match expression.value {
|
||||
"True" => {
|
||||
return Expression::Name(Box::new(Name {
|
||||
value: "False",
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
}
|
||||
"False" => {
|
||||
return Expression::Name(Box::new(Name {
|
||||
value: "True",
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Expression::UnaryOperation(Box::new(UnaryOperation {
|
||||
operator: libcst_native::UnaryOp::Not {
|
||||
whitespace_after: space(),
|
||||
},
|
||||
expression: Box::new(expression.clone()),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -30,6 +30,11 @@ impl<'a> Docstring<'a> {
|
||||
pub(crate) fn leading_quote(&self) -> &'a str {
|
||||
&self.contents[TextRange::up_to(self.body_range.start())]
|
||||
}
|
||||
|
||||
pub(crate) fn triple_quoted(&self) -> bool {
|
||||
let leading_quote = self.leading_quote();
|
||||
leading_quote.ends_with("\"\"\"") || leading_quote.ends_with("'''")
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for Docstring<'_> {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
//!
|
||||
//! [Ruff]: https://github.com/astral-sh/ruff
|
||||
|
||||
#[cfg(feature = "clap")]
|
||||
pub use rule_selector::clap_completion::RuleSelectorParser;
|
||||
pub use rule_selector::RuleSelector;
|
||||
pub use rules::pycodestyle::rules::{IOError, SyntaxError};
|
||||
|
||||
|
||||
@@ -36,9 +36,6 @@ use crate::settings::{flags, Settings};
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::{directives, fs};
|
||||
|
||||
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
||||
const CARGO_PKG_REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
|
||||
|
||||
/// A [`Result`]-like type that returns both data and an error. Used to return
|
||||
/// diagnostics even in the face of parse errors, since many diagnostics can be
|
||||
/// generated without a full AST.
|
||||
@@ -543,8 +540,9 @@ fn report_failed_to_converge_error(path: &Path, transformed: &str, diagnostics:
|
||||
let codes = collect_rule_codes(diagnostics.iter().map(|diagnostic| diagnostic.kind.rule()));
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!(
|
||||
"{}: Failed to converge after {} iterations in `{}` with rule codes {}:---\n{}\n---",
|
||||
"{}{} Failed to converge after {} iterations in `{}` with rule codes {}:---\n{}\n---",
|
||||
"debug error".red().bold(),
|
||||
":".bold(),
|
||||
MAX_ITERATIONS,
|
||||
fs::relativize_path(path),
|
||||
codes,
|
||||
@@ -553,18 +551,17 @@ fn report_failed_to_converge_error(path: &Path, transformed: &str, diagnostics:
|
||||
} else {
|
||||
eprintln!(
|
||||
r#"
|
||||
{}: Failed to converge after {} iterations.
|
||||
{}{} Failed to converge after {} iterations.
|
||||
|
||||
This indicates a bug in `{}`. If you could open an issue at:
|
||||
This indicates a bug in Ruff. If you could open an issue at:
|
||||
|
||||
{}/issues/new?title=%5BInfinite%20loop%5D
|
||||
https://github.com/astral-sh/ruff/issues/new?title=%5BInfinite%20loop%5D
|
||||
|
||||
...quoting the contents of `{}`, the rule codes {}, along with the `pyproject.toml` settings and executed command, we'd be very appreciative!
|
||||
"#,
|
||||
"error".red().bold(),
|
||||
":".bold(),
|
||||
MAX_ITERATIONS,
|
||||
CARGO_PKG_NAME,
|
||||
CARGO_PKG_REPOSITORY,
|
||||
fs::relativize_path(path),
|
||||
codes
|
||||
);
|
||||
@@ -581,8 +578,9 @@ fn report_autofix_syntax_error(
|
||||
let codes = collect_rule_codes(rules);
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!(
|
||||
"{}: Autofix introduced a syntax error in `{}` with rule codes {}: {}\n---\n{}\n---",
|
||||
"{}{} Autofix introduced a syntax error in `{}` with rule codes {}: {}\n---\n{}\n---",
|
||||
"error".red().bold(),
|
||||
":".bold(),
|
||||
fs::relativize_path(path),
|
||||
codes,
|
||||
error,
|
||||
@@ -591,17 +589,16 @@ fn report_autofix_syntax_error(
|
||||
} else {
|
||||
eprintln!(
|
||||
r#"
|
||||
{}: Autofix introduced a syntax error. Reverting all changes.
|
||||
{}{} Autofix introduced a syntax error. Reverting all changes.
|
||||
|
||||
This indicates a bug in `{}`. If you could open an issue at:
|
||||
This indicates a bug in Ruff. If you could open an issue at:
|
||||
|
||||
{}/issues/new?title=%5BAutofix%20error%5D
|
||||
https://github.com/astral-sh/ruff/issues/new?title=%5BAutofix%20error%5D
|
||||
|
||||
...quoting the contents of `{}`, the rule codes {}, along with the `pyproject.toml` settings and executed command, we'd be very appreciative!
|
||||
"#,
|
||||
"error".red().bold(),
|
||||
CARGO_PKG_NAME,
|
||||
CARGO_PKG_REPOSITORY,
|
||||
":".bold(),
|
||||
fs::relativize_path(path),
|
||||
codes,
|
||||
);
|
||||
|
||||
@@ -199,6 +199,9 @@ pub enum Linter {
|
||||
/// [refurb](https://pypi.org/project/refurb/)
|
||||
#[prefix = "FURB"]
|
||||
Refurb,
|
||||
/// [flake8-logging](https://pypi.org/project/flake8-logging/)
|
||||
#[prefix = "LOG"]
|
||||
Flake8Logging,
|
||||
/// Ruff-specific rules
|
||||
#[prefix = "RUF"]
|
||||
Ruff,
|
||||
|
||||
@@ -15,10 +15,8 @@ use crate::settings::types::PreviewMode;
|
||||
pub enum RuleSelector {
|
||||
/// Select all rules (includes rules in preview if enabled)
|
||||
All,
|
||||
/// Category to select all rules in preview (includes legacy nursery rules)
|
||||
Preview,
|
||||
/// Legacy category to select all rules in the "nursery" which predated preview mode
|
||||
#[deprecated(note = "Use `RuleSelector::Preview` for new rules instead")]
|
||||
#[deprecated(note = "The nursery was replaced with 'preview mode' which has no selector")]
|
||||
Nursery,
|
||||
/// Legacy category to select both the `mccabe` and `flake8-comprehensions` linters
|
||||
/// via a single selector.
|
||||
@@ -54,7 +52,6 @@ impl FromStr for RuleSelector {
|
||||
"ALL" => Ok(Self::All),
|
||||
#[allow(deprecated)]
|
||||
"NURSERY" => Ok(Self::Nursery),
|
||||
"PREVIEW" => Ok(Self::Preview),
|
||||
"C" => Ok(Self::C),
|
||||
"T" => Ok(Self::T),
|
||||
_ => {
|
||||
@@ -121,7 +118,6 @@ impl RuleSelector {
|
||||
RuleSelector::All => ("", "ALL"),
|
||||
#[allow(deprecated)]
|
||||
RuleSelector::Nursery => ("", "NURSERY"),
|
||||
RuleSelector::Preview => ("", "PREVIEW"),
|
||||
RuleSelector::C => ("", "C"),
|
||||
RuleSelector::T => ("", "T"),
|
||||
RuleSelector::Prefix { prefix, .. } | RuleSelector::Rule { prefix, .. } => {
|
||||
@@ -185,9 +181,6 @@ impl RuleSelector {
|
||||
RuleSelector::Nursery => {
|
||||
RuleSelectorIter::Nursery(Rule::iter().filter(Rule::is_nursery))
|
||||
}
|
||||
RuleSelector::Preview => RuleSelectorIter::Nursery(
|
||||
Rule::iter().filter(|rule| rule.is_preview() || rule.is_nursery()),
|
||||
),
|
||||
RuleSelector::C => RuleSelectorIter::Chain(
|
||||
Linter::Flake8Comprehensions
|
||||
.rules()
|
||||
@@ -261,8 +254,9 @@ mod schema {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
enum_values: Some(
|
||||
[
|
||||
// Include the non-standard "ALL" selector.
|
||||
// Include the non-standard "ALL" and "NURSERY" selectors.
|
||||
"ALL".to_string(),
|
||||
"NURSERY".to_string(),
|
||||
// Include the legacy "C" and "T" selectors.
|
||||
"C".to_string(),
|
||||
"T".to_string(),
|
||||
@@ -301,7 +295,6 @@ impl RuleSelector {
|
||||
pub fn specificity(&self) -> Specificity {
|
||||
match self {
|
||||
RuleSelector::All => Specificity::All,
|
||||
RuleSelector::Preview => Specificity::All,
|
||||
#[allow(deprecated)]
|
||||
RuleSelector::Nursery => Specificity::All,
|
||||
RuleSelector::T => Specificity::LinterGroup,
|
||||
@@ -343,13 +336,14 @@ pub enum Specificity {
|
||||
}
|
||||
|
||||
#[cfg(feature = "clap")]
|
||||
mod clap_completion {
|
||||
pub mod clap_completion {
|
||||
use clap::builder::{PossibleValue, TypedValueParser, ValueParserFactory};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::{
|
||||
codes::RuleCodePrefix,
|
||||
registry::{Linter, RuleNamespace},
|
||||
rule_selector::is_single_rule_selector,
|
||||
RuleSelector,
|
||||
};
|
||||
|
||||
@@ -369,17 +363,29 @@ mod clap_completion {
|
||||
|
||||
fn parse_ref(
|
||||
&self,
|
||||
_cmd: &clap::Command,
|
||||
_arg: Option<&clap::Arg>,
|
||||
cmd: &clap::Command,
|
||||
arg: Option<&clap::Arg>,
|
||||
value: &std::ffi::OsStr,
|
||||
) -> Result<Self::Value, clap::Error> {
|
||||
let value = value
|
||||
.to_str()
|
||||
.ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?;
|
||||
|
||||
value
|
||||
.parse()
|
||||
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::InvalidValue, e))
|
||||
value.parse().map_err(|_| {
|
||||
let mut error =
|
||||
clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd);
|
||||
if let Some(arg) = arg {
|
||||
error.insert(
|
||||
clap::error::ContextKind::InvalidArg,
|
||||
clap::error::ContextValue::String(arg.to_string()),
|
||||
);
|
||||
}
|
||||
error.insert(
|
||||
clap::error::ContextKind::InvalidValue,
|
||||
clap::error::ContextValue::String(value.to_string()),
|
||||
);
|
||||
error
|
||||
})
|
||||
}
|
||||
|
||||
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
|
||||
@@ -394,27 +400,34 @@ mod clap_completion {
|
||||
RuleCodePrefix::iter()
|
||||
// Filter out rule gated behind `#[cfg(feature = "unreachable-code")]`, which is
|
||||
// off-by-default
|
||||
.filter(|p| {
|
||||
format!("{}{}", p.linter().common_prefix(), p.short_code())
|
||||
!= "RUF014"
|
||||
.filter(|prefix| {
|
||||
format!(
|
||||
"{}{}",
|
||||
prefix.linter().common_prefix(),
|
||||
prefix.short_code()
|
||||
) != "RUF014"
|
||||
})
|
||||
.map(|p| {
|
||||
let prefix = p.linter().common_prefix();
|
||||
let code = p.short_code();
|
||||
|
||||
let mut rules_iter = p.rules();
|
||||
let rule1 = rules_iter.next();
|
||||
let rule2 = rules_iter.next();
|
||||
|
||||
let value = PossibleValue::new(format!("{prefix}{code}"));
|
||||
|
||||
if rule2.is_none() {
|
||||
let rule1 = rule1.unwrap();
|
||||
let name: &'static str = rule1.into();
|
||||
value.help(name)
|
||||
} else {
|
||||
value
|
||||
.filter_map(|prefix| {
|
||||
// Ex) `UP`
|
||||
if prefix.short_code().is_empty() {
|
||||
let code = prefix.linter().common_prefix();
|
||||
let name = prefix.linter().name();
|
||||
return Some(PossibleValue::new(code).help(name));
|
||||
}
|
||||
|
||||
// Ex) `UP004`
|
||||
if is_single_rule_selector(&prefix) {
|
||||
let rule = prefix.rules().next()?;
|
||||
let code = format!(
|
||||
"{}{}",
|
||||
prefix.linter().common_prefix(),
|
||||
prefix.short_code()
|
||||
);
|
||||
let name: &'static str = rule.into();
|
||||
return Some(PossibleValue::new(code).help(name));
|
||||
}
|
||||
|
||||
None
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -148,7 +148,7 @@ impl Violation for StartProcessWithNoShell {
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `subprocess.Popen()`](https://docs.python.org/3/library/subprocess.html#subprocess.Popen)
|
||||
/// - [Common Weakness Enumeration: CWE-78](https://cwe.mitre.org/data/definitions/78.html)
|
||||
/// - [Common Weakness Enumeration: CWE-426](https://cwe.mitre.org/data/definitions/426.html)
|
||||
#[violation]
|
||||
pub struct StartProcessWithPartialPath;
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ impl Violation for MutableArgumentDefault {
|
||||
fn message(&self) -> String {
|
||||
format!("Do not use mutable data structures for argument defaults")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> Option<String> {
|
||||
Some(format!("Replace with `None`; initialize within function"))
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ use crate::checkers::ast::Checker;
|
||||
/// contains multiple characters, the reader may be misled into thinking that
|
||||
/// a prefix or suffix is being removed, rather than a set of characters.
|
||||
///
|
||||
/// In Python 3.9 and later, you can use `str#removeprefix` and
|
||||
/// `str#removesuffix` to remove an exact prefix or suffix from a string,
|
||||
/// In Python 3.9 and later, you can use `str.removeprefix` and
|
||||
/// `str.removesuffix` to remove an exact prefix or suffix from a string,
|
||||
/// respectively, which should be preferred when possible.
|
||||
///
|
||||
/// ## Example
|
||||
|
||||
@@ -137,13 +137,13 @@ fn is_standard_library_override(
|
||||
return false;
|
||||
};
|
||||
match name {
|
||||
// Ex) `Event#set`
|
||||
// Ex) `Event.set`
|
||||
"set" => bases.iter().any(|base| {
|
||||
semantic
|
||||
.resolve_call_path(base)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["threading", "Event"]))
|
||||
}),
|
||||
// Ex) `Filter#filter`
|
||||
// Ex) `Filter.filter`
|
||||
"filter" => bases.iter().any(|base| {
|
||||
semantic
|
||||
.resolve_call_path(base)
|
||||
|
||||
@@ -7,17 +7,17 @@ use libcst_native::{
|
||||
RightCurlyBrace, RightParen, RightSquareBracket, Set, SetComp, SimpleString, SimpleWhitespace,
|
||||
TrailingWhitespace, Tuple,
|
||||
};
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use ruff_diagnostics::{Edit, Fix};
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::autofix::codemods::CodegenStylist;
|
||||
use crate::autofix::edits::pad;
|
||||
use crate::cst::helpers::space;
|
||||
use crate::cst::helpers::{negate, space};
|
||||
use crate::rules::flake8_comprehensions::rules::ObjectType;
|
||||
use crate::{
|
||||
checkers::ast::Checker,
|
||||
@@ -728,7 +728,7 @@ pub(crate) fn fix_unnecessary_call_around_sorted(
|
||||
})
|
||||
)
|
||||
}) {
|
||||
// Negate the `reverse` argument
|
||||
// Negate the `reverse` argument.
|
||||
inner_call
|
||||
.args
|
||||
.clone()
|
||||
@@ -741,31 +741,9 @@ pub(crate) fn fix_unnecessary_call_around_sorted(
|
||||
..
|
||||
})
|
||||
) {
|
||||
if let Expression::Name(ref val) = arg.value {
|
||||
if val.value == "True" {
|
||||
// TODO: even better would be to drop the argument, as False is the default
|
||||
arg.value = Expression::Name(Box::new(Name {
|
||||
value: "False",
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
arg
|
||||
} else if val.value == "False" {
|
||||
arg.value = Expression::Name(Box::new(Name {
|
||||
value: "True",
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}));
|
||||
arg
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
} else {
|
||||
arg
|
||||
}
|
||||
} else {
|
||||
arg
|
||||
arg.value = negate(&arg.value);
|
||||
}
|
||||
arg
|
||||
})
|
||||
.collect_vec()
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::{self as ast, Comprehension, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -86,28 +87,16 @@ pub(crate) fn unnecessary_dict_comprehension(
|
||||
if !generator.ifs.is_empty() || generator.is_async {
|
||||
return;
|
||||
}
|
||||
let Some(key) = key.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
let Some(value) = value.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
let Expr::Tuple(ast::ExprTuple { elts, .. }) = &generator.target else {
|
||||
return;
|
||||
};
|
||||
let [target_key, target_value] = elts.as_slice() else {
|
||||
return;
|
||||
};
|
||||
let Some(target_key) = target_key.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
let Some(target_value) = target_value.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
if target_key.id != key.id {
|
||||
if ComparableExpr::from(key) != ComparableExpr::from(target_key) {
|
||||
return;
|
||||
}
|
||||
if target_value.id != value.id {
|
||||
if ComparableExpr::from(value) != ComparableExpr::from(target_value) {
|
||||
return;
|
||||
}
|
||||
add_diagnostic(checker, expr);
|
||||
@@ -126,13 +115,7 @@ pub(crate) fn unnecessary_list_set_comprehension(
|
||||
if !generator.ifs.is_empty() || generator.is_async {
|
||||
return;
|
||||
}
|
||||
let Some(elt) = elt.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
let Some(target) = generator.target.as_name_expr() else {
|
||||
return;
|
||||
};
|
||||
if elt.id != target.id {
|
||||
if ComparableExpr::from(elt) != ComparableExpr::from(&generator.target) {
|
||||
return;
|
||||
}
|
||||
add_diagnostic(checker, expr);
|
||||
|
||||
@@ -103,17 +103,18 @@ C413.py:7:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
||||
7 |+sorted(x, key=lambda e: e, reverse=False)
|
||||
8 8 | reversed(sorted(x, reverse=True, key=lambda e: e))
|
||||
9 9 | reversed(sorted(x, reverse=False))
|
||||
10 10 |
|
||||
10 10 | reversed(sorted(x, reverse=x))
|
||||
|
||||
C413.py:8:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
||||
|
|
||||
6 | reversed(sorted(x, reverse=True))
|
||||
7 | reversed(sorted(x, key=lambda e: e, reverse=True))
|
||||
8 | reversed(sorted(x, reverse=True, key=lambda e: e))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
|
||||
9 | reversed(sorted(x, reverse=False))
|
||||
|
|
||||
= help: Remove unnecessary `reversed` call
|
||||
|
|
||||
6 | reversed(sorted(x, reverse=True))
|
||||
7 | reversed(sorted(x, key=lambda e: e, reverse=True))
|
||||
8 | reversed(sorted(x, reverse=True, key=lambda e: e))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
|
||||
9 | reversed(sorted(x, reverse=False))
|
||||
10 | reversed(sorted(x, reverse=x))
|
||||
|
|
||||
= help: Remove unnecessary `reversed` call
|
||||
|
||||
ℹ Suggested fix
|
||||
5 5 | reversed(sorted(x, key=lambda e: e))
|
||||
@@ -122,8 +123,8 @@ C413.py:8:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
||||
8 |-reversed(sorted(x, reverse=True, key=lambda e: e))
|
||||
8 |+sorted(x, reverse=False, key=lambda e: e)
|
||||
9 9 | reversed(sorted(x, reverse=False))
|
||||
10 10 |
|
||||
11 11 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
||||
10 10 | reversed(sorted(x, reverse=x))
|
||||
11 11 | reversed(sorted(x, reverse=not x))
|
||||
|
||||
C413.py:9:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
||||
|
|
||||
@@ -131,8 +132,8 @@ C413.py:9:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
||||
8 | reversed(sorted(x, reverse=True, key=lambda e: e))
|
||||
9 | reversed(sorted(x, reverse=False))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
|
||||
10 |
|
||||
11 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
||||
10 | reversed(sorted(x, reverse=x))
|
||||
11 | reversed(sorted(x, reverse=not x))
|
||||
|
|
||||
= help: Remove unnecessary `reversed` call
|
||||
|
||||
@@ -142,46 +143,87 @@ C413.py:9:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
||||
8 8 | reversed(sorted(x, reverse=True, key=lambda e: e))
|
||||
9 |-reversed(sorted(x, reverse=False))
|
||||
9 |+sorted(x, reverse=True)
|
||||
10 10 |
|
||||
11 11 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
||||
12 12 | reversed(sorted(i for i in range(42)))
|
||||
10 10 | reversed(sorted(x, reverse=x))
|
||||
11 11 | reversed(sorted(x, reverse=not x))
|
||||
12 12 |
|
||||
|
||||
C413.py:12:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
||||
C413.py:10:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
||||
|
|
||||
11 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
||||
12 | reversed(sorted(i for i in range(42)))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
|
||||
13 | reversed(sorted((i for i in range(42)), reverse=True))
|
||||
8 | reversed(sorted(x, reverse=True, key=lambda e: e))
|
||||
9 | reversed(sorted(x, reverse=False))
|
||||
10 | reversed(sorted(x, reverse=x))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
|
||||
11 | reversed(sorted(x, reverse=not x))
|
||||
|
|
||||
= help: Remove unnecessary `reversed` call
|
||||
|
||||
ℹ Suggested fix
|
||||
7 7 | reversed(sorted(x, key=lambda e: e, reverse=True))
|
||||
8 8 | reversed(sorted(x, reverse=True, key=lambda e: e))
|
||||
9 9 | reversed(sorted(x, reverse=False))
|
||||
10 10 |
|
||||
11 11 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
||||
12 |-reversed(sorted(i for i in range(42)))
|
||||
12 |+sorted((i for i in range(42)), reverse=True)
|
||||
13 13 | reversed(sorted((i for i in range(42)), reverse=True))
|
||||
14 14 |
|
||||
15 15 |
|
||||
10 |-reversed(sorted(x, reverse=x))
|
||||
10 |+sorted(x, reverse=not x)
|
||||
11 11 | reversed(sorted(x, reverse=not x))
|
||||
12 12 |
|
||||
13 13 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
||||
|
||||
C413.py:13:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
||||
C413.py:11:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
||||
|
|
||||
11 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
||||
12 | reversed(sorted(i for i in range(42)))
|
||||
13 | reversed(sorted((i for i in range(42)), reverse=True))
|
||||
9 | reversed(sorted(x, reverse=False))
|
||||
10 | reversed(sorted(x, reverse=x))
|
||||
11 | reversed(sorted(x, reverse=not x))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
|
||||
12 |
|
||||
13 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
||||
|
|
||||
= help: Remove unnecessary `reversed` call
|
||||
|
||||
ℹ Suggested fix
|
||||
8 8 | reversed(sorted(x, reverse=True, key=lambda e: e))
|
||||
9 9 | reversed(sorted(x, reverse=False))
|
||||
10 10 | reversed(sorted(x, reverse=x))
|
||||
11 |-reversed(sorted(x, reverse=not x))
|
||||
11 |+sorted(x, reverse=x)
|
||||
12 12 |
|
||||
13 13 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
||||
14 14 | reversed(sorted(i for i in range(42)))
|
||||
|
||||
C413.py:14:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
||||
|
|
||||
13 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
||||
14 | reversed(sorted(i for i in range(42)))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
|
||||
15 | reversed(sorted((i for i in range(42)), reverse=True))
|
||||
|
|
||||
= help: Remove unnecessary `reversed` call
|
||||
|
||||
ℹ Suggested fix
|
||||
11 11 | reversed(sorted(x, reverse=not x))
|
||||
12 12 |
|
||||
13 13 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
||||
14 |-reversed(sorted(i for i in range(42)))
|
||||
14 |+sorted((i for i in range(42)), reverse=True)
|
||||
15 15 | reversed(sorted((i for i in range(42)), reverse=True))
|
||||
16 16 |
|
||||
17 17 |
|
||||
|
||||
C413.py:15:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
||||
|
|
||||
13 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
||||
14 | reversed(sorted(i for i in range(42)))
|
||||
15 | reversed(sorted((i for i in range(42)), reverse=True))
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
|
||||
|
|
||||
= help: Remove unnecessary `reversed` call
|
||||
|
||||
ℹ Suggested fix
|
||||
10 10 |
|
||||
11 11 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
||||
12 12 | reversed(sorted(i for i in range(42)))
|
||||
13 |-reversed(sorted((i for i in range(42)), reverse=True))
|
||||
13 |+sorted((i for i in range(42)), reverse=False)
|
||||
14 14 |
|
||||
15 15 |
|
||||
16 16 | def reversed(*args, **kwargs):
|
||||
12 12 |
|
||||
13 13 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
||||
14 14 | reversed(sorted(i for i in range(42)))
|
||||
15 |-reversed(sorted((i for i in range(42)), reverse=True))
|
||||
15 |+sorted((i for i in range(42)), reverse=False)
|
||||
16 16 |
|
||||
17 17 |
|
||||
18 18 | def reversed(*args, **kwargs):
|
||||
|
||||
|
||||
|
||||
@@ -40,17 +40,18 @@ C416.py:7:1: C416 [*] Unnecessary `set` comprehension (rewrite using `set()`)
|
||||
7 |+set(x)
|
||||
8 8 | {k: v for k, v in y}
|
||||
9 9 | {k: v for k, v in d.items()}
|
||||
10 10 |
|
||||
10 10 | [(k, v) for k, v in d.items()]
|
||||
|
||||
C416.py:8:1: C416 [*] Unnecessary `dict` comprehension (rewrite using `dict()`)
|
||||
|
|
||||
6 | [i for i in x]
|
||||
7 | {i for i in x}
|
||||
8 | {k: v for k, v in y}
|
||||
| ^^^^^^^^^^^^^^^^^^^^ C416
|
||||
9 | {k: v for k, v in d.items()}
|
||||
|
|
||||
= help: Rewrite using `dict()`
|
||||
|
|
||||
6 | [i for i in x]
|
||||
7 | {i for i in x}
|
||||
8 | {k: v for k, v in y}
|
||||
| ^^^^^^^^^^^^^^^^^^^^ C416
|
||||
9 | {k: v for k, v in d.items()}
|
||||
10 | [(k, v) for k, v in d.items()]
|
||||
|
|
||||
= help: Rewrite using `dict()`
|
||||
|
||||
ℹ Suggested fix
|
||||
5 5 |
|
||||
@@ -59,8 +60,8 @@ C416.py:8:1: C416 [*] Unnecessary `dict` comprehension (rewrite using `dict()`)
|
||||
8 |-{k: v for k, v in y}
|
||||
8 |+dict(y)
|
||||
9 9 | {k: v for k, v in d.items()}
|
||||
10 10 |
|
||||
11 11 | [i for i, in z]
|
||||
10 10 | [(k, v) for k, v in d.items()]
|
||||
11 11 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
|
||||
C416.py:9:1: C416 [*] Unnecessary `dict` comprehension (rewrite using `dict()`)
|
||||
|
|
||||
@@ -68,8 +69,8 @@ C416.py:9:1: C416 [*] Unnecessary `dict` comprehension (rewrite using `dict()`)
|
||||
8 | {k: v for k, v in y}
|
||||
9 | {k: v for k, v in d.items()}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C416
|
||||
10 |
|
||||
11 | [i for i, in z]
|
||||
10 | [(k, v) for k, v in d.items()]
|
||||
11 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
|
|
||||
= help: Rewrite using `dict()`
|
||||
|
||||
@@ -79,23 +80,64 @@ C416.py:9:1: C416 [*] Unnecessary `dict` comprehension (rewrite using `dict()`)
|
||||
8 8 | {k: v for k, v in y}
|
||||
9 |-{k: v for k, v in d.items()}
|
||||
9 |+dict(d.items())
|
||||
10 10 |
|
||||
11 11 | [i for i, in z]
|
||||
12 12 | [i for i, j in y]
|
||||
10 10 | [(k, v) for k, v in d.items()]
|
||||
11 11 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
12 12 |
|
||||
|
||||
C416.py:22:70: C416 [*] Unnecessary `list` comprehension (rewrite using `list()`)
|
||||
C416.py:10:1: C416 [*] Unnecessary `list` comprehension (rewrite using `list()`)
|
||||
|
|
||||
21 | # Regression test for: https://github.com/astral-sh/ruff/issues/7196
|
||||
22 | any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in[t for t in SymbolType])
|
||||
8 | {k: v for k, v in y}
|
||||
9 | {k: v for k, v in d.items()}
|
||||
10 | [(k, v) for k, v in d.items()]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C416
|
||||
11 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
|
|
||||
= help: Rewrite using `list()`
|
||||
|
||||
ℹ Suggested fix
|
||||
7 7 | {i for i in x}
|
||||
8 8 | {k: v for k, v in y}
|
||||
9 9 | {k: v for k, v in d.items()}
|
||||
10 |-[(k, v) for k, v in d.items()]
|
||||
10 |+list(d.items())
|
||||
11 11 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
12 12 |
|
||||
13 13 | [i for i, in z]
|
||||
|
||||
C416.py:11:1: C416 [*] Unnecessary `dict` comprehension (rewrite using `dict()`)
|
||||
|
|
||||
9 | {k: v for k, v in d.items()}
|
||||
10 | [(k, v) for k, v in d.items()]
|
||||
11 | {k: (a, b) for k, (a, b) in d.items()}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C416
|
||||
12 |
|
||||
13 | [i for i, in z]
|
||||
|
|
||||
= help: Rewrite using `dict()`
|
||||
|
||||
ℹ Suggested fix
|
||||
8 8 | {k: v for k, v in y}
|
||||
9 9 | {k: v for k, v in d.items()}
|
||||
10 10 | [(k, v) for k, v in d.items()]
|
||||
11 |-{k: (a, b) for k, (a, b) in d.items()}
|
||||
11 |+dict(d.items())
|
||||
12 12 |
|
||||
13 13 | [i for i, in z]
|
||||
14 14 | [i for i, j in y]
|
||||
|
||||
C416.py:24:70: C416 [*] Unnecessary `list` comprehension (rewrite using `list()`)
|
||||
|
|
||||
23 | # Regression test for: https://github.com/astral-sh/ruff/issues/7196
|
||||
24 | any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in[t for t in SymbolType])
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ C416
|
||||
|
|
||||
= help: Rewrite using `list()`
|
||||
|
||||
ℹ Suggested fix
|
||||
19 19 | {k: v if v else None for k, v in y}
|
||||
20 20 |
|
||||
21 21 | # Regression test for: https://github.com/astral-sh/ruff/issues/7196
|
||||
22 |-any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in[t for t in SymbolType])
|
||||
22 |+any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in list(SymbolType))
|
||||
21 21 | {k: v if v else None for k, v in y}
|
||||
22 22 |
|
||||
23 23 | # Regression test for: https://github.com/astral-sh/ruff/issues/7196
|
||||
24 |-any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in[t for t in SymbolType])
|
||||
24 |+any(len(symbol_table.get_by_type(symbol_type)) > 0 for symbol_type in list(SymbolType))
|
||||
|
||||
|
||||
|
||||
27
crates/ruff/src/rules/flake8_logging/mod.rs
Normal file
27
crates/ruff/src/rules/flake8_logging/mod.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
//! Rules from [flake8-logging](https://pypi.org/project/flake8-logging/).
|
||||
pub(crate) mod rules;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::assert_messages;
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::Settings;
|
||||
use crate::test::test_path;
|
||||
|
||||
#[test_case(Rule::DirectLoggerInstantiation, Path::new("LOG001.py"))]
|
||||
#[test_case(Rule::UndocumentedWarn, Path::new("LOG009.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_logging").join(path).as_path(),
|
||||
&Settings::for_rule(rule_code),
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for direct instantiation of `logging.Logger`, as opposed to using
|
||||
/// `logging.getLogger()`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The [Logger Objects] documentation states that:
|
||||
///
|
||||
/// > Note that Loggers should NEVER be instantiated directly, but always
|
||||
/// > through the module-level function `logging.getLogger(name)`.
|
||||
///
|
||||
/// If a logger is directly instantiated, it won't be added to the logger
|
||||
/// tree, and will bypass all configuration. Messages logged to it will
|
||||
/// only be sent to the "handler of last resort", skipping any filtering
|
||||
/// or formatting.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import logging
|
||||
///
|
||||
/// logger = logging.Logger(__name__)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import logging
|
||||
///
|
||||
/// logger = logging.getLogger(__name__)
|
||||
/// ```
|
||||
///
|
||||
/// [Logger Objects]: https://docs.python.org/3/library/logging.html#logger-objects
|
||||
#[violation]
|
||||
pub struct DirectLoggerInstantiation;
|
||||
|
||||
impl Violation for DirectLoggerInstantiation {
|
||||
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use `logging.getLogger()` to instantiate loggers")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> Option<String> {
|
||||
Some(format!("Replace with `logging.getLogger()`"))
|
||||
}
|
||||
}
|
||||
|
||||
/// LOG001
|
||||
pub(crate) fn direct_logger_instantiation(checker: &mut Checker, call: &ast::ExprCall) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(call.func.as_ref())
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "Logger"]))
|
||||
{
|
||||
let mut diagnostic = Diagnostic::new(DirectLoggerInstantiation, call.func.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import("logging", "getLogger"),
|
||||
call.func.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
let reference_edit = Edit::range_replacement(binding, call.func.range());
|
||||
Ok(Fix::suggested_edits(import_edit, [reference_edit]))
|
||||
});
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
5
crates/ruff/src/rules/flake8_logging/rules/mod.rs
Normal file
5
crates/ruff/src/rules/flake8_logging/rules/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub(crate) use direct_logger_instantiation::*;
|
||||
pub(crate) use undocumented_warn::*;
|
||||
|
||||
mod direct_logger_instantiation;
|
||||
mod undocumented_warn;
|
||||
@@ -0,0 +1,71 @@
|
||||
use ruff_python_ast::Expr;
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `logging.WARN`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The `logging.WARN` constant is an undocumented alias for `logging.WARNING`.
|
||||
///
|
||||
/// Although it’s not explicitly deprecated, `logging.WARN` is not mentioned
|
||||
/// in the `logging` documentation. Prefer `logging.WARNING` instead.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import logging
|
||||
///
|
||||
///
|
||||
/// logging.basicConfig(level=logging.WARN)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import logging
|
||||
///
|
||||
///
|
||||
/// logging.basicConfig(level=logging.WARNING)
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct UndocumentedWarn;
|
||||
|
||||
impl Violation for UndocumentedWarn {
|
||||
const AUTOFIX: AutofixKind = AutofixKind::Sometimes;
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use of undocumented `logging.WARN` constant")
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> Option<String> {
|
||||
Some(format!("Replace `logging.WARN` with `logging.WARNING`"))
|
||||
}
|
||||
}
|
||||
|
||||
/// LOG009
|
||||
pub(crate) fn undocumented_warn(checker: &mut Checker, expr: &Expr) {
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(expr)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["logging", "WARN"]))
|
||||
{
|
||||
let mut diagnostic = Diagnostic::new(UndocumentedWarn, expr.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) = checker.importer().get_or_import_symbol(
|
||||
&ImportRequest::import("logging", "WARNING"),
|
||||
expr.start(),
|
||||
checker.semantic(),
|
||||
)?;
|
||||
let reference_edit = Edit::range_replacement(binding, expr.range());
|
||||
Ok(Fix::suggested_edits(import_edit, [reference_edit]))
|
||||
});
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_logging/mod.rs
|
||||
---
|
||||
LOG001.py:3:1: LOG001 [*] Use `logging.getLogger()` to instantiate loggers
|
||||
|
|
||||
1 | import logging
|
||||
2 |
|
||||
3 | logging.Logger(__name__)
|
||||
| ^^^^^^^^^^^^^^ LOG001
|
||||
4 | logging.Logger()
|
||||
5 | logging.getLogger(__name__)
|
||||
|
|
||||
= help: Replace with `logging.getLogger()`
|
||||
|
||||
ℹ Suggested fix
|
||||
1 1 | import logging
|
||||
2 2 |
|
||||
3 |-logging.Logger(__name__)
|
||||
3 |+logging.getLogger(__name__)
|
||||
4 4 | logging.Logger()
|
||||
5 5 | logging.getLogger(__name__)
|
||||
|
||||
LOG001.py:4:1: LOG001 [*] Use `logging.getLogger()` to instantiate loggers
|
||||
|
|
||||
3 | logging.Logger(__name__)
|
||||
4 | logging.Logger()
|
||||
| ^^^^^^^^^^^^^^ LOG001
|
||||
5 | logging.getLogger(__name__)
|
||||
|
|
||||
= help: Replace with `logging.getLogger()`
|
||||
|
||||
ℹ Suggested fix
|
||||
1 1 | import logging
|
||||
2 2 |
|
||||
3 3 | logging.Logger(__name__)
|
||||
4 |-logging.Logger()
|
||||
4 |+logging.getLogger()
|
||||
5 5 | logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_logging/mod.rs
|
||||
---
|
||||
LOG009.py:3:1: LOG009 [*] Use of undocumented `logging.WARN` constant
|
||||
|
|
||||
1 | import logging
|
||||
2 |
|
||||
3 | logging.WARN # LOG009
|
||||
| ^^^^^^^^^^^^ LOG009
|
||||
4 | logging.WARNING # OK
|
||||
|
|
||||
= help: Replace `logging.WARN` with `logging.WARNING`
|
||||
|
||||
ℹ Suggested fix
|
||||
1 1 | import logging
|
||||
2 2 |
|
||||
3 |-logging.WARN # LOG009
|
||||
3 |+logging.WARNING # LOG009
|
||||
4 4 | logging.WARNING # OK
|
||||
5 5 |
|
||||
6 6 | from logging import WARN, WARNING
|
||||
|
||||
LOG009.py:8:1: LOG009 [*] Use of undocumented `logging.WARN` constant
|
||||
|
|
||||
6 | from logging import WARN, WARNING
|
||||
7 |
|
||||
8 | WARN # LOG009
|
||||
| ^^^^ LOG009
|
||||
9 | WARNING # OK
|
||||
|
|
||||
= help: Replace `logging.WARN` with `logging.WARNING`
|
||||
|
||||
ℹ Suggested fix
|
||||
5 5 |
|
||||
6 6 | from logging import WARN, WARNING
|
||||
7 7 |
|
||||
8 |-WARN # LOG009
|
||||
8 |+logging.WARNING # LOG009
|
||||
9 9 | WARNING # OK
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use anyhow::Result;
|
||||
use anyhow::{bail, Context};
|
||||
use libcst_native::{
|
||||
self, Assert, BooleanOp, CompoundStatement, Expression, ParenthesizedNode, SimpleStatementLine,
|
||||
SimpleWhitespace, SmallStatement, Statement, TrailingWhitespace, UnaryOperation,
|
||||
SimpleWhitespace, SmallStatement, Statement, TrailingWhitespace,
|
||||
};
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
@@ -21,7 +21,7 @@ use ruff_text_size::Ranged;
|
||||
|
||||
use crate::autofix::codemods::CodegenStylist;
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::cst::helpers::space;
|
||||
use crate::cst::helpers::negate;
|
||||
use crate::cst::matchers::match_indented_block;
|
||||
use crate::cst::matchers::match_module;
|
||||
use crate::importer::ImportRequest;
|
||||
@@ -567,23 +567,6 @@ fn is_composite_condition(test: &Expr) -> CompositionKind {
|
||||
CompositionKind::None
|
||||
}
|
||||
|
||||
/// Negate a condition, i.e., `a` => `not a` and `not a` => `a`.
|
||||
fn negate<'a>(expression: &Expression<'a>) -> Expression<'a> {
|
||||
if let Expression::UnaryOperation(ref expression) = expression {
|
||||
if matches!(expression.operator, libcst_native::UnaryOp::Not { .. }) {
|
||||
return *expression.expression.clone();
|
||||
}
|
||||
}
|
||||
Expression::UnaryOperation(Box::new(UnaryOperation {
|
||||
operator: libcst_native::UnaryOp::Not {
|
||||
whitespace_after: space(),
|
||||
},
|
||||
expression: Box::new(expression.clone()),
|
||||
lpar: vec![],
|
||||
rpar: vec![],
|
||||
}))
|
||||
}
|
||||
|
||||
/// Propagate parentheses from a parent to a child expression, if necessary.
|
||||
///
|
||||
/// For example, when splitting:
|
||||
|
||||
@@ -12,10 +12,10 @@ use crate::registry::AsRule;
|
||||
use crate::rules::flynt::helpers;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `str#join` calls that can be replaced with f-strings.
|
||||
/// Checks for `str.join` calls that can be replaced with f-strings.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// f-strings are more readable and generally preferred over `str#join` calls.
|
||||
/// f-strings are more readable and generally preferred over `str.join` calls.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
|
||||
@@ -22,6 +22,7 @@ pub mod flake8_future_annotations;
|
||||
pub mod flake8_gettext;
|
||||
pub mod flake8_implicit_str_concat;
|
||||
pub mod flake8_import_conventions;
|
||||
pub mod flake8_logging;
|
||||
pub mod flake8_logging_format;
|
||||
pub mod flake8_no_pep420;
|
||||
pub mod flake8_pie;
|
||||
|
||||
@@ -19,6 +19,7 @@ mod tests {
|
||||
#[test_case(Rule::TryExceptInLoop, Path::new("PERF203.py"))]
|
||||
#[test_case(Rule::ManualListComprehension, Path::new("PERF401.py"))]
|
||||
#[test_case(Rule::ManualListCopy, Path::new("PERF402.py"))]
|
||||
#[test_case(Rule::ManualDictComprehension, Path::new("PERF403.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::helpers::any_over_expr;
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||
use ruff_python_semantic::analyze::typing::is_dict;
|
||||
use ruff_python_semantic::Binding;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `for` loops that can be replaced by a dictionary comprehension.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// When creating or extending a dictionary in a for-loop, prefer a dictionary
|
||||
/// comprehension. Comprehensions are more readable and more performant.
|
||||
///
|
||||
/// For example, when comparing `{x: x for x in list(range(1000))}` to the `for`
|
||||
/// loop version, the comprehension is ~10% faster on Python 3.11.
|
||||
///
|
||||
/// Note that, as with all `perflint` rules, this is only intended as a
|
||||
/// micro-optimization, and will have a negligible impact on performance in
|
||||
/// most cases.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// pairs = (("a", 1), ("b", 2))
|
||||
/// result = {}
|
||||
/// for x, y in pairs:
|
||||
/// if y % 2:
|
||||
/// result[x] = y
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// pairs = (("a", 1), ("b", 2))
|
||||
/// result = {x: y for x, y in pairs if y % 2}
|
||||
/// ```
|
||||
///
|
||||
/// If you're appending to an existing dictionary, use the `update` method instead:
|
||||
/// ```python
|
||||
/// pairs = (("a", 1), ("b", 2))
|
||||
/// result.update({x: y for x, y in pairs if y % 2})
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct ManualDictComprehension;
|
||||
|
||||
impl Violation for ManualDictComprehension {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Use a dictionary comprehension instead of a for-loop")
|
||||
}
|
||||
}
|
||||
|
||||
/// PERF403
|
||||
pub(crate) fn manual_dict_comprehension(checker: &mut Checker, target: &Expr, body: &[Stmt]) {
|
||||
let (stmt, if_test) = match body {
|
||||
// ```python
|
||||
// for idx, name in enumerate(names):
|
||||
// if idx % 2 == 0:
|
||||
// result[name] = idx
|
||||
// ```
|
||||
[Stmt::If(ast::StmtIf {
|
||||
body,
|
||||
elif_else_clauses,
|
||||
test,
|
||||
..
|
||||
})] => {
|
||||
// TODO(charlie): If there's an `else` clause, verify that the `else` has the
|
||||
// same structure.
|
||||
if !elif_else_clauses.is_empty() {
|
||||
return;
|
||||
}
|
||||
let [stmt] = body.as_slice() else {
|
||||
return;
|
||||
};
|
||||
(stmt, Some(test))
|
||||
}
|
||||
// ```python
|
||||
// for idx, name in enumerate(names):
|
||||
// result[name] = idx
|
||||
// ```
|
||||
[stmt] => (stmt, None),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let Stmt::Assign(ast::StmtAssign {
|
||||
targets,
|
||||
value,
|
||||
range,
|
||||
}) = stmt
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let [Expr::Subscript(ast::ExprSubscript {
|
||||
value: subscript_value,
|
||||
slice,
|
||||
..
|
||||
})] = targets.as_slice()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
match target {
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||
if !elts
|
||||
.iter()
|
||||
.any(|elt| ComparableExpr::from(slice) == ComparableExpr::from(elt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if !elts
|
||||
.iter()
|
||||
.any(|elt| ComparableExpr::from(value) == ComparableExpr::from(elt))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
Expr::Name(_) => {
|
||||
if ComparableExpr::from(slice) != ComparableExpr::from(target) {
|
||||
return;
|
||||
}
|
||||
if ComparableExpr::from(value) != ComparableExpr::from(target) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
|
||||
// Exclude non-dictionary value.
|
||||
let Expr::Name(ast::ExprName {
|
||||
id: subscript_name, ..
|
||||
}) = subscript_value.as_ref()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let scope = checker.semantic().current_scope();
|
||||
let bindings: Vec<&Binding> = scope
|
||||
.get_all(subscript_name)
|
||||
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||
.collect();
|
||||
|
||||
let [binding] = bindings.as_slice() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !is_dict(binding, checker.semantic()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid if the value is used in the conditional test, e.g.,
|
||||
//
|
||||
// ```python
|
||||
// for x in y:
|
||||
// if x in filtered:
|
||||
// filtered[x] = y
|
||||
// ```
|
||||
//
|
||||
// Converting this to a dictionary comprehension would raise a `NameError` as
|
||||
// `filtered` is not defined yet:
|
||||
//
|
||||
// ```python
|
||||
// filtered = {x: y for x in y if x in filtered}
|
||||
// ```
|
||||
if if_test.is_some_and(|test| {
|
||||
any_over_expr(test, &|expr| {
|
||||
expr.as_name_expr()
|
||||
.is_some_and(|expr| expr.id == *subscript_name)
|
||||
})
|
||||
}) {
|
||||
return;
|
||||
}
|
||||
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(ManualDictComprehension, *range));
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
pub(crate) use incorrect_dict_iterator::*;
|
||||
pub(crate) use manual_dict_comprehension::*;
|
||||
pub(crate) use manual_list_comprehension::*;
|
||||
pub(crate) use manual_list_copy::*;
|
||||
pub(crate) use try_except_in_loop::*;
|
||||
pub(crate) use unnecessary_list_cast::*;
|
||||
|
||||
mod incorrect_dict_iterator;
|
||||
mod manual_dict_comprehension;
|
||||
mod manual_list_comprehension;
|
||||
mod manual_list_copy;
|
||||
mod try_except_in_loop;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/perflint/mod.rs
|
||||
---
|
||||
PERF403.py:5:9: PERF403 Use a dictionary comprehension instead of a for-loop
|
||||
|
|
||||
3 | result = {}
|
||||
4 | for idx, name in enumerate(fruit):
|
||||
5 | result[idx] = name # PERF403
|
||||
| ^^^^^^^^^^^^^^^^^^ PERF403
|
||||
|
|
||||
|
||||
PERF403.py:13:13: PERF403 Use a dictionary comprehension instead of a for-loop
|
||||
|
|
||||
11 | for idx, name in enumerate(fruit):
|
||||
12 | if idx % 2:
|
||||
13 | result[idx] = name # PERF403
|
||||
| ^^^^^^^^^^^^^^^^^^ PERF403
|
||||
|
|
||||
|
||||
PERF403.py:31:13: PERF403 Use a dictionary comprehension instead of a for-loop
|
||||
|
|
||||
29 | for idx, name in enumerate(fruit):
|
||||
30 | if idx % 2:
|
||||
31 | result[idx] = name # PERF403
|
||||
| ^^^^^^^^^^^^^^^^^^ PERF403
|
||||
|
|
||||
|
||||
PERF403.py:61:13: PERF403 Use a dictionary comprehension instead of a for-loop
|
||||
|
|
||||
59 | for idx, name in enumerate(fruit):
|
||||
60 | if idx % 2:
|
||||
61 | result[idx] = name # PERF403
|
||||
| ^^^^^^^^^^^^^^^^^^ PERF403
|
||||
|
|
||||
|
||||
PERF403.py:76:9: PERF403 Use a dictionary comprehension instead of a for-loop
|
||||
|
|
||||
74 | result = {}
|
||||
75 | for name in fruit:
|
||||
76 | result[name] = name # PERF403
|
||||
| ^^^^^^^^^^^^^^^^^^^ PERF403
|
||||
|
|
||||
|
||||
PERF403.py:83:9: PERF403 Use a dictionary comprehension instead of a for-loop
|
||||
|
|
||||
81 | result = {}
|
||||
82 | for idx, name in enumerate(fruit):
|
||||
83 | result[name] = idx # PERF403
|
||||
| ^^^^^^^^^^^^^^^^^^ PERF403
|
||||
|
|
||||
|
||||
|
||||
@@ -70,6 +70,10 @@ impl Violation for BlankLineAfterSummary {
|
||||
pub(crate) fn blank_after_summary(checker: &mut Checker, docstring: &Docstring) {
|
||||
let body = docstring.body();
|
||||
|
||||
if !docstring.triple_quoted() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut lines_count: usize = 1;
|
||||
let mut blanks_count = 0;
|
||||
for line in body.trim().universal_newlines().skip(1) {
|
||||
|
||||
@@ -63,6 +63,10 @@ pub(crate) fn newline_after_last_paragraph(checker: &mut Checker, docstring: &Do
|
||||
let contents = docstring.contents;
|
||||
let body = docstring.body();
|
||||
|
||||
if !docstring.triple_quoted() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut line_count = 0;
|
||||
for line in NewlineWithTrailingNewline::from(body.as_str()) {
|
||||
if !line.trim().is_empty() {
|
||||
|
||||
@@ -97,5 +97,6 @@ D.py:658:5: D204 [*] 1 blank line required after class docstring
|
||||
659 |+
|
||||
659 660 | def sort_services(self):
|
||||
660 661 | pass
|
||||
661 662 |
|
||||
|
||||
|
||||
|
||||
@@ -77,4 +77,13 @@ D.py:658:5: D300 Use triple double quotes `"""`
|
||||
660 | pass
|
||||
|
|
||||
|
||||
D.py:664:5: D300 Use triple double quotes `"""`
|
||||
|
|
||||
663 | def newline_after_closing_quote(self):
|
||||
664 | "We enforce a newline after the closing quote for a multi-line docstring \
|
||||
| _____^
|
||||
665 | | but continuations shouldn't be considered multi-line"
|
||||
| |_________________________________________________________^ D300
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -310,4 +310,21 @@ D.py:641:18: D400 [*] First line should end with a period
|
||||
643 643 |
|
||||
644 644 | def single_line_docstring_with_an_escaped_backslash():
|
||||
|
||||
D.py:664:5: D400 [*] First line should end with a period
|
||||
|
|
||||
663 | def newline_after_closing_quote(self):
|
||||
664 | "We enforce a newline after the closing quote for a multi-line docstring \
|
||||
| _____^
|
||||
665 | | but continuations shouldn't be considered multi-line"
|
||||
| |_________________________________________________________^ D400
|
||||
|
|
||||
= help: Add period
|
||||
|
||||
ℹ Suggested fix
|
||||
662 662 |
|
||||
663 663 | def newline_after_closing_quote(self):
|
||||
664 664 | "We enforce a newline after the closing quote for a multi-line docstring \
|
||||
665 |- but continuations shouldn't be considered multi-line"
|
||||
665 |+ but continuations shouldn't be considered multi-line."
|
||||
|
||||
|
||||
|
||||
@@ -292,4 +292,21 @@ D.py:641:18: D415 [*] First line should end with a period, question mark, or exc
|
||||
643 643 |
|
||||
644 644 | def single_line_docstring_with_an_escaped_backslash():
|
||||
|
||||
D.py:664:5: D415 [*] First line should end with a period, question mark, or exclamation point
|
||||
|
|
||||
663 | def newline_after_closing_quote(self):
|
||||
664 | "We enforce a newline after the closing quote for a multi-line docstring \
|
||||
| _____^
|
||||
665 | | but continuations shouldn't be considered multi-line"
|
||||
| |_________________________________________________________^ D415
|
||||
|
|
||||
= help: Add closing punctuation
|
||||
|
||||
ℹ Suggested fix
|
||||
662 662 |
|
||||
663 663 | def newline_after_closing_quote(self):
|
||||
664 664 | "We enforce a newline after the closing quote for a multi-line docstring \
|
||||
665 |- but continuations shouldn't be considered multi-line"
|
||||
665 |+ but continuations shouldn't be considered multi-line."
|
||||
|
||||
|
||||
|
||||
@@ -256,4 +256,20 @@ mod tests {
|
||||
assert_messages!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_many_public_methods() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("pylint/too_many_public_methods.py"),
|
||||
&Settings {
|
||||
pylint: pylint::settings::Settings {
|
||||
max_public_methods: 7,
|
||||
..pylint::settings::Settings::default()
|
||||
},
|
||||
..Settings::for_rules(vec![Rule::TooManyPublicMethods])
|
||||
},
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,15 +11,15 @@ use crate::checkers::ast::Checker;
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks duplicate characters in `str#strip` calls.
|
||||
/// Checks duplicate characters in `str.strip` calls.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// All characters in `str#strip` calls are removed from both the leading and
|
||||
/// All characters in `str.strip` calls are removed from both the leading and
|
||||
/// trailing ends of the string. Including duplicate characters in the call
|
||||
/// is redundant and often indicative of a mistake.
|
||||
///
|
||||
/// In Python 3.9 and later, you can use `str#removeprefix` and
|
||||
/// `str#removesuffix` to remove an exact prefix or suffix from a string,
|
||||
/// In Python 3.9 and later, you can use `str.removeprefix` and
|
||||
/// `str.removesuffix` to remove an exact prefix or suffix from a string,
|
||||
/// respectively, which should be preferred when possible.
|
||||
///
|
||||
/// ## Example
|
||||
|
||||
@@ -43,6 +43,7 @@ pub(crate) use subprocess_run_without_check::*;
|
||||
pub(crate) use sys_exit_alias::*;
|
||||
pub(crate) use too_many_arguments::*;
|
||||
pub(crate) use too_many_branches::*;
|
||||
pub(crate) use too_many_public_methods::*;
|
||||
pub(crate) use too_many_return_statements::*;
|
||||
pub(crate) use too_many_statements::*;
|
||||
pub(crate) use type_bivariance::*;
|
||||
@@ -101,6 +102,7 @@ mod subprocess_run_without_check;
|
||||
mod sys_exit_alias;
|
||||
mod too_many_arguments;
|
||||
mod too_many_branches;
|
||||
mod too_many_public_methods;
|
||||
mod too_many_return_statements;
|
||||
mod too_many_statements;
|
||||
mod type_bivariance;
|
||||
|
||||
@@ -10,7 +10,7 @@ use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::hashable::HashableExpr;
|
||||
use ruff_python_ast::{self as ast, BoolOp, CmpOp, Expr};
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::Ranged;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use crate::autofix::snippet::SourceCodeSnippet;
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -74,7 +74,8 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
|
||||
return;
|
||||
}
|
||||
|
||||
let mut value_to_comparators: FxHashMap<HashableExpr, (usize, Vec<&Expr>)> =
|
||||
// Map from expression hash to (starting offset, number of comparisons, list
|
||||
let mut value_to_comparators: FxHashMap<HashableExpr, (TextSize, Vec<&Expr>)> =
|
||||
FxHashMap::with_capacity_and_hasher(
|
||||
bool_op.values.len() * 2,
|
||||
BuildHasherDefault::default(),
|
||||
@@ -95,30 +96,31 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
|
||||
};
|
||||
|
||||
if matches!(left.as_ref(), Expr::Name(_) | Expr::Attribute(_)) {
|
||||
let (left_count, left_matches) = value_to_comparators
|
||||
let (_, left_matches) = value_to_comparators
|
||||
.entry(left.deref().into())
|
||||
.or_insert_with(|| (0, Vec::new()));
|
||||
*left_count += 1;
|
||||
.or_insert_with(|| (left.start(), Vec::new()));
|
||||
left_matches.push(right);
|
||||
}
|
||||
|
||||
if matches!(right, Expr::Name(_) | Expr::Attribute(_)) {
|
||||
let (right_count, right_matches) = value_to_comparators
|
||||
let (_, right_matches) = value_to_comparators
|
||||
.entry(right.into())
|
||||
.or_insert_with(|| (0, Vec::new()));
|
||||
*right_count += 1;
|
||||
.or_insert_with(|| (right.start(), Vec::new()));
|
||||
right_matches.push(left);
|
||||
}
|
||||
}
|
||||
|
||||
for (value, (count, comparators)) in value_to_comparators {
|
||||
if count > 1 {
|
||||
for (value, (_, comparators)) in value_to_comparators
|
||||
.iter()
|
||||
.sorted_by_key(|(_, (start, _))| *start)
|
||||
{
|
||||
if comparators.len() > 1 {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
RepeatedEqualityComparison {
|
||||
expression: SourceCodeSnippet::new(merged_membership_test(
|
||||
value.as_expr(),
|
||||
bool_op.op,
|
||||
&comparators,
|
||||
comparators,
|
||||
checker.locator(),
|
||||
)),
|
||||
},
|
||||
|
||||
126
crates/ruff/src/rules/pylint/rules/too_many_public_methods.rs
Normal file
126
crates/ruff/src/rules/pylint/rules/too_many_public_methods.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_semantic::analyze::visibility::{self, Visibility::Public};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for classes with too many public methods
|
||||
///
|
||||
/// By default, this rule allows up to 20 statements, as configured by the
|
||||
/// [`pylint.max-public-methods`] option.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Classes with many public methods are harder to understand
|
||||
/// and maintain.
|
||||
///
|
||||
/// Instead, consider refactoring the class into separate classes.
|
||||
///
|
||||
/// ## Example
|
||||
/// Assuming that `pylint.max-public-settings` is set to 5:
|
||||
/// ```python
|
||||
/// class Linter:
|
||||
/// def __init__(self):
|
||||
/// pass
|
||||
///
|
||||
/// def pylint(self):
|
||||
/// pass
|
||||
///
|
||||
/// def pylint_settings(self):
|
||||
/// pass
|
||||
///
|
||||
/// def flake8(self):
|
||||
/// pass
|
||||
///
|
||||
/// def flake8_settings(self):
|
||||
/// pass
|
||||
///
|
||||
/// def pydocstyle(self):
|
||||
/// pass
|
||||
///
|
||||
/// def pydocstyle_settings(self):
|
||||
/// pass
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// class Linter:
|
||||
/// def __init__(self):
|
||||
/// self.pylint = Pylint()
|
||||
/// self.flake8 = Flake8()
|
||||
/// self.pydocstyle = Pydocstyle()
|
||||
///
|
||||
/// def lint(self):
|
||||
/// pass
|
||||
///
|
||||
///
|
||||
/// class Pylint:
|
||||
/// def lint(self):
|
||||
/// pass
|
||||
///
|
||||
/// def settings(self):
|
||||
/// pass
|
||||
///
|
||||
///
|
||||
/// class Flake8:
|
||||
/// def lint(self):
|
||||
/// pass
|
||||
///
|
||||
/// def settings(self):
|
||||
/// pass
|
||||
///
|
||||
///
|
||||
/// class Pydocstyle:
|
||||
/// def lint(self):
|
||||
/// pass
|
||||
///
|
||||
/// def settings(self):
|
||||
/// pass
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - `pylint.max-public-methods`
|
||||
#[violation]
|
||||
pub struct TooManyPublicMethods {
|
||||
methods: usize,
|
||||
max_methods: usize,
|
||||
}
|
||||
|
||||
impl Violation for TooManyPublicMethods {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let TooManyPublicMethods {
|
||||
methods,
|
||||
max_methods,
|
||||
} = self;
|
||||
format!("Too many public methods ({methods} > {max_methods})")
|
||||
}
|
||||
}
|
||||
|
||||
/// R0904
|
||||
pub(crate) fn too_many_public_methods(
|
||||
checker: &mut Checker,
|
||||
class_def: &ast::StmtClassDef,
|
||||
max_methods: usize,
|
||||
) {
|
||||
let methods = class_def
|
||||
.body
|
||||
.iter()
|
||||
.filter(|stmt| {
|
||||
stmt.as_function_def_stmt()
|
||||
.is_some_and(|node| matches!(visibility::method_visibility(node), Public))
|
||||
})
|
||||
.count();
|
||||
|
||||
if methods > max_methods {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
TooManyPublicMethods {
|
||||
methods,
|
||||
max_methods,
|
||||
},
|
||||
class_def.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ pub struct Settings {
|
||||
pub max_returns: usize,
|
||||
pub max_branches: usize,
|
||||
pub max_statements: usize,
|
||||
pub max_public_methods: usize,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
@@ -52,6 +53,7 @@ impl Default for Settings {
|
||||
max_returns: 6,
|
||||
max_branches: 12,
|
||||
max_statements: 50,
|
||||
max_public_methods: 20,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pylint/mod.rs
|
||||
---
|
||||
too_many_public_methods.py:1:1: PLR0904 Too many public methods (10 > 7)
|
||||
|
|
||||
1 | / class Everything:
|
||||
2 | | foo = 1
|
||||
3 | |
|
||||
4 | | def __init__(self):
|
||||
5 | | pass
|
||||
6 | |
|
||||
7 | | def _private(self):
|
||||
8 | | pass
|
||||
9 | |
|
||||
10 | | def method1(self):
|
||||
11 | | pass
|
||||
12 | |
|
||||
13 | | def method2(self):
|
||||
14 | | pass
|
||||
15 | |
|
||||
16 | | def method3(self):
|
||||
17 | | pass
|
||||
18 | |
|
||||
19 | | def method4(self):
|
||||
20 | | pass
|
||||
21 | |
|
||||
22 | | def method5(self):
|
||||
23 | | pass
|
||||
24 | |
|
||||
25 | | def method6(self):
|
||||
26 | | pass
|
||||
27 | |
|
||||
28 | | def method7(self):
|
||||
29 | | pass
|
||||
30 | |
|
||||
31 | | def method8(self):
|
||||
32 | | pass
|
||||
33 | |
|
||||
34 | | def method9(self):
|
||||
35 | | pass
|
||||
| |____________^ PLR0904
|
||||
36 |
|
||||
37 | class Small:
|
||||
|
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@ use crate::rules::pyflakes::format::FormatSummary;
|
||||
use crate::rules::pyupgrade::helpers::curly_escape;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `str#format` calls that can be replaced with f-strings.
|
||||
/// Checks for `str.format` calls that can be replaced with f-strings.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// f-strings are more readable and generally preferred over `str#format`
|
||||
/// f-strings are more readable and generally preferred over `str.format`
|
||||
/// calls.
|
||||
///
|
||||
/// ## Example
|
||||
|
||||
@@ -42,13 +42,16 @@ pub struct UnnecessaryEncodeUTF8 {
|
||||
impl AlwaysAutofixableViolation for UnnecessaryEncodeUTF8 {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Unnecessary call to `encode` as UTF-8")
|
||||
match self.reason {
|
||||
Reason::BytesLiteral => format!("Unnecessary call to `encode` as UTF-8"),
|
||||
Reason::DefaultArgument => format!("Unnecessary UTF-8 `encoding` argument to `encode`"),
|
||||
}
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> String {
|
||||
match self.reason {
|
||||
Reason::BytesLiteral => "Rewrite as bytes literal".to_string(),
|
||||
Reason::DefaultArgument => "Remove unnecessary encoding argument".to_string(),
|
||||
Reason::DefaultArgument => "Remove unnecessary `encoding` argument".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use ast::{Constant, ExprCall, ExprConstant};
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{
|
||||
self as ast,
|
||||
visitor::{self, Visitor},
|
||||
@@ -6,20 +8,29 @@ use ruff_python_ast::{
|
||||
TypeParam, TypeParamTypeVar,
|
||||
};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
|
||||
use crate::{registry::AsRule, settings::types::PythonVersion};
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::{registry::AsRule, settings::types::PythonVersion};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for use of `TypeAlias` annotation for declaring type aliases.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The `type` keyword was introduced in Python 3.12 by PEP-695 for defining type aliases.
|
||||
/// The type keyword is easier to read and provides cleaner support for generics.
|
||||
/// The `type` keyword was introduced in Python 3.12 by [PEP 695] for defining
|
||||
/// type aliases. The `type` keyword is easier to read and provides cleaner
|
||||
/// support for generics.
|
||||
///
|
||||
/// ## Known problems
|
||||
/// [PEP 695] uses inferred variance for type parameters, instead of the
|
||||
/// `covariant` and `contravariant` keywords used by `TypeParam` variables. As
|
||||
/// such, rewriting a `TypeParam` variable to a `type` alias may change its
|
||||
/// variance.
|
||||
///
|
||||
/// Unlike `TypeParam` variables, [PEP 695]-style `type` aliases cannot be used
|
||||
/// at runtime. For example, calling `isinstance` on a `type` alias will throw
|
||||
/// a `TypeError`. As such, rewriting a `TypeParam` via the `type` keyword will
|
||||
/// cause issues for parameters that are used for such runtime checks.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -30,6 +41,8 @@ use crate::checkers::ast::Checker;
|
||||
/// ```python
|
||||
/// type ListOfInt = list[int]
|
||||
/// ```
|
||||
///
|
||||
/// [PEP 695]: https://peps.python.org/pep-0695/
|
||||
#[violation]
|
||||
pub struct NonPEP695TypeAlias {
|
||||
name: String,
|
||||
@@ -83,24 +96,36 @@ pub(crate) fn non_pep695_type_alias(checker: &mut Checker, stmt: &StmtAnnAssign)
|
||||
let mut diagnostic = Diagnostic::new(NonPEP695TypeAlias { name: name.clone() }, stmt.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
let mut visitor = TypeVarReferenceVisitor {
|
||||
names: vec![],
|
||||
vars: vec![],
|
||||
semantic: checker.semantic(),
|
||||
};
|
||||
visitor.visit_expr(value);
|
||||
|
||||
let type_params = if visitor.names.is_empty() {
|
||||
let type_params = if visitor.vars.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(ast::TypeParams {
|
||||
range: TextRange::default(),
|
||||
type_params: visitor
|
||||
.names
|
||||
.iter()
|
||||
.map(|name| {
|
||||
.vars
|
||||
.into_iter()
|
||||
.map(|TypeVar { name, restriction }| {
|
||||
TypeParam::TypeVar(TypeParamTypeVar {
|
||||
range: TextRange::default(),
|
||||
name: Identifier::new(name.id.clone(), TextRange::default()),
|
||||
bound: None,
|
||||
bound: match restriction {
|
||||
Some(TypeVarRestriction::Bound(bound)) => {
|
||||
Some(Box::new(bound.clone()))
|
||||
}
|
||||
Some(TypeVarRestriction::Constraint(constraints)) => {
|
||||
Some(Box::new(Expr::Tuple(ast::ExprTuple {
|
||||
range: TextRange::default(),
|
||||
elts: constraints.into_iter().cloned().collect(),
|
||||
ctx: ast::ExprContext::Load,
|
||||
})))
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
@@ -120,8 +145,22 @@ pub(crate) fn non_pep695_type_alias(checker: &mut Checker, stmt: &StmtAnnAssign)
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum TypeVarRestriction<'a> {
|
||||
/// A type variable with a bound, e.g., `TypeVar("T", bound=int)`.
|
||||
Bound(&'a Expr),
|
||||
/// A type variable with constraints, e.g., `TypeVar("T", int, str)`.
|
||||
Constraint(Vec<&'a Expr>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TypeVar<'a> {
|
||||
name: &'a ExprName,
|
||||
restriction: Option<TypeVarRestriction<'a>>,
|
||||
}
|
||||
|
||||
struct TypeVarReferenceVisitor<'a> {
|
||||
names: Vec<&'a ExprName>,
|
||||
vars: Vec<TypeVar<'a>>,
|
||||
semantic: &'a SemanticModel<'a>,
|
||||
}
|
||||
|
||||
@@ -149,16 +188,16 @@ impl<'a> Visitor<'a> for TypeVarReferenceVisitor<'a> {
|
||||
..
|
||||
}) => {
|
||||
if self.semantic.match_typing_expr(subscript_value, "TypeVar") {
|
||||
self.names.push(name);
|
||||
self.vars.push(TypeVar {
|
||||
name,
|
||||
restriction: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
Expr::Call(ExprCall {
|
||||
func, arguments, ..
|
||||
}) => {
|
||||
// TODO(zanieb): Add support for bounds and variance declarations
|
||||
// for now this only supports `TypeVar("...")`
|
||||
if self.semantic.match_typing_expr(func, "TypeVar")
|
||||
&& arguments.args.len() == 1
|
||||
&& arguments.args.first().is_some_and(|arg| {
|
||||
matches!(
|
||||
arg,
|
||||
@@ -168,9 +207,18 @@ impl<'a> Visitor<'a> for TypeVarReferenceVisitor<'a> {
|
||||
})
|
||||
)
|
||||
})
|
||||
&& arguments.keywords.is_empty()
|
||||
{
|
||||
self.names.push(name);
|
||||
let restriction = if let Some(bound) = arguments.find_keyword("bound") {
|
||||
Some(TypeVarRestriction::Bound(&bound.value))
|
||||
} else if arguments.args.len() > 1 {
|
||||
Some(TypeVarRestriction::Constraint(
|
||||
arguments.args.iter().skip(1).collect(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.vars.push(TypeVar { name, restriction });
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -227,7 +227,7 @@ UP012.py:24:5: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
26 26 |
|
||||
27 27 | # `encode` on variables should not be processed.
|
||||
|
||||
UP012.py:32:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
UP012.py:32:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
31 | bar = "bar"
|
||||
32 | f"foo{bar}".encode("utf-8")
|
||||
@@ -235,7 +235,7 @@ UP012.py:32:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
33 | encoding = "latin"
|
||||
34 | "foo".encode(encoding)
|
||||
|
|
||||
= help: Remove unnecessary encoding argument
|
||||
= help: Remove unnecessary `encoding` argument
|
||||
|
||||
ℹ Fix
|
||||
29 29 | string.encode("utf-8")
|
||||
@@ -247,7 +247,7 @@ UP012.py:32:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
34 34 | "foo".encode(encoding)
|
||||
35 35 | f"foo{bar}".encode(encoding)
|
||||
|
||||
UP012.py:36:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
UP012.py:36:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
34 | "foo".encode(encoding)
|
||||
35 | f"foo{bar}".encode(encoding)
|
||||
@@ -258,7 +258,7 @@ UP012.py:36:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
39 |
|
||||
40 | # `encode` with custom args and kwargs should not be processed.
|
||||
|
|
||||
= help: Remove unnecessary encoding argument
|
||||
= help: Remove unnecessary `encoding` argument
|
||||
|
||||
ℹ Fix
|
||||
33 33 | encoding = "latin"
|
||||
@@ -272,7 +272,7 @@ UP012.py:36:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
40 38 | # `encode` with custom args and kwargs should not be processed.
|
||||
41 39 | "foo".encode("utf-8", errors="replace")
|
||||
|
||||
UP012.py:53:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
UP012.py:53:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
52 | # Unicode literals should only be stripped of default encoding.
|
||||
53 | "unicode text©".encode("utf-8") # "unicode text©".encode()
|
||||
@@ -280,7 +280,7 @@ UP012.py:53:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
54 | "unicode text©".encode()
|
||||
55 | "unicode text©".encode(encoding="UTF8") # "unicode text©".encode()
|
||||
|
|
||||
= help: Remove unnecessary encoding argument
|
||||
= help: Remove unnecessary `encoding` argument
|
||||
|
||||
ℹ Fix
|
||||
50 50 | "unicode text©".encode(encoding="utf-8", errors="replace")
|
||||
@@ -292,7 +292,7 @@ UP012.py:53:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
55 55 | "unicode text©".encode(encoding="UTF8") # "unicode text©".encode()
|
||||
56 56 |
|
||||
|
||||
UP012.py:55:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
UP012.py:55:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
53 | "unicode text©".encode("utf-8") # "unicode text©".encode()
|
||||
54 | "unicode text©".encode()
|
||||
@@ -301,7 +301,7 @@ UP012.py:55:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
56 |
|
||||
57 | r"foo\o".encode("utf-8") # br"foo\o"
|
||||
|
|
||||
= help: Remove unnecessary encoding argument
|
||||
= help: Remove unnecessary `encoding` argument
|
||||
|
||||
ℹ Fix
|
||||
52 52 | # Unicode literals should only be stripped of default encoding.
|
||||
@@ -471,7 +471,7 @@ UP012.py:69:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
74 74 | (f"foo{bar}").encode("utf-8")
|
||||
75 75 | (f"foo{bar}").encode(encoding="utf-8")
|
||||
|
||||
UP012.py:74:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
UP012.py:74:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
72 | )).encode()
|
||||
73 |
|
||||
@@ -480,7 +480,7 @@ UP012.py:74:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
75 | (f"foo{bar}").encode(encoding="utf-8")
|
||||
76 | ("unicode text©").encode("utf-8")
|
||||
|
|
||||
= help: Remove unnecessary encoding argument
|
||||
= help: Remove unnecessary `encoding` argument
|
||||
|
||||
ℹ Fix
|
||||
71 71 | "def"
|
||||
@@ -492,7 +492,7 @@ UP012.py:74:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
76 76 | ("unicode text©").encode("utf-8")
|
||||
77 77 | ("unicode text©").encode(encoding="utf-8")
|
||||
|
||||
UP012.py:75:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
UP012.py:75:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
74 | (f"foo{bar}").encode("utf-8")
|
||||
75 | (f"foo{bar}").encode(encoding="utf-8")
|
||||
@@ -500,7 +500,7 @@ UP012.py:75:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
76 | ("unicode text©").encode("utf-8")
|
||||
77 | ("unicode text©").encode(encoding="utf-8")
|
||||
|
|
||||
= help: Remove unnecessary encoding argument
|
||||
= help: Remove unnecessary `encoding` argument
|
||||
|
||||
ℹ Fix
|
||||
72 72 | )).encode()
|
||||
@@ -511,7 +511,7 @@ UP012.py:75:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
76 76 | ("unicode text©").encode("utf-8")
|
||||
77 77 | ("unicode text©").encode(encoding="utf-8")
|
||||
|
||||
UP012.py:76:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
UP012.py:76:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
74 | (f"foo{bar}").encode("utf-8")
|
||||
75 | (f"foo{bar}").encode(encoding="utf-8")
|
||||
@@ -519,7 +519,7 @@ UP012.py:76:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP012
|
||||
77 | ("unicode text©").encode(encoding="utf-8")
|
||||
|
|
||||
= help: Remove unnecessary encoding argument
|
||||
= help: Remove unnecessary `encoding` argument
|
||||
|
||||
ℹ Fix
|
||||
73 73 |
|
||||
@@ -529,14 +529,14 @@ UP012.py:76:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
76 |+("unicode text©").encode()
|
||||
77 77 | ("unicode text©").encode(encoding="utf-8")
|
||||
|
||||
UP012.py:77:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
||||
UP012.py:77:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
|
||||
|
|
||||
75 | (f"foo{bar}").encode(encoding="utf-8")
|
||||
76 | ("unicode text©").encode("utf-8")
|
||||
77 | ("unicode text©").encode(encoding="utf-8")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP012
|
||||
|
|
||||
= help: Remove unnecessary encoding argument
|
||||
= help: Remove unnecessary `encoding` argument
|
||||
|
||||
ℹ Fix
|
||||
74 74 | (f"foo{bar}").encode("utf-8")
|
||||
|
||||
@@ -69,7 +69,7 @@ UP040.py:14:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of t
|
||||
14 | x: typing.TypeAlias = list[T]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
|
||||
15 |
|
||||
16 | # UP040 bounded generic (todo)
|
||||
16 | # UP040 bounded generic
|
||||
|
|
||||
= help: Use the `type` keyword
|
||||
|
||||
@@ -80,153 +80,154 @@ UP040.py:14:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of t
|
||||
14 |-x: typing.TypeAlias = list[T]
|
||||
14 |+type x[T] = list[T]
|
||||
15 15 |
|
||||
16 16 | # UP040 bounded generic (todo)
|
||||
16 16 | # UP040 bounded generic
|
||||
17 17 | T = typing.TypeVar("T", bound=int)
|
||||
|
||||
UP040.py:18:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
|
|
||||
16 | # UP040 bounded generic (todo)
|
||||
16 | # UP040 bounded generic
|
||||
17 | T = typing.TypeVar("T", bound=int)
|
||||
18 | x: typing.TypeAlias = list[T]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
|
||||
19 |
|
||||
20 | T = typing.TypeVar("T", int, str)
|
||||
20 | # UP040 constrained generic
|
||||
|
|
||||
= help: Use the `type` keyword
|
||||
|
||||
ℹ Fix
|
||||
15 15 |
|
||||
16 16 | # UP040 bounded generic (todo)
|
||||
16 16 | # UP040 bounded generic
|
||||
17 17 | T = typing.TypeVar("T", bound=int)
|
||||
18 |-x: typing.TypeAlias = list[T]
|
||||
18 |+type x = list[T]
|
||||
18 |+type x[T: int] = list[T]
|
||||
19 19 |
|
||||
20 20 | T = typing.TypeVar("T", int, str)
|
||||
21 21 | x: typing.TypeAlias = list[T]
|
||||
20 20 | # UP040 constrained generic
|
||||
21 21 | T = typing.TypeVar("T", int, str)
|
||||
|
||||
UP040.py:21:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
UP040.py:22:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
|
|
||||
20 | T = typing.TypeVar("T", int, str)
|
||||
21 | x: typing.TypeAlias = list[T]
|
||||
20 | # UP040 constrained generic
|
||||
21 | T = typing.TypeVar("T", int, str)
|
||||
22 | x: typing.TypeAlias = list[T]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
|
||||
22 |
|
||||
23 | # UP040 contravariant generic (todo)
|
||||
23 |
|
||||
24 | # UP040 contravariant generic
|
||||
|
|
||||
= help: Use the `type` keyword
|
||||
|
||||
ℹ Fix
|
||||
18 18 | x: typing.TypeAlias = list[T]
|
||||
19 19 |
|
||||
20 20 | T = typing.TypeVar("T", int, str)
|
||||
21 |-x: typing.TypeAlias = list[T]
|
||||
21 |+type x = list[T]
|
||||
22 22 |
|
||||
23 23 | # UP040 contravariant generic (todo)
|
||||
24 24 | T = typing.TypeVar("T", contravariant=True)
|
||||
20 20 | # UP040 constrained generic
|
||||
21 21 | T = typing.TypeVar("T", int, str)
|
||||
22 |-x: typing.TypeAlias = list[T]
|
||||
22 |+type x[T: (int, str)] = list[T]
|
||||
23 23 |
|
||||
24 24 | # UP040 contravariant generic
|
||||
25 25 | T = typing.TypeVar("T", contravariant=True)
|
||||
|
||||
UP040.py:25:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
UP040.py:26:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
|
|
||||
23 | # UP040 contravariant generic (todo)
|
||||
24 | T = typing.TypeVar("T", contravariant=True)
|
||||
25 | x: typing.TypeAlias = list[T]
|
||||
24 | # UP040 contravariant generic
|
||||
25 | T = typing.TypeVar("T", contravariant=True)
|
||||
26 | x: typing.TypeAlias = list[T]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
|
||||
26 |
|
||||
27 | # UP040 covariant generic (todo)
|
||||
27 |
|
||||
28 | # UP040 covariant generic
|
||||
|
|
||||
= help: Use the `type` keyword
|
||||
|
||||
ℹ Fix
|
||||
22 22 |
|
||||
23 23 | # UP040 contravariant generic (todo)
|
||||
24 24 | T = typing.TypeVar("T", contravariant=True)
|
||||
25 |-x: typing.TypeAlias = list[T]
|
||||
25 |+type x = list[T]
|
||||
26 26 |
|
||||
27 27 | # UP040 covariant generic (todo)
|
||||
28 28 | T = typing.TypeVar("T", covariant=True)
|
||||
23 23 |
|
||||
24 24 | # UP040 contravariant generic
|
||||
25 25 | T = typing.TypeVar("T", contravariant=True)
|
||||
26 |-x: typing.TypeAlias = list[T]
|
||||
26 |+type x[T] = list[T]
|
||||
27 27 |
|
||||
28 28 | # UP040 covariant generic
|
||||
29 29 | T = typing.TypeVar("T", covariant=True)
|
||||
|
||||
UP040.py:29:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
UP040.py:30:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
|
|
||||
27 | # UP040 covariant generic (todo)
|
||||
28 | T = typing.TypeVar("T", covariant=True)
|
||||
29 | x: typing.TypeAlias = list[T]
|
||||
28 | # UP040 covariant generic
|
||||
29 | T = typing.TypeVar("T", covariant=True)
|
||||
30 | x: typing.TypeAlias = list[T]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
|
||||
30 |
|
||||
31 | # UP040 in class scope
|
||||
31 |
|
||||
32 | # UP040 in class scope
|
||||
|
|
||||
= help: Use the `type` keyword
|
||||
|
||||
ℹ Fix
|
||||
26 26 |
|
||||
27 27 | # UP040 covariant generic (todo)
|
||||
28 28 | T = typing.TypeVar("T", covariant=True)
|
||||
29 |-x: typing.TypeAlias = list[T]
|
||||
29 |+type x = list[T]
|
||||
30 30 |
|
||||
31 31 | # UP040 in class scope
|
||||
32 32 | T = typing.TypeVar["T"]
|
||||
27 27 |
|
||||
28 28 | # UP040 covariant generic
|
||||
29 29 | T = typing.TypeVar("T", covariant=True)
|
||||
30 |-x: typing.TypeAlias = list[T]
|
||||
30 |+type x[T] = list[T]
|
||||
31 31 |
|
||||
32 32 | # UP040 in class scope
|
||||
33 33 | T = typing.TypeVar["T"]
|
||||
|
||||
UP040.py:35:5: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
UP040.py:36:5: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
|
|
||||
33 | class Foo:
|
||||
34 | # reference to global variable
|
||||
35 | x: typing.TypeAlias = list[T]
|
||||
34 | class Foo:
|
||||
35 | # reference to global variable
|
||||
36 | x: typing.TypeAlias = list[T]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
|
||||
36 |
|
||||
37 | # reference to class variable
|
||||
37 |
|
||||
38 | # reference to class variable
|
||||
|
|
||||
= help: Use the `type` keyword
|
||||
|
||||
ℹ Fix
|
||||
32 32 | T = typing.TypeVar["T"]
|
||||
33 33 | class Foo:
|
||||
34 34 | # reference to global variable
|
||||
35 |- x: typing.TypeAlias = list[T]
|
||||
35 |+ type x[T] = list[T]
|
||||
36 36 |
|
||||
37 37 | # reference to class variable
|
||||
38 38 | TCLS = typing.TypeVar["TCLS"]
|
||||
33 33 | T = typing.TypeVar["T"]
|
||||
34 34 | class Foo:
|
||||
35 35 | # reference to global variable
|
||||
36 |- x: typing.TypeAlias = list[T]
|
||||
36 |+ type x[T] = list[T]
|
||||
37 37 |
|
||||
38 38 | # reference to class variable
|
||||
39 39 | TCLS = typing.TypeVar["TCLS"]
|
||||
|
||||
UP040.py:39:5: UP040 [*] Type alias `y` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
UP040.py:40:5: UP040 [*] Type alias `y` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
|
|
||||
37 | # reference to class variable
|
||||
38 | TCLS = typing.TypeVar["TCLS"]
|
||||
39 | y: typing.TypeAlias = list[TCLS]
|
||||
38 | # reference to class variable
|
||||
39 | TCLS = typing.TypeVar["TCLS"]
|
||||
40 | y: typing.TypeAlias = list[TCLS]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
|
||||
40 |
|
||||
41 | # UP040 wont add generics in fix
|
||||
41 |
|
||||
42 | # UP040 wont add generics in fix
|
||||
|
|
||||
= help: Use the `type` keyword
|
||||
|
||||
ℹ Fix
|
||||
36 36 |
|
||||
37 37 | # reference to class variable
|
||||
38 38 | TCLS = typing.TypeVar["TCLS"]
|
||||
39 |- y: typing.TypeAlias = list[TCLS]
|
||||
39 |+ type y[TCLS] = list[TCLS]
|
||||
40 40 |
|
||||
41 41 | # UP040 wont add generics in fix
|
||||
42 42 | T = typing.TypeVar(*args)
|
||||
37 37 |
|
||||
38 38 | # reference to class variable
|
||||
39 39 | TCLS = typing.TypeVar["TCLS"]
|
||||
40 |- y: typing.TypeAlias = list[TCLS]
|
||||
40 |+ type y[TCLS] = list[TCLS]
|
||||
41 41 |
|
||||
42 42 | # UP040 wont add generics in fix
|
||||
43 43 | T = typing.TypeVar(*args)
|
||||
|
||||
UP040.py:43:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
UP040.py:44:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
||||
|
|
||||
41 | # UP040 wont add generics in fix
|
||||
42 | T = typing.TypeVar(*args)
|
||||
43 | x: typing.TypeAlias = list[T]
|
||||
42 | # UP040 wont add generics in fix
|
||||
43 | T = typing.TypeVar(*args)
|
||||
44 | x: typing.TypeAlias = list[T]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
|
||||
44 |
|
||||
45 | # OK
|
||||
45 |
|
||||
46 | # OK
|
||||
|
|
||||
= help: Use the `type` keyword
|
||||
|
||||
ℹ Fix
|
||||
40 40 |
|
||||
41 41 | # UP040 wont add generics in fix
|
||||
42 42 | T = typing.TypeVar(*args)
|
||||
43 |-x: typing.TypeAlias = list[T]
|
||||
43 |+type x = list[T]
|
||||
44 44 |
|
||||
45 45 | # OK
|
||||
46 46 | x: TypeAlias
|
||||
41 41 |
|
||||
42 42 | # UP040 wont add generics in fix
|
||||
43 43 | T = typing.TypeVar(*args)
|
||||
44 |-x: typing.TypeAlias = list[T]
|
||||
44 |+type x = list[T]
|
||||
45 45 |
|
||||
46 46 | # OK
|
||||
47 47 | x: TypeAlias
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `set#remove` that can be replaced with `set#discard`.
|
||||
/// Checks for uses of `set.remove` that can be replaced with `set.discard`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// If an element should be removed from a set if it is present, it is more
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::rules::refurb::helpers::generate_method_call;
|
||||
/// Checks for unbounded slice expressions to copy a list.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The `list#copy` method is more readable and consistent with copying other
|
||||
/// The `list.copy` method is more readable and consistent with copying other
|
||||
/// types.
|
||||
///
|
||||
/// ## Known problems
|
||||
|
||||
@@ -50,7 +50,7 @@ ruff_python_parser = { path = "../ruff_python_parser" }
|
||||
codspeed = ["codspeed-criterion-compat"]
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dev-dependencies]
|
||||
mimalloc = "0.1.34"
|
||||
mimalloc = "0.1.39"
|
||||
|
||||
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))'.dev-dependencies]
|
||||
tikv-jemallocator = "0.5.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.289"
|
||||
version = "0.0.290"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -52,7 +52,7 @@ is-macro = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
itoa = { version = "1.0.6" }
|
||||
log = { workspace = true }
|
||||
notify = { version = "5.1.0" }
|
||||
notify = { version = "6.1.1" }
|
||||
path-absolutize = { workspace = true, features = ["once_cell_cache"] }
|
||||
rayon = { version = "1.7.0" }
|
||||
regex = { workspace = true }
|
||||
@@ -79,7 +79,7 @@ test-case = { workspace = true }
|
||||
ureq = { version = "2.6.2", features = [] }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
mimalloc = "0.1.34"
|
||||
mimalloc = "0.1.39"
|
||||
|
||||
[target.'cfg(all(not(target_os = "windows"), not(target_os = "openbsd"), any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "powerpc64")))'.dependencies]
|
||||
tikv-jemallocator = "0.5.0"
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::{command, Parser};
|
||||
use regex::Regex;
|
||||
use ruff::line_width::LineLength;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff::line_width::LineLength;
|
||||
use ruff::logging::LogLevel;
|
||||
use ruff::registry::Rule;
|
||||
use ruff::settings::types::{
|
||||
FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat,
|
||||
};
|
||||
use ruff::RuleSelector;
|
||||
use ruff::{RuleSelector, RuleSelectorParser};
|
||||
use ruff_workspace::configuration::{Configuration, RuleSelection};
|
||||
use ruff_workspace::resolver::ConfigProcessor;
|
||||
|
||||
@@ -129,7 +128,7 @@ pub struct CheckCommand {
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = parse_rule_selector,
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
@@ -139,7 +138,7 @@ pub struct CheckCommand {
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = parse_rule_selector,
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
@@ -149,7 +148,7 @@ pub struct CheckCommand {
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = parse_rule_selector,
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
@@ -159,7 +158,7 @@ pub struct CheckCommand {
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = parse_rule_selector,
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide = true
|
||||
)]
|
||||
@@ -191,7 +190,7 @@ pub struct CheckCommand {
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = parse_rule_selector,
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
@@ -201,7 +200,7 @@ pub struct CheckCommand {
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = parse_rule_selector,
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
@@ -211,7 +210,7 @@ pub struct CheckCommand {
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = parse_rule_selector,
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide_possible_values = true
|
||||
)]
|
||||
@@ -221,7 +220,7 @@ pub struct CheckCommand {
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
value_name = "RULE_CODE",
|
||||
value_parser = parse_rule_selector,
|
||||
value_parser = RuleSelectorParser,
|
||||
help_heading = "Rule selection",
|
||||
hide = true
|
||||
)]
|
||||
@@ -511,11 +510,6 @@ impl FormatCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_rule_selector(env: &str) -> Result<RuleSelector, std::io::Error> {
|
||||
RuleSelector::from_str(env)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))
|
||||
}
|
||||
|
||||
fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
|
||||
match (yes, no) {
|
||||
(true, false) => Some(true),
|
||||
|
||||
@@ -211,16 +211,16 @@ fn lint_path(
|
||||
match result {
|
||||
Ok(inner) => inner,
|
||||
Err(error) => {
|
||||
let message = r#"This indicates a bug in `ruff`. If you could open an issue at:
|
||||
let message = r#"This indicates a bug in Ruff. If you could open an issue at:
|
||||
|
||||
https://github.com/astral-sh/ruff/issues/new?title=%5BLinter%20panic%5D
|
||||
https://github.com/astral-sh/ruff/issues/new?title=%5BLinter%20panic%5D
|
||||
|
||||
with the relevant file contents, the `pyproject.toml` settings, and the following stack trace, we'd be very appreciative!
|
||||
...with the relevant file contents, the `pyproject.toml` settings, and the following stack trace, we'd be very appreciative!
|
||||
"#;
|
||||
|
||||
warn!(
|
||||
error!(
|
||||
"{}{}{} {message}\n{error}",
|
||||
"Linting panicked ".bold(),
|
||||
"Panicked while linting ".bold(),
|
||||
fs::relativize_path(path).bold(),
|
||||
":".bold()
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
use log::error;
|
||||
use rayon::iter::Either::{Left, Right};
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use thiserror::Error;
|
||||
@@ -22,6 +23,7 @@ use ruff_source_file::{find_newline, LineEnding};
|
||||
use ruff_workspace::resolver::python_files_in_path;
|
||||
|
||||
use crate::args::{FormatArguments, Overrides};
|
||||
use crate::panic::{catch_unwind, PanicError};
|
||||
use crate::resolve::resolve;
|
||||
use crate::ExitStatus;
|
||||
|
||||
@@ -85,7 +87,13 @@ pub(crate) fn format(
|
||||
.with_line_width(LineWidth::from(NonZeroU16::from(line_length)))
|
||||
.with_preview(preview);
|
||||
debug!("Formatting {} with {:?}", path.display(), options);
|
||||
Some(format_path(path, options, mode))
|
||||
|
||||
Some(match catch_unwind(|| format_path(path, options, mode)) {
|
||||
Ok(inner) => inner,
|
||||
Err(error) => {
|
||||
Err(FormatCommandError::Panic(Some(path.to_path_buf()), error))
|
||||
}
|
||||
})
|
||||
}
|
||||
Err(err) => Some(Err(FormatCommandError::Ignore(err))),
|
||||
}
|
||||
@@ -95,22 +103,20 @@ pub(crate) fn format(
|
||||
Err(err) => Right(err),
|
||||
});
|
||||
let duration = start.elapsed();
|
||||
|
||||
debug!(
|
||||
"Formatted {} files in {:.2?}",
|
||||
results.len() + errors.len(),
|
||||
duration
|
||||
);
|
||||
|
||||
let summary = FormatResultSummary::new(results, mode);
|
||||
|
||||
// Report on any errors.
|
||||
if !errors.is_empty() {
|
||||
warn!("Encountered {} errors while formatting:", errors.len());
|
||||
for error in &errors {
|
||||
warn!("{error}");
|
||||
}
|
||||
for error in &errors {
|
||||
error!("{error}");
|
||||
}
|
||||
|
||||
let summary = FormatResultSummary::new(results, mode);
|
||||
|
||||
// Report on the formatting changes.
|
||||
if log_level >= LogLevel::Default {
|
||||
#[allow(clippy::print_stdout)]
|
||||
@@ -255,6 +261,7 @@ pub(crate) enum FormatCommandError {
|
||||
Read(Option<PathBuf>, io::Error),
|
||||
Write(Option<PathBuf>, io::Error),
|
||||
FormatModule(Option<PathBuf>, FormatModuleError),
|
||||
Panic(Option<PathBuf>, PanicError),
|
||||
}
|
||||
|
||||
impl Display for FormatCommandError {
|
||||
@@ -320,6 +327,29 @@ impl Display for FormatCommandError {
|
||||
write!(f, "{}{} {err}", "Failed to format".bold(), ":".bold())
|
||||
}
|
||||
}
|
||||
Self::Panic(path, err) => {
|
||||
let message = r#"This indicates a bug in Ruff. If you could open an issue at:
|
||||
|
||||
https://github.com/astral-sh/ruff/issues/new?title=%5BFormatter%20panic%5D
|
||||
|
||||
...with the relevant file contents, the `pyproject.toml` settings, and the following stack trace, we'd be very appreciative!
|
||||
"#;
|
||||
if let Some(path) = path {
|
||||
write!(
|
||||
f,
|
||||
"{}{}{} {message}\n{err}",
|
||||
"Panicked while formatting ".bold(),
|
||||
fs::relativize_path(path).bold(),
|
||||
":".bold()
|
||||
)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{} {message}\n{err}",
|
||||
"Panicked while formatting.".bold()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,13 +111,15 @@ pub fn run(
|
||||
{
|
||||
eprintln!(
|
||||
r#"
|
||||
{}: `ruff` crashed. This indicates a bug in `ruff`. If you could open an issue at:
|
||||
{}{} {} If you could open an issue at:
|
||||
|
||||
https://github.com/astral-sh/ruff/issues/new?title=%5BPanic%5D
|
||||
https://github.com/astral-sh/ruff/issues/new?title=%5BPanic%5D
|
||||
|
||||
quoting the executed command, along with the relevant file contents and `pyproject.toml` settings, we'd be very appreciative!
|
||||
...quoting the executed command, along with the relevant file contents and `pyproject.toml` settings, we'd be very appreciative!
|
||||
"#,
|
||||
"error".red().bold(),
|
||||
":".bold(),
|
||||
"Ruff crashed.".bold(),
|
||||
);
|
||||
}
|
||||
default_panic_hook(info);
|
||||
|
||||
@@ -341,7 +341,7 @@ fn nursery_group_selector_preview_enabled() {
|
||||
Found 2 errors.
|
||||
|
||||
----- stderr -----
|
||||
warning: The `NURSERY` selector has been deprecated. Use the `PREVIEW` selector instead.
|
||||
warning: The `NURSERY` selector has been deprecated.
|
||||
"###);
|
||||
}
|
||||
|
||||
@@ -439,38 +439,21 @@ fn preview_disabled_prefix_empty() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preview_disabled_group_selector() {
|
||||
// `--select PREVIEW` should warn without the `--preview` flag
|
||||
let args = ["--select", "PREVIEW"];
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(args)
|
||||
.pass_stdin("I=42\n"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: Selection `PREVIEW` has no effect because the `--preview` flag was not included.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn preview_enabled_group_selector() {
|
||||
// `--select PREVIEW` is okay with the `--preview` flag and shouldn't warn
|
||||
fn preview_group_selector() {
|
||||
// `--select PREVIEW` should error (selector was removed)
|
||||
let args = ["--select", "PREVIEW", "--preview"];
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(STDIN_BASE_OPTIONS)
|
||||
.args(args)
|
||||
.pass_stdin("I=42\n"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
-:1:1: CPY001 Missing copyright notice at top of file
|
||||
-:1:2: E225 Missing whitespace around operator
|
||||
Found 2 errors.
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'PREVIEW' for '--select <RULE_CODE>'
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
@@ -483,13 +466,13 @@ fn preview_enabled_group_ignore() {
|
||||
.args(args)
|
||||
.pass_stdin("I=42\n"), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
-:1:1: E741 Ambiguous variable name: `I`
|
||||
-:1:2: E225 Missing whitespace around operator
|
||||
Found 2 errors.
|
||||
|
||||
----- stderr -----
|
||||
error: invalid value 'PREVIEW' for '--ignore <RULE_CODE>'
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
//! An equivalent object hierarchy to the `RustPython` AST hierarchy, but with the
|
||||
//! ability to compare expressions for equality (via [`Eq`] and [`Hash`]).
|
||||
//!
|
||||
//! Two [`ComparableExpr`]s are considered equal if the underlying AST nodes have the
|
||||
//! same shape, ignoring trivia (e.g., parentheses, comments, and whitespace), the
|
||||
//! location in the source code, and other contextual information (e.g., whether they
|
||||
//! represent reads or writes, which is typically encoded in the Python AST).
|
||||
//!
|
||||
//! For example, in `[(a, b) for a, b in c]`, the `(a, b)` and `a, b` expressions are
|
||||
//! considered equal, despite the former being parenthesized, and despite the former
|
||||
//! being a write ([`ast::ExprContext::Store`]) and the latter being a read
|
||||
//! ([`ast::ExprContext::Load`]).
|
||||
//!
|
||||
//! Similarly, `"a" "b"` and `"ab"` would be considered equal, despite the former being
|
||||
//! an implicit concatenation of string literals, as these expressions are considered to
|
||||
//! have the same shape in that they evaluate to the same value.
|
||||
|
||||
use crate as ast;
|
||||
use num_bigint::BigInt;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub enum ComparableExprContext {
|
||||
Load,
|
||||
Store,
|
||||
Del,
|
||||
}
|
||||
|
||||
impl From<&ast::ExprContext> for ComparableExprContext {
|
||||
fn from(ctx: &ast::ExprContext) -> Self {
|
||||
match ctx {
|
||||
ast::ExprContext::Load => Self::Load,
|
||||
ast::ExprContext::Store => Self::Store,
|
||||
ast::ExprContext::Del => Self::Del,
|
||||
}
|
||||
}
|
||||
}
|
||||
use crate as ast;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub enum ComparableBoolOp {
|
||||
@@ -665,38 +663,32 @@ pub struct ExprConstant<'a> {
|
||||
pub struct ExprAttribute<'a> {
|
||||
value: Box<ComparableExpr<'a>>,
|
||||
attr: &'a str,
|
||||
ctx: ComparableExprContext,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ExprSubscript<'a> {
|
||||
value: Box<ComparableExpr<'a>>,
|
||||
slice: Box<ComparableExpr<'a>>,
|
||||
ctx: ComparableExprContext,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ExprStarred<'a> {
|
||||
value: Box<ComparableExpr<'a>>,
|
||||
ctx: ComparableExprContext,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ExprName<'a> {
|
||||
id: &'a str,
|
||||
ctx: ComparableExprContext,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ExprList<'a> {
|
||||
elts: Vec<ComparableExpr<'a>>,
|
||||
ctx: ComparableExprContext,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ExprTuple<'a> {
|
||||
elts: Vec<ComparableExpr<'a>>,
|
||||
ctx: ComparableExprContext,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
@@ -915,50 +907,46 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> {
|
||||
ast::Expr::Attribute(ast::ExprAttribute {
|
||||
value,
|
||||
attr,
|
||||
ctx,
|
||||
ctx: _,
|
||||
range: _,
|
||||
}) => Self::Attribute(ExprAttribute {
|
||||
value: value.into(),
|
||||
attr: attr.as_str(),
|
||||
ctx: ctx.into(),
|
||||
}),
|
||||
ast::Expr::Subscript(ast::ExprSubscript {
|
||||
value,
|
||||
slice,
|
||||
ctx,
|
||||
ctx: _,
|
||||
range: _,
|
||||
}) => Self::Subscript(ExprSubscript {
|
||||
value: value.into(),
|
||||
slice: slice.into(),
|
||||
ctx: ctx.into(),
|
||||
}),
|
||||
ast::Expr::Starred(ast::ExprStarred {
|
||||
value,
|
||||
ctx,
|
||||
ctx: _,
|
||||
range: _,
|
||||
}) => Self::Starred(ExprStarred {
|
||||
value: value.into(),
|
||||
ctx: ctx.into(),
|
||||
}),
|
||||
ast::Expr::Name(ast::ExprName { id, ctx, range: _ }) => Self::Name(ExprName {
|
||||
id: id.as_str(),
|
||||
ctx: ctx.into(),
|
||||
}),
|
||||
ast::Expr::Name(ast::ExprName {
|
||||
id,
|
||||
ctx: _,
|
||||
range: _,
|
||||
}) => Self::Name(ExprName { id: id.as_str() }),
|
||||
ast::Expr::List(ast::ExprList {
|
||||
elts,
|
||||
ctx,
|
||||
ctx: _,
|
||||
range: _,
|
||||
}) => Self::List(ExprList {
|
||||
elts: elts.iter().map(Into::into).collect(),
|
||||
ctx: ctx.into(),
|
||||
}),
|
||||
ast::Expr::Tuple(ast::ExprTuple {
|
||||
elts,
|
||||
ctx,
|
||||
ctx: _,
|
||||
range: _,
|
||||
}) => Self::Tuple(ExprTuple {
|
||||
elts: elts.iter().map(Into::into).collect(),
|
||||
ctx: ctx.into(),
|
||||
}),
|
||||
ast::Expr::Slice(ast::ExprSlice {
|
||||
lower,
|
||||
|
||||
@@ -36,7 +36,7 @@ a.aaaaaaaaaaaaaaaaaaaaa.lllllllllllllllllllllllllllloooooooooong.chaaaaaaaaaaaaa
|
||||
# Test that we add parentheses around the outermost attribute access in an attribute
|
||||
# chain if and only if we need them, that is if there are own line comments inside
|
||||
# the chain.
|
||||
x1 = (
|
||||
x10 = (
|
||||
a
|
||||
.b
|
||||
# comment 1
|
||||
@@ -46,6 +46,27 @@ x1 = (
|
||||
.d
|
||||
)
|
||||
|
||||
x11 = (
|
||||
a
|
||||
.b
|
||||
# comment 1
|
||||
. # comment 2
|
||||
# comment 3
|
||||
c
|
||||
).d
|
||||
|
||||
x12 = (
|
||||
(
|
||||
a
|
||||
.b
|
||||
# comment 1
|
||||
. # comment 2
|
||||
# comment 3
|
||||
c
|
||||
)
|
||||
.d
|
||||
)
|
||||
|
||||
x20 = (
|
||||
a
|
||||
.b
|
||||
@@ -106,7 +127,7 @@ x6 = (
|
||||
a.b
|
||||
)
|
||||
|
||||
# regression: https://github.com/astral-sh/ruff/issues/6181
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/6181
|
||||
(#
|
||||
()).a
|
||||
|
||||
@@ -125,3 +146,9 @@ x6 = (
|
||||
# dangling before dot own-line
|
||||
.b
|
||||
)
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
|
||||
result = (
|
||||
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
|
||||
+ 1
|
||||
).bit_length()
|
||||
|
||||
@@ -206,6 +206,11 @@ if (
|
||||
for user_id in set(target_user_ids) - {u.user_id for u in updates}:
|
||||
updates.append(UserPresenceState.default(user_id))
|
||||
|
||||
# If either operator is parenthesized, use non-simple formatting.
|
||||
# See: https://github.com/astral-sh/ruff/issues/7318.
|
||||
10**(2)
|
||||
10**2
|
||||
|
||||
# Keeps parenthesized left hand sides
|
||||
(
|
||||
log(self.price / self.strike)
|
||||
|
||||
@@ -265,3 +265,8 @@ f( # a
|
||||
kwargs,
|
||||
)
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
|
||||
result = (
|
||||
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
|
||||
+ 1
|
||||
)()
|
||||
|
||||
@@ -91,3 +91,22 @@ f = "f"[:,]
|
||||
g1 = "g"[(1):(2)]
|
||||
g2 = "g"[(1):(2):(3)]
|
||||
|
||||
# Don't omit optional parentheses for subscripts
|
||||
# https://github.com/astral-sh/ruff/issues/7319
|
||||
def f():
|
||||
return (
|
||||
package_version is not None
|
||||
and package_version.split(".")[:2] == package_info.version.split(".")[:2]
|
||||
)
|
||||
|
||||
|
||||
# Group to ensure other arguments don't expand.
|
||||
self.assertEqual(
|
||||
houses.all()[0].occupants.all()[0].houses.all()[1].rooms.all()[0],
|
||||
self.room2_1,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
suite._tests[0].id().split(".")[0],
|
||||
os.path.basename(os.getcwd()),
|
||||
)
|
||||
|
||||
5
crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/subscript.py
vendored
Normal file
5
crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/subscript.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
|
||||
result = (
|
||||
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
|
||||
+ 1
|
||||
)[0]
|
||||
@@ -466,8 +466,11 @@ impl Format<PyFormatContext<'_>> for BinaryLike<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
const fn is_simple_power_expression(left: &Expr, right: &Expr) -> bool {
|
||||
is_simple_power_operand(left) && is_simple_power_operand(right)
|
||||
fn is_simple_power_expression(left: &Expr, right: &Expr, source: &str) -> bool {
|
||||
is_simple_power_operand(left)
|
||||
&& is_simple_power_operand(right)
|
||||
&& !is_expression_parenthesized(left.into(), source)
|
||||
&& !is_expression_parenthesized(right.into(), source)
|
||||
}
|
||||
|
||||
/// Return `true` if an [`Expr`] adheres to [Black's definition](https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#line-breaks-binary-operators)
|
||||
@@ -661,13 +664,17 @@ impl Format<PyFormatContext<'_>> for FlatBinaryExpressionSlice<'_> {
|
||||
&& is_simple_power_expression(
|
||||
left.last_operand().expression(),
|
||||
right.first_operand().expression(),
|
||||
f.context().source(),
|
||||
);
|
||||
|
||||
if let Some(leading) = left.first_operand().leading_binary_comments() {
|
||||
leading_comments(leading).fmt(f)?;
|
||||
}
|
||||
|
||||
in_parentheses_only_group(&left).fmt(f)?;
|
||||
match &left.0 {
|
||||
[OperandOrOperator::Operand(operand)] => operand.fmt(f)?,
|
||||
_ => in_parentheses_only_group(&left).fmt(f)?,
|
||||
}
|
||||
|
||||
if let Some(trailing) = left.last_operand().trailing_binary_comments() {
|
||||
trailing_comments(trailing).fmt(f)?;
|
||||
@@ -708,7 +715,10 @@ impl Format<PyFormatContext<'_>> for FlatBinaryExpressionSlice<'_> {
|
||||
leading_comments(leading).fmt(f)?;
|
||||
}
|
||||
|
||||
in_parentheses_only_group(&right).fmt(f)
|
||||
match &right.0 {
|
||||
[OperandOrOperator::Operand(operand)] => operand.fmt(f),
|
||||
_ => in_parentheses_only_group(&right).fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -150,6 +150,8 @@ impl NeedsParentheses for ExprAttribute {
|
||||
OptionalParentheses::Always
|
||||
} else if self.value.is_name_expr() {
|
||||
OptionalParentheses::BestFit
|
||||
} else if is_expression_parenthesized(self.value.as_ref().into(), context.source()) {
|
||||
OptionalParentheses::Never
|
||||
} else {
|
||||
self.value.needs_parentheses(self.into(), context)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::{Expr, ExprCall};
|
||||
|
||||
use crate::comments::{dangling_comments, SourceComment};
|
||||
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
|
||||
use crate::expression::parentheses::{
|
||||
is_expression_parenthesized, NeedsParentheses, OptionalParentheses,
|
||||
};
|
||||
use crate::expression::CallChainLayout;
|
||||
use crate::prelude::*;
|
||||
|
||||
@@ -86,6 +88,8 @@ impl NeedsParentheses for ExprCall {
|
||||
OptionalParentheses::Multiline
|
||||
} else if context.comments().has_dangling(self) {
|
||||
OptionalParentheses::Always
|
||||
} else if is_expression_parenthesized(self.func.as_ref().into(), context.source()) {
|
||||
OptionalParentheses::Never
|
||||
} else {
|
||||
self.func.needs_parentheses(self.into(), context)
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@ use ruff_python_ast::node::{AnyNodeRef, AstNode};
|
||||
use ruff_python_ast::{Expr, ExprSubscript};
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::context::{NodeLevel, WithNodeLevel};
|
||||
use crate::expression::expr_tuple::TupleParentheses;
|
||||
use crate::expression::parentheses::{parenthesized, NeedsParentheses, OptionalParentheses};
|
||||
use crate::expression::parentheses::{
|
||||
is_expression_parenthesized, parenthesized, NeedsParentheses, OptionalParentheses,
|
||||
};
|
||||
use crate::expression::CallChainLayout;
|
||||
use crate::prelude::*;
|
||||
|
||||
@@ -41,34 +42,34 @@ impl FormatNodeRule<ExprSubscript> for FormatExprSubscript {
|
||||
"A subscript expression can only have a single dangling comment, the one after the bracket"
|
||||
);
|
||||
|
||||
let format_value = format_with(|f| match value.as_ref() {
|
||||
Expr::Attribute(expr) => expr.format().with_options(call_chain_layout).fmt(f),
|
||||
Expr::Call(expr) => expr.format().with_options(call_chain_layout).fmt(f),
|
||||
Expr::Subscript(expr) => expr.format().with_options(call_chain_layout).fmt(f),
|
||||
_ => value.format().fmt(f),
|
||||
});
|
||||
|
||||
if let NodeLevel::Expression(Some(_)) = f.context().node_level() {
|
||||
// Enforce the optional parentheses for parenthesized values.
|
||||
let mut f = WithNodeLevel::new(NodeLevel::Expression(None), f);
|
||||
write!(f, [format_value])?;
|
||||
} else {
|
||||
format_value.fmt(f)?;
|
||||
}
|
||||
|
||||
let format_slice = format_with(|f: &mut PyFormatter| {
|
||||
let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f);
|
||||
|
||||
if let Expr::Tuple(tuple) = slice.as_ref() {
|
||||
write!(f, [tuple.format().with_options(TupleParentheses::Preserve)])
|
||||
} else {
|
||||
write!(f, [slice.format()])
|
||||
let format_inner = format_with(|f| {
|
||||
match value.as_ref() {
|
||||
Expr::Attribute(expr) => expr.format().with_options(call_chain_layout).fmt(f)?,
|
||||
Expr::Call(expr) => expr.format().with_options(call_chain_layout).fmt(f)?,
|
||||
Expr::Subscript(expr) => expr.format().with_options(call_chain_layout).fmt(f)?,
|
||||
_ => value.format().fmt(f)?,
|
||||
}
|
||||
|
||||
let format_slice = format_with(|f: &mut PyFormatter| {
|
||||
if let Expr::Tuple(tuple) = slice.as_ref() {
|
||||
write!(f, [tuple.format().with_options(TupleParentheses::Preserve)])
|
||||
} else {
|
||||
write!(f, [slice.format()])
|
||||
}
|
||||
});
|
||||
|
||||
parenthesized("[", &format_slice, "]")
|
||||
.with_dangling_comments(dangling_comments)
|
||||
.fmt(f)
|
||||
});
|
||||
|
||||
parenthesized("[", &format_slice, "]")
|
||||
.with_dangling_comments(dangling_comments)
|
||||
.fmt(f)
|
||||
let is_call_chain_root = self.call_chain_layout == CallChainLayout::Default
|
||||
&& call_chain_layout == CallChainLayout::Fluent;
|
||||
if is_call_chain_root {
|
||||
write!(f, [group(&format_inner)])
|
||||
} else {
|
||||
write!(f, [format_inner])
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_dangling_comments(
|
||||
@@ -92,6 +93,8 @@ impl NeedsParentheses for ExprSubscript {
|
||||
== CallChainLayout::Fluent
|
||||
{
|
||||
OptionalParentheses::Multiline
|
||||
} else if is_expression_parenthesized(self.value.as_ref().into(), context.source()) {
|
||||
OptionalParentheses::Never
|
||||
} else {
|
||||
match self.value.needs_parentheses(self.into(), context) {
|
||||
OptionalParentheses::BestFit => OptionalParentheses::Never,
|
||||
|
||||
@@ -385,14 +385,21 @@ fn can_omit_optional_parentheses(expr: &Expr, context: &PyFormatContext) -> bool
|
||||
// Only use the more complex IR when there is any expression that we can possibly split by
|
||||
false
|
||||
} else {
|
||||
fn is_parenthesized(expr: &Expr, context: &PyFormatContext) -> bool {
|
||||
// Don't break subscripts except in parenthesized context. It looks weird.
|
||||
!matches!(expr, Expr::Subscript(_))
|
||||
&& has_parentheses(expr, context).is_some_and(OwnParentheses::is_non_empty)
|
||||
}
|
||||
|
||||
// Only use the layout if the first or last expression has parentheses of some sort, and
|
||||
// those parentheses are non-empty.
|
||||
let first_parenthesized = visitor.first.is_some_and(|first| {
|
||||
has_parentheses(first, context).is_some_and(OwnParentheses::is_non_empty)
|
||||
});
|
||||
let last_parenthesized = visitor.last.is_some_and(|last| {
|
||||
has_parentheses(last, context).is_some_and(OwnParentheses::is_non_empty)
|
||||
});
|
||||
let first_parenthesized = visitor
|
||||
.first
|
||||
.is_some_and(|first| is_parenthesized(first, context));
|
||||
let last_parenthesized = visitor
|
||||
.last
|
||||
.is_some_and(|last| is_parenthesized(last, context));
|
||||
|
||||
first_parenthesized || last_parenthesized
|
||||
}
|
||||
}
|
||||
@@ -502,6 +509,7 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> {
|
||||
self.any_parenthesized_expressions = true;
|
||||
// Only walk the function, the subscript is always parenthesized
|
||||
self.visit_expr(value);
|
||||
self.last = Some(expr);
|
||||
// Don't walk the slice, because the slice is always parenthesized.
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ impl From<ParseError> for FormatModuleError {
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level=Level::TRACE, skip_all, err)]
|
||||
#[tracing::instrument(level=Level::TRACE, skip_all)]
|
||||
pub fn format_module(
|
||||
contents: &str,
|
||||
options: PyFormatOptions,
|
||||
|
||||
@@ -275,23 +275,7 @@ last_call()
|
||||
flags & ~select.EPOLLIN and waiters.write_task is not None
|
||||
lambda arg: None
|
||||
lambda a=True: a
|
||||
@@ -39,10 +39,11 @@
|
||||
lambda a, b, c=True, *, d=(1 << v2), e="str": a
|
||||
lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b
|
||||
manylambdas = lambda x=lambda y=lambda z=1: z: y(): x()
|
||||
-foo = lambda port_id, ignore_missing: {
|
||||
- "port1": port1_resource,
|
||||
- "port2": port2_resource,
|
||||
-}[port_id]
|
||||
+foo = (
|
||||
+ lambda port_id, ignore_missing: {"port1": port1_resource, "port2": port2_resource}[
|
||||
+ port_id
|
||||
+ ]
|
||||
+)
|
||||
1 if True else 2
|
||||
str or None if True else str or bytes or None
|
||||
(str or None) if True else (str or bytes or None)
|
||||
@@ -115,7 +116,7 @@
|
||||
@@ -115,7 +115,7 @@
|
||||
arg,
|
||||
another,
|
||||
kwarg="hey",
|
||||
@@ -346,11 +330,10 @@ lambda a, b, c=True: a
|
||||
lambda a, b, c=True, *, d=(1 << v2), e="str": a
|
||||
lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b
|
||||
manylambdas = lambda x=lambda y=lambda z=1: z: y(): x()
|
||||
foo = (
|
||||
lambda port_id, ignore_missing: {"port1": port1_resource, "port2": port2_resource}[
|
||||
port_id
|
||||
]
|
||||
)
|
||||
foo = lambda port_id, ignore_missing: {
|
||||
"port1": port1_resource,
|
||||
"port2": port2_resource,
|
||||
}[port_id]
|
||||
1 if True else 2
|
||||
str or None if True else str or bytes or None
|
||||
(str or None) if True else (str or bytes or None)
|
||||
|
||||
@@ -42,7 +42,7 @@ a.aaaaaaaaaaaaaaaaaaaaa.lllllllllllllllllllllllllllloooooooooong.chaaaaaaaaaaaaa
|
||||
# Test that we add parentheses around the outermost attribute access in an attribute
|
||||
# chain if and only if we need them, that is if there are own line comments inside
|
||||
# the chain.
|
||||
x1 = (
|
||||
x10 = (
|
||||
a
|
||||
.b
|
||||
# comment 1
|
||||
@@ -52,6 +52,27 @@ x1 = (
|
||||
.d
|
||||
)
|
||||
|
||||
x11 = (
|
||||
a
|
||||
.b
|
||||
# comment 1
|
||||
. # comment 2
|
||||
# comment 3
|
||||
c
|
||||
).d
|
||||
|
||||
x12 = (
|
||||
(
|
||||
a
|
||||
.b
|
||||
# comment 1
|
||||
. # comment 2
|
||||
# comment 3
|
||||
c
|
||||
)
|
||||
.d
|
||||
)
|
||||
|
||||
x20 = (
|
||||
a
|
||||
.b
|
||||
@@ -112,7 +133,7 @@ x6 = (
|
||||
a.b
|
||||
)
|
||||
|
||||
# regression: https://github.com/astral-sh/ruff/issues/6181
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/6181
|
||||
(#
|
||||
()).a
|
||||
|
||||
@@ -131,6 +152,12 @@ x6 = (
|
||||
# dangling before dot own-line
|
||||
.b
|
||||
)
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
|
||||
result = (
|
||||
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
|
||||
+ 1
|
||||
).bit_length()
|
||||
```
|
||||
|
||||
## Output
|
||||
@@ -173,7 +200,7 @@ a.aaaaaaaaaaaaaaaaaaaaa.lllllllllllllllllllllllllllloooooooooong.chaaaaaaaaaaaaa
|
||||
# Test that we add parentheses around the outermost attribute access in an attribute
|
||||
# chain if and only if we need them, that is if there are own line comments inside
|
||||
# the chain.
|
||||
x1 = (
|
||||
x10 = (
|
||||
a.b
|
||||
# comment 1
|
||||
. # comment 2
|
||||
@@ -181,6 +208,22 @@ x1 = (
|
||||
c.d
|
||||
)
|
||||
|
||||
x11 = (
|
||||
a.b
|
||||
# comment 1
|
||||
. # comment 2
|
||||
# comment 3
|
||||
c
|
||||
).d
|
||||
|
||||
x12 = (
|
||||
a.b
|
||||
# comment 1
|
||||
. # comment 2
|
||||
# comment 3
|
||||
c
|
||||
).d
|
||||
|
||||
x20 = a.b
|
||||
x21 = (
|
||||
# leading name own line
|
||||
@@ -227,7 +270,7 @@ x6 = (
|
||||
a.b
|
||||
)
|
||||
|
||||
# regression: https://github.com/astral-sh/ruff/issues/6181
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/6181
|
||||
( #
|
||||
()
|
||||
).a
|
||||
@@ -245,6 +288,12 @@ x6 = (
|
||||
# dangling before dot own-line
|
||||
.b
|
||||
)
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
|
||||
result = (
|
||||
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
|
||||
+ 1
|
||||
).bit_length()
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -212,6 +212,11 @@ if (
|
||||
for user_id in set(target_user_ids) - {u.user_id for u in updates}:
|
||||
updates.append(UserPresenceState.default(user_id))
|
||||
|
||||
# If either operator is parenthesized, use non-simple formatting.
|
||||
# See: https://github.com/astral-sh/ruff/issues/7318.
|
||||
10**(2)
|
||||
10**2
|
||||
|
||||
# Keeps parenthesized left hand sides
|
||||
(
|
||||
log(self.price / self.strike)
|
||||
@@ -657,6 +662,11 @@ if (
|
||||
for user_id in set(target_user_ids) - {u.user_id for u in updates}:
|
||||
updates.append(UserPresenceState.default(user_id))
|
||||
|
||||
# If either operator is parenthesized, use non-simple formatting.
|
||||
# See: https://github.com/astral-sh/ruff/issues/7318.
|
||||
10 ** (2)
|
||||
10**2
|
||||
|
||||
# Keeps parenthesized left hand sides
|
||||
(
|
||||
log(self.price / self.strike)
|
||||
|
||||
@@ -271,6 +271,11 @@ f( # a
|
||||
kwargs,
|
||||
)
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
|
||||
result = (
|
||||
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
|
||||
+ 1
|
||||
)()
|
||||
```
|
||||
|
||||
## Output
|
||||
@@ -537,6 +542,12 @@ f( # a
|
||||
** # h
|
||||
kwargs,
|
||||
)
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
|
||||
result = (
|
||||
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
|
||||
+ 1
|
||||
)()
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -324,13 +324,13 @@ ct_match = (
|
||||
aaaaaaaaaaact_id == self.get_content_type(obj=rel_obj, using=instance._state.db)[id]
|
||||
)
|
||||
|
||||
ct_match = {
|
||||
aaaaaaaaaaaaaaaa
|
||||
} == self.get_content_type(obj=rel_obj, using=instance._state.db)[id]
|
||||
ct_match = {aaaaaaaaaaaaaaaa} == self.get_content_type(
|
||||
obj=rel_obj, using=instance._state.db
|
||||
)[id]
|
||||
|
||||
ct_match = (
|
||||
aaaaaaaaaaaaaaaa
|
||||
) == self.get_content_type(obj=rel_obj, using=instance._state.db)[id]
|
||||
ct_match = (aaaaaaaaaaaaaaaa) == self.get_content_type(
|
||||
obj=rel_obj, using=instance._state.db
|
||||
)[id]
|
||||
|
||||
# Subscripts expressions with trailing attributes.
|
||||
|
||||
|
||||
@@ -97,6 +97,25 @@ f = "f"[:,]
|
||||
g1 = "g"[(1):(2)]
|
||||
g2 = "g"[(1):(2):(3)]
|
||||
|
||||
# Don't omit optional parentheses for subscripts
|
||||
# https://github.com/astral-sh/ruff/issues/7319
|
||||
def f():
|
||||
return (
|
||||
package_version is not None
|
||||
and package_version.split(".")[:2] == package_info.version.split(".")[:2]
|
||||
)
|
||||
|
||||
|
||||
# Group to ensure other arguments don't expand.
|
||||
self.assertEqual(
|
||||
houses.all()[0].occupants.all()[0].houses.all()[1].rooms.all()[0],
|
||||
self.room2_1,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
suite._tests[0].id().split(".")[0],
|
||||
os.path.basename(os.getcwd()),
|
||||
)
|
||||
```
|
||||
|
||||
## Output
|
||||
@@ -191,6 +210,27 @@ f = "f"[:,]
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/5733
|
||||
g1 = "g"[(1):(2)]
|
||||
g2 = "g"[(1):(2):(3)]
|
||||
|
||||
|
||||
# Don't omit optional parentheses for subscripts
|
||||
# https://github.com/astral-sh/ruff/issues/7319
|
||||
def f():
|
||||
return (
|
||||
package_version is not None
|
||||
and package_version.split(".")[:2] == package_info.version.split(".")[:2]
|
||||
)
|
||||
|
||||
|
||||
# Group to ensure other arguments don't expand.
|
||||
self.assertEqual(
|
||||
houses.all()[0].occupants.all()[0].houses.all()[1].rooms.all()[0],
|
||||
self.room2_1,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
suite._tests[0].id().split(".")[0],
|
||||
os.path.basename(os.getcwd()),
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/subscript.py
|
||||
---
|
||||
## Input
|
||||
```py
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
|
||||
result = (
|
||||
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
|
||||
+ 1
|
||||
)[0]
|
||||
```
|
||||
|
||||
## Output
|
||||
```py
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
|
||||
result = (
|
||||
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
|
||||
+ 1
|
||||
)[0]
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -184,7 +184,7 @@ pub(crate) fn function_visibility(function: &ast::StmtFunctionDef) -> Visibility
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn method_visibility(function: &ast::StmtFunctionDef) -> Visibility {
|
||||
pub fn method_visibility(function: &ast::StmtFunctionDef) -> Visibility {
|
||||
// Is this a setter or deleter?
|
||||
if function.decorator_list.iter().any(|decorator| {
|
||||
collect_call_path(&decorator.expression).is_some_and(|call_path| {
|
||||
|
||||
@@ -545,7 +545,7 @@ impl<'a> SemanticModel<'a> {
|
||||
///
|
||||
/// For example, given `["Class", "method"`], resolve the `BindingKind::ClassDefinition`
|
||||
/// associated with `Class`, then the `BindingKind::FunctionDefinition` associated with
|
||||
/// `Class#method`.
|
||||
/// `Class.method`.
|
||||
pub fn lookup_attribute(&'a self, value: &'a Expr) -> Option<BindingId> {
|
||||
let call_path = collect_call_path(value)?;
|
||||
|
||||
|
||||
@@ -13,6 +13,6 @@ regex = { workspace = true }
|
||||
ruff_python_ast = { path = "../ruff_python_ast" }
|
||||
ruff_python_parser = { path = "../ruff_python_parser" }
|
||||
ruff_text_size = { path = "../ruff_text_size" }
|
||||
shlex = "1.1.0"
|
||||
shlex = "1.2.0"
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||
|
||||
@@ -391,7 +391,7 @@ impl<'a> Locator<'a> {
|
||||
/// Finds the closest [`TextSize`] not exceeding the offset for which `is_char_boundary` is
|
||||
/// `true`.
|
||||
///
|
||||
/// Can be replaced with `str#floor_char_boundary` once it's stable.
|
||||
/// Can be replaced with `str::floor_char_boundary` once it's stable.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
|
||||
@@ -15,8 +15,8 @@ impl UniversalNewlines for str {
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [`str#lines`], but accommodates LF, CRLF, and CR line endings,
|
||||
/// the latter of which are not supported by [`str#lines`].
|
||||
/// Like [`str::lines`], but accommodates LF, CRLF, and CR line endings,
|
||||
/// the latter of which are not supported by [`str::lines`].
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
|
||||
@@ -35,7 +35,7 @@ console_log = { version = "1.0.0" }
|
||||
log = { workspace = true }
|
||||
|
||||
serde = { workspace = true }
|
||||
serde-wasm-bindgen = { version = "0.5.0" }
|
||||
serde-wasm-bindgen = { version = "0.6.0" }
|
||||
wasm-bindgen = { version = "0.2.84" }
|
||||
js-sys = { version = "0.3.61" }
|
||||
|
||||
|
||||
@@ -588,11 +588,12 @@ impl Configuration {
|
||||
#[allow(deprecated)]
|
||||
if matches!(selector, RuleSelector::Nursery) {
|
||||
let suggestion = if preview.is_disabled() {
|
||||
"Use the `--preview` flag instead."
|
||||
" Use the `--preview` flag instead."
|
||||
} else {
|
||||
"Use the `PREVIEW` selector instead."
|
||||
// We have no suggested alternative since there is intentionally no "PREVIEW" selector
|
||||
""
|
||||
};
|
||||
warn_user_once!("The `NURSERY` selector has been deprecated. {suggestion}");
|
||||
warn_user_once!("The `NURSERY` selector has been deprecated.{suggestion}");
|
||||
}
|
||||
|
||||
if preview.is_disabled() {
|
||||
@@ -851,7 +852,14 @@ mod tests {
|
||||
Rule::QuadraticListSummation,
|
||||
];
|
||||
|
||||
const PREVIEW_RULES: &[Rule] = &[Rule::SliceCopy];
|
||||
const PREVIEW_RULES: &[Rule] = &[
|
||||
Rule::DirectLoggerInstantiation,
|
||||
Rule::ManualDictComprehension,
|
||||
Rule::SliceCopy,
|
||||
Rule::TooManyPublicMethods,
|
||||
Rule::TooManyPublicMethods,
|
||||
Rule::UndocumentedWarn,
|
||||
];
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn resolve_rules(
|
||||
@@ -1092,6 +1100,27 @@ mod tests {
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_all_preview() {
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![RuleSelector::All]),
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
Some(PreviewMode::Disabled),
|
||||
);
|
||||
assert!(!actual.intersects(&RuleSet::from_rules(PREVIEW_RULES)));
|
||||
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![RuleSelector::All]),
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
Some(PreviewMode::Enabled),
|
||||
);
|
||||
assert!(actual.intersects(&RuleSet::from_rules(PREVIEW_RULES)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_linter_preview() {
|
||||
let actual = resolve_rules(
|
||||
@@ -1161,31 +1190,6 @@ mod tests {
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn select_preview() {
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![RuleSelector::Preview]),
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
Some(PreviewMode::Disabled),
|
||||
);
|
||||
let expected = RuleSet::empty();
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
let actual = resolve_rules(
|
||||
[RuleSelection {
|
||||
select: Some(vec![RuleSelector::Preview]),
|
||||
..RuleSelection::default()
|
||||
}],
|
||||
Some(PreviewMode::Enabled),
|
||||
);
|
||||
|
||||
let expected =
|
||||
RuleSet::from_rules(NURSERY_RULES).union(&RuleSet::from_rules(PREVIEW_RULES));
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nursery_select_code() {
|
||||
// Backwards compatible behavior allows selection of nursery rules with their exact code
|
||||
|
||||
@@ -319,7 +319,8 @@ pub struct Options {
|
||||
"#
|
||||
)]
|
||||
/// The line length to use when enforcing long-lines violations (like
|
||||
/// `E501`). Must be greater than `0`.
|
||||
/// `E501`). Must be greater than `0` and less than or equal to `320`.
|
||||
#[cfg_attr(feature = "schemars", schemars(range(min = 1, max = 320)))]
|
||||
pub line_length: Option<LineLength>,
|
||||
#[option(
|
||||
default = "4",
|
||||
@@ -2155,6 +2156,13 @@ pub struct PylintOptions {
|
||||
/// Maximum number of statements allowed for a function or method body (see:
|
||||
/// `PLR0915`).
|
||||
pub max_statements: Option<usize>,
|
||||
#[option(
|
||||
default = r"20",
|
||||
value_type = "int",
|
||||
example = r"max-public-methods = 20"
|
||||
)]
|
||||
/// Maximum number of public methods allowed for a class (see: `PLR0904`).
|
||||
pub max_public_methods: Option<usize>,
|
||||
}
|
||||
|
||||
impl PylintOptions {
|
||||
@@ -2168,6 +2176,9 @@ impl PylintOptions {
|
||||
max_returns: self.max_returns.unwrap_or(defaults.max_returns),
|
||||
max_branches: self.max_branches.unwrap_or(defaults.max_branches),
|
||||
max_statements: self.max_statements.unwrap_or(defaults.max_statements),
|
||||
max_public_methods: self
|
||||
.max_public_methods
|
||||
.unwrap_or(defaults.max_public_methods),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,8 +394,9 @@ i = 1 # noqa: E741, F841
|
||||
x = 1 # noqa
|
||||
```
|
||||
|
||||
Note that, for multi-line strings, the `noqa` directive should come at the end of the string, and
|
||||
will apply to the entire string, like so:
|
||||
For multi-line strings (like docstrings),
|
||||
the `noqa` directive should come at the end of the string (after the closing triple quote),
|
||||
and will apply to the entire string, like so:
|
||||
|
||||
```python
|
||||
"""Lorem ipsum dolor sit amet.
|
||||
@@ -475,7 +476,7 @@ Ruff supports two command-line flags that alter its exit code behavior:
|
||||
`--exit-non-zero-on-fix` can result in a non-zero exit code even if no violations remain after
|
||||
autofixing.
|
||||
|
||||
## Autocompletion
|
||||
## Shell autocompletion
|
||||
|
||||
Ruff supports autocompletion for most shells. A shell-specific completion script can be generated
|
||||
by `ruff generate-shell-completion <SHELL>`, where `<SHELL>` is one of `bash`, `elvish`, `fig`, `fish`,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user