Compare commits

..

6 Commits

Author SHA1 Message Date
Zanie
1880cb45df Commit test rules :) 2023-09-13 16:17:30 -05:00
Zanie
df8a3db939 Update integration tests to use test rules 2023-09-13 16:00:30 -05:00
Zanie
595207b853 Swap the order of nursery and preview rules 2023-09-13 15:49:55 -05:00
Zanie
97654a9911 Fix invocations 2023-09-13 15:48:02 -05:00
Zanie
eccda3d3ed Use feature instead of test cfg 2023-09-13 15:33:36 -05:00
Zanie
daa9b4db72 WIP: Add rules only enabled during testing
Failing with... the trait `std::convert::From<codes::Rule>` is not implemented for `DiagnosticKind`
2023-09-13 15:33:36 -05:00
107 changed files with 829 additions and 2060 deletions

View File

@@ -366,31 +366,3 @@ 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
View File

@@ -221,7 +221,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c2f7349907b712260e64b0afe2f84692af14a454be26187d9df565c7f69266a"
dependencies = [
"memchr",
"regex-automata 0.3.8",
"regex-automata 0.3.7",
"serde",
]
@@ -272,14 +272,15 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.30"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877"
checksum = "95ed24df0632f708f5f6d8082675bef2596f7084dee3dd55f632290bf35bfe0f"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"time",
"wasm-bindgen",
"windows-targets 0.48.5",
]
@@ -313,19 +314,20 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.3"
version = "4.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6"
checksum = "7c8d502cbaec4595d2e7d5f61e318f05417bd2b66fdc3809498f0d3fdf0bea27"
dependencies = [
"clap_builder",
"clap_derive",
"once_cell",
]
[[package]]
name = "clap_builder"
version = "4.4.2"
version = "4.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
checksum = "5891c7bc0edb3e1c2204fc5e94009affabeb1821c9e5fdc3959536c5c0bb984d"
dependencies = [
"anstream",
"anstyle",
@@ -376,14 +378,14 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.4.2"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
checksum = "c9fd1a5729c4548118d7d70ff234a44868d00489a4b6597b0b020918a0e91a1a"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -608,7 +610,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -619,7 +621,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -810,7 +812,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.290"
version = "0.0.289"
dependencies = [
"anyhow",
"clap",
@@ -881,7 +883,7 @@ dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
@@ -1120,7 +1122,7 @@ dependencies = [
"pmutil 0.6.1",
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -1275,9 +1277,9 @@ dependencies = [
[[package]]
name = "libmimalloc-sys"
version = "0.1.35"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3979b5c37ece694f1f5e51e7ecc871fdb0f517ed04ee45f88d15d6d553cb9664"
checksum = "25d058a81af0d1c22d7a1c948576bee6d673f7af3c0f35564abd6c81122f513d"
dependencies = [
"cc",
"libc",
@@ -1343,9 +1345,9 @@ dependencies = [
[[package]]
name = "mimalloc"
version = "0.1.39"
version = "0.1.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa01922b5ea280a911e323e4d2fd24b7fe5cc4042e0d2cda3c40775cdc4bdc9c"
checksum = "972e5f23f6716f62665760b0f4cbf592576a80c7b879ba9beaafc0e558894127"
dependencies = [
"libmimalloc-sys",
]
@@ -1373,7 +1375,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
"libc",
"log",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.48.0",
]
@@ -1418,21 +1420,20 @@ dependencies = [
[[package]]
name = "notify"
version = "6.1.1"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486"
dependencies = [
"bitflags 2.4.0",
"bitflags 1.3.2",
"crossbeam-channel",
"filetime",
"fsevent-sys",
"inotify",
"kqueue",
"libc",
"log",
"mio",
"walkdir",
"windows-sys 0.48.0",
"windows-sys 0.45.0",
]
[[package]]
@@ -1720,7 +1721,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -1805,9 +1806,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.67"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
"unicode-ident",
]
@@ -1940,13 +1941,13 @@ dependencies = [
[[package]]
name = "regex"
version = "1.9.5"
version = "1.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.3.8",
"regex-automata 0.3.7",
"regex-syntax 0.7.5",
]
@@ -1961,9 +1962,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.3.8"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629"
dependencies = [
"aho-corasick",
"memchr",
@@ -2021,7 +2022,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.290"
version = "0.0.289"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2119,7 +2120,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.290"
version = "0.0.289"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2259,7 +2260,7 @@ dependencies = [
"proc-macro2",
"quote",
"ruff_python_trivia",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -2673,9 +2674,9 @@ dependencies = [
[[package]]
name = "serde-wasm-bindgen"
version = "0.6.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30c9933e5689bd420dc6c87b7a1835701810cbc10cd86a26e4da45b73e6b1d78"
checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e"
dependencies = [
"js-sys",
"serde",
@@ -2690,7 +2691,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -2752,7 +2753,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -2775,9 +2776,9 @@ dependencies = [
[[package]]
name = "shlex"
version = "1.2.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "similar"
@@ -2847,7 +2848,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -2863,9 +2864,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.33"
version = "2.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668"
checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
dependencies = [
"proc-macro2",
"quote",
@@ -2970,22 +2971,22 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.48"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.48"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -3018,6 +3019,17 @@ 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"
@@ -3054,9 +3066,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "toml"
version = "0.7.8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
dependencies = [
"serde",
"serde_spanned",
@@ -3075,9 +3087,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.19.15"
version = "0.19.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
dependencies = [
"indexmap",
"serde",
@@ -3107,7 +3119,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -3310,7 +3322,7 @@ checksum = "f7e1ba1f333bd65ce3c9f27de592fcbc256dafe3af2717f56d7c87761fbaccf4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -3377,6 +3389,12 @@ 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"
@@ -3404,7 +3422,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
"wasm-bindgen-shared",
]
@@ -3438,7 +3456,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View File

@@ -14,8 +14,8 @@ license = "MIT"
[workspace.dependencies]
anyhow = { version = "1.0.69" }
bitflags = { version = "2.3.1" }
chrono = { version = "0.4.30", default-features = false, features = ["clock"] }
clap = { version = "4.4.3", features = ["derive"] }
chrono = { version = "0.4.23", default-features = false, features = ["clock"] }
clap = { version = "4.1.8", 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.67" }
proc-macro2 = { version = "1.0.51" }
quote = { version = "1.0.23" }
regex = { version = "1.9.5" }
regex = { version = "1.7.1" }
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.33" }
syn = { version = "2.0.15" }
test-case = { version = "3.0.0" }
thiserror = { version = "1.0.48" }
toml = { version = "0.7.8" }
thiserror = { version = "1.0.43" }
toml = { version = "0.7.2" }
tracing = "0.1.37"
tracing-indicatif = "0.3.4"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }

25
LICENSE
View File

@@ -1224,31 +1224,6 @@ 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

View File

@@ -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.290
rev: v0.0.289
hooks:
- id: ruff
```
@@ -274,7 +274,6 @@ 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/)

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.290"
version = "0.0.289"
description = """
Convert Flake8 configuration files to Ruff configuration files.
"""

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.290"
version = "0.0.289"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -89,3 +89,5 @@ default = []
schemars = ["dep:schemars"]
# Enables the UnreachableCode rule
unreachable-code = []
# Enables rules for internal integration tests
test-rules = []

View File

@@ -7,8 +7,6 @@ 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)))

View File

@@ -7,8 +7,6 @@ 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]

View File

@@ -1,5 +0,0 @@
import logging
logging.Logger(__name__)
logging.Logger()
logging.getLogger(__name__)

View File

@@ -1,9 +0,0 @@
import logging
logging.WARN # LOG009
logging.WARNING # OK
from logging import WARN, WARNING
WARN # LOG009
WARNING # OK

View File

@@ -1,83 +0,0 @@
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

View File

@@ -658,8 +658,3 @@ 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"

View File

@@ -1,60 +0,0 @@
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

View File

@@ -13,19 +13,18 @@ x: typing.TypeAlias = list[T]
T = typing.TypeVar("T")
x: typing.TypeAlias = list[T]
# UP040 bounded generic
# UP040 bounded generic (todo)
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
# UP040 contravariant generic (todo)
T = typing.TypeVar("T", contravariant=True)
x: typing.TypeAlias = list[T]
# UP040 covariant generic
# UP040 covariant generic (todo)
T = typing.TypeVar("T", covariant=True)
x: typing.TypeAlias = list[T]

View File

@@ -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,
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_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,9 +260,6 @@ 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);
}
@@ -329,9 +326,6 @@ 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(
@@ -892,9 +886,6 @@ 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,

View File

@@ -411,13 +411,6 @@ 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);
}
@@ -1184,7 +1177,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
iter,
orelse,
is_async,
range: _,
..
},
) => {
if checker.any_enabled(&[
@@ -1218,9 +1211,6 @@ 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);
}

View File

@@ -269,7 +269,6 @@ 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),
@@ -866,6 +865,13 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "017") => (RuleGroup::Nursery, rules::ruff::rules::QuadraticListSummation),
(Ruff, "100") => (RuleGroup::Unspecified, rules::ruff::rules::UnusedNOQA),
(Ruff, "200") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidPyprojectToml),
#[cfg(feature = "test-rules")]
(Ruff, "900") => (RuleGroup::Unspecified, rules::ruff::rules::StableTestRule),
#[cfg(feature = "test-rules")]
(Ruff, "911") => (RuleGroup::Preview, rules::ruff::rules::PreviewTestRule),
#[cfg(feature = "test-rules")]
#[allow(deprecated)]
(Ruff, "912") => (RuleGroup::Nursery, rules::ruff::rules::NurseryTestRule),
// flake8-django
(Flake8Django, "001") => (RuleGroup::Unspecified, rules::flake8_django::rules::DjangoNullableModelStringField),
@@ -898,7 +904,6 @@ 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),
@@ -920,10 +925,6 @@ 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,
})
}

View File

@@ -1,6 +1,4 @@
use libcst_native::{
Expression, Name, NameOrAttribute, ParenthesizableWhitespace, SimpleWhitespace, UnaryOperation,
};
use libcst_native::{Expression, NameOrAttribute, ParenthesizableWhitespace, SimpleWhitespace};
fn compose_call_path_inner<'a>(expr: &'a Expression, parts: &mut Vec<&'a str>) {
match expr {
@@ -52,41 +50,3 @@ 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![],
}))
}

View File

@@ -30,11 +30,6 @@ 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<'_> {

View File

@@ -5,8 +5,6 @@
//!
//! [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};

View File

@@ -8,16 +8,6 @@ use itertools::Itertools;
use log::error;
use rustc_hash::FxHashMap;
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::imports::ImportMap;
use ruff_python_ast::PySourceType;
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_parser::lexer::LexResult;
use ruff_python_parser::{AsMode, ParseError};
use ruff_source_file::{Locator, SourceFileBuilder};
use ruff_text_size::Ranged;
use crate::autofix::{fix_file, FixResult};
use crate::checkers::ast::check_ast;
use crate::checkers::filesystem::check_file_path;
@@ -35,6 +25,18 @@ use crate::rules::pycodestyle;
use crate::settings::{flags, Settings};
use crate::source_kind::SourceKind;
use crate::{directives, fs};
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::imports::ImportMap;
use ruff_python_ast::PySourceType;
use ruff_python_codegen::Stylist;
use ruff_python_index::Indexer;
use ruff_python_parser::lexer::LexResult;
use ruff_python_parser::{AsMode, ParseError};
use ruff_source_file::{Locator, SourceFileBuilder};
use ruff_text_size::Ranged;
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
@@ -210,6 +212,31 @@ pub fn check_path(
));
}
// Raise violations for internal test rules
#[cfg(feature = "test-rules")]
{
if settings.rules.enabled(Rule::StableTestRule) {
diagnostics.push(Diagnostic::new(
crate::rules::ruff::rules::StableTestRule,
ruff_text_size::TextRange::default(),
));
}
if settings.rules.enabled(Rule::PreviewTestRule) {
diagnostics.push(Diagnostic::new(
crate::rules::ruff::rules::PreviewTestRule,
ruff_text_size::TextRange::default(),
));
}
if settings.rules.enabled(Rule::NurseryTestRule) {
diagnostics.push(Diagnostic::new(
crate::rules::ruff::rules::NurseryTestRule,
ruff_text_size::TextRange::default(),
));
}
}
// Ignore diagnostics based on per-file-ignores.
if !diagnostics.is_empty() && !settings.per_file_ignores.is_empty() {
let ignores = fs::ignores_from_path(path, &settings.per_file_ignores);
@@ -540,9 +567,8 @@ 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,
@@ -551,17 +577,18 @@ 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 Ruff. If you could open an issue at:
This indicates a bug in `{}`. If you could open an issue at:
https://github.com/astral-sh/ruff/issues/new?title=%5BInfinite%20loop%5D
{}/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
);
@@ -578,9 +605,8 @@ 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,
@@ -589,16 +615,17 @@ 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 Ruff. If you could open an issue at:
This indicates a bug in `{}`. If you could open an issue at:
https://github.com/astral-sh/ruff/issues/new?title=%5BAutofix%20error%5D
{}/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(),
":".bold(),
CARGO_PKG_NAME,
CARGO_PKG_REPOSITORY,
fs::relativize_path(path),
codes,
);

View File

@@ -199,9 +199,6 @@ 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,

View File

@@ -15,8 +15,10 @@ 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 = "The nursery was replaced with 'preview mode' which has no selector")]
#[deprecated(note = "Use `RuleSelector::Preview` for new rules instead")]
Nursery,
/// Legacy category to select both the `mccabe` and `flake8-comprehensions` linters
/// via a single selector.
@@ -52,6 +54,7 @@ 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),
_ => {
@@ -118,6 +121,7 @@ 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, .. } => {
@@ -181,6 +185,9 @@ 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()
@@ -254,9 +261,8 @@ mod schema {
instance_type: Some(InstanceType::String.into()),
enum_values: Some(
[
// Include the non-standard "ALL" and "NURSERY" selectors.
// Include the non-standard "ALL" selector.
"ALL".to_string(),
"NURSERY".to_string(),
// Include the legacy "C" and "T" selectors.
"C".to_string(),
"T".to_string(),
@@ -295,6 +301,7 @@ 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,
@@ -336,14 +343,13 @@ pub enum Specificity {
}
#[cfg(feature = "clap")]
pub mod clap_completion {
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,
};
@@ -363,29 +369,17 @@ pub 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(|_| {
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
})
value
.parse()
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::InvalidValue, e))
}
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
@@ -400,34 +394,27 @@ pub mod clap_completion {
RuleCodePrefix::iter()
// Filter out rule gated behind `#[cfg(feature = "unreachable-code")]`, which is
// off-by-default
.filter(|prefix| {
format!(
"{}{}",
prefix.linter().common_prefix(),
prefix.short_code()
) != "RUF014"
.filter(|p| {
format!("{}{}", p.linter().common_prefix(), p.short_code())
!= "RUF014"
})
.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));
}
.map(|p| {
let prefix = p.linter().common_prefix();
let code = p.short_code();
// 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));
}
let mut rules_iter = p.rules();
let rule1 = rules_iter.next();
let rule2 = rules_iter.next();
None
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
}
}),
),
),

View File

@@ -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-426](https://cwe.mitre.org/data/definitions/426.html)
/// - [Common Weakness Enumeration: CWE-78](https://cwe.mitre.org/data/definitions/78.html)
#[violation]
pub struct StartProcessWithPartialPath;

View File

@@ -70,7 +70,6 @@ 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"))
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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::{negate, space};
use crate::cst::helpers::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,9 +741,31 @@ pub(crate) fn fix_unnecessary_call_around_sorted(
..
})
) {
arg.value = negate(&arg.value);
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
})
.collect_vec()
} else {

View File

@@ -1,6 +1,5 @@
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;
@@ -87,16 +86,28 @@ 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;
};
if ComparableExpr::from(key) != ComparableExpr::from(target_key) {
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 {
return;
}
if ComparableExpr::from(value) != ComparableExpr::from(target_value) {
if target_value.id != value.id {
return;
}
add_diagnostic(checker, expr);
@@ -115,7 +126,13 @@ pub(crate) fn unnecessary_list_set_comprehension(
if !generator.ifs.is_empty() || generator.is_async {
return;
}
if ComparableExpr::from(elt) != ComparableExpr::from(&generator.target) {
let Some(elt) = elt.as_name_expr() else {
return;
};
let Some(target) = generator.target.as_name_expr() else {
return;
};
if elt.id != target.id {
return;
}
add_diagnostic(checker, expr);

View File

@@ -103,18 +103,17 @@ 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 | reversed(sorted(x, reverse=x))
10 10 |
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))
10 | reversed(sorted(x, reverse=x))
|
= 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))
|
= help: Remove unnecessary `reversed` call
Suggested fix
5 5 | reversed(sorted(x, key=lambda e: e))
@@ -123,8 +122,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 | reversed(sorted(x, reverse=x))
11 11 | reversed(sorted(x, reverse=not x))
10 10 |
11 11 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
C413.py:9:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
@@ -132,8 +131,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 | reversed(sorted(x, reverse=x))
11 | reversed(sorted(x, reverse=not x))
10 |
11 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
|
= help: Remove unnecessary `reversed` call
@@ -143,87 +142,46 @@ 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 | reversed(sorted(x, reverse=x))
11 11 | reversed(sorted(x, reverse=not x))
12 12 |
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)))
C413.py:10:1: C413 [*] Unnecessary `reversed` call around `sorted()`
C413.py:12:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
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 |-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:11:1: C413 [*] Unnecessary `reversed` call around `sorted()`
|
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)))
11 | # Regression test for: https://github.com/astral-sh/ruff/issues/7289
12 | reversed(sorted(i for i in range(42)))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
15 | reversed(sorted((i for i in range(42)), reverse=True))
13 | 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 |
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 |
C413.py:15:1: C413 [*] Unnecessary `reversed` call around `sorted()`
C413.py:13: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))
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))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C413
|
= help: Remove unnecessary `reversed` call
Suggested fix
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):
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):

View File

@@ -40,18 +40,17 @@ 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 | [(k, v) for k, v in d.items()]
10 10 |
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()}
10 | [(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()}
|
= help: Rewrite using `dict()`
Suggested fix
5 5 |
@@ -60,8 +59,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 | [(k, v) for k, v in d.items()]
11 11 | {k: (a, b) for k, (a, b) in d.items()}
10 10 |
11 11 | [i for i, in z]
C416.py:9:1: C416 [*] Unnecessary `dict` comprehension (rewrite using `dict()`)
|
@@ -69,8 +68,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 | [(k, v) for k, v in d.items()]
11 | {k: (a, b) for k, (a, b) in d.items()}
10 |
11 | [i for i, in z]
|
= help: Rewrite using `dict()`
@@ -80,64 +79,23 @@ 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 | [(k, v) for k, v in d.items()]
11 11 | {k: (a, b) for k, (a, b) in d.items()}
12 12 |
10 10 |
11 11 | [i for i, in z]
12 12 | [i for i, j in y]
C416.py:10:1: C416 [*] Unnecessary `list` comprehension (rewrite using `list()`)
C416.py:22:70: C416 [*] Unnecessary `list` comprehension (rewrite using `list()`)
|
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])
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])
| ^^^^^^^^^^^^^^^^^^^^^^^ C416
|
= help: Rewrite using `list()`
Suggested fix
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))
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))

View File

@@ -1,27 +0,0 @@
//! 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(())
}
}

View File

@@ -1,77 +0,0 @@
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);
}
}

View File

@@ -1,5 +0,0 @@
pub(crate) use direct_logger_instantiation::*;
pub(crate) use undocumented_warn::*;
mod direct_logger_instantiation;
mod undocumented_warn;

View File

@@ -1,71 +0,0 @@
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 its 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);
}
}

View File

@@ -1,40 +0,0 @@
---
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__)

View File

@@ -1,41 +0,0 @@
---
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

View File

@@ -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,
SimpleWhitespace, SmallStatement, Statement, TrailingWhitespace, UnaryOperation,
};
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::negate;
use crate::cst::helpers::space;
use crate::cst::matchers::match_indented_block;
use crate::cst::matchers::match_module;
use crate::importer::ImportRequest;
@@ -567,6 +567,23 @@ 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:

View File

@@ -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

View File

@@ -22,7 +22,6 @@ 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;

View File

@@ -19,7 +19,6 @@ 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(

View File

@@ -1,178 +0,0 @@
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));
}

View File

@@ -1,12 +1,10 @@
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;

View File

@@ -1,52 +0,0 @@
---
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
|

View File

@@ -70,10 +70,6 @@ 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) {

View File

@@ -63,10 +63,6 @@ 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() {

View File

@@ -97,6 +97,5 @@ D.py:658:5: D204 [*] 1 blank line required after class docstring
659 |+
659 660 | def sort_services(self):
660 661 | pass
661 662 |

View File

@@ -77,13 +77,4 @@ 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
|

View File

@@ -310,21 +310,4 @@ 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."

View File

@@ -292,21 +292,4 @@ 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."

View File

@@ -256,20 +256,4 @@ 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(())
}
}

View File

@@ -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

View File

@@ -43,7 +43,6 @@ 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::*;
@@ -102,7 +101,6 @@ 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;

View File

@@ -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, TextSize};
use ruff_text_size::Ranged;
use crate::autofix::snippet::SourceCodeSnippet;
use crate::checkers::ast::Checker;
@@ -74,8 +74,7 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
return;
}
// Map from expression hash to (starting offset, number of comparisons, list
let mut value_to_comparators: FxHashMap<HashableExpr, (TextSize, Vec<&Expr>)> =
let mut value_to_comparators: FxHashMap<HashableExpr, (usize, Vec<&Expr>)> =
FxHashMap::with_capacity_and_hasher(
bool_op.values.len() * 2,
BuildHasherDefault::default(),
@@ -96,31 +95,30 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
};
if matches!(left.as_ref(), Expr::Name(_) | Expr::Attribute(_)) {
let (_, left_matches) = value_to_comparators
let (left_count, left_matches) = value_to_comparators
.entry(left.deref().into())
.or_insert_with(|| (left.start(), Vec::new()));
.or_insert_with(|| (0, Vec::new()));
*left_count += 1;
left_matches.push(right);
}
if matches!(right, Expr::Name(_) | Expr::Attribute(_)) {
let (_, right_matches) = value_to_comparators
let (right_count, right_matches) = value_to_comparators
.entry(right.into())
.or_insert_with(|| (right.start(), Vec::new()));
.or_insert_with(|| (0, Vec::new()));
*right_count += 1;
right_matches.push(left);
}
}
for (value, (_, comparators)) in value_to_comparators
.iter()
.sorted_by_key(|(_, (start, _))| *start)
{
if comparators.len() > 1 {
for (value, (count, comparators)) in value_to_comparators {
if count > 1 {
checker.diagnostics.push(Diagnostic::new(
RepeatedEqualityComparison {
expression: SourceCodeSnippet::new(merged_membership_test(
value.as_expr(),
bool_op.op,
comparators,
&comparators,
checker.locator(),
)),
},

View File

@@ -1,126 +0,0 @@
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(),
));
}
}

View File

@@ -42,7 +42,6 @@ pub struct Settings {
pub max_returns: usize,
pub max_branches: usize,
pub max_statements: usize,
pub max_public_methods: usize,
}
impl Default for Settings {
@@ -53,7 +52,6 @@ impl Default for Settings {
max_returns: 6,
max_branches: 12,
max_statements: 50,
max_public_methods: 20,
}
}
}

View File

@@ -1,46 +0,0 @@
---
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:
|

View File

@@ -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

View File

@@ -42,16 +42,13 @@ pub struct UnnecessaryEncodeUTF8 {
impl AlwaysAutofixableViolation for UnnecessaryEncodeUTF8 {
#[derive_message_formats]
fn message(&self) -> String {
match self.reason {
Reason::BytesLiteral => format!("Unnecessary call to `encode` as UTF-8"),
Reason::DefaultArgument => format!("Unnecessary UTF-8 `encoding` argument to `encode`"),
}
format!("Unnecessary call to `encode` as UTF-8")
}
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(),
}
}
}

View File

@@ -1,6 +1,4 @@
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},
@@ -8,29 +6,20 @@ 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.
///
/// ## 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.
/// 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.
///
/// ## Example
/// ```python
@@ -41,8 +30,6 @@ use crate::{registry::AsRule, settings::types::PythonVersion};
/// ```python
/// type ListOfInt = list[int]
/// ```
///
/// [PEP 695]: https://peps.python.org/pep-0695/
#[violation]
pub struct NonPEP695TypeAlias {
name: String,
@@ -96,36 +83,24 @@ 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 {
vars: vec![],
names: vec![],
semantic: checker.semantic(),
};
visitor.visit_expr(value);
let type_params = if visitor.vars.is_empty() {
let type_params = if visitor.names.is_empty() {
None
} else {
Some(ast::TypeParams {
range: TextRange::default(),
type_params: visitor
.vars
.into_iter()
.map(|TypeVar { name, restriction }| {
.names
.iter()
.map(|name| {
TypeParam::TypeVar(TypeParamTypeVar {
range: TextRange::default(),
name: Identifier::new(name.id.clone(), TextRange::default()),
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,
},
bound: None,
})
})
.collect(),
@@ -145,22 +120,8 @@ 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> {
vars: Vec<TypeVar<'a>>,
names: Vec<&'a ExprName>,
semantic: &'a SemanticModel<'a>,
}
@@ -188,16 +149,16 @@ impl<'a> Visitor<'a> for TypeVarReferenceVisitor<'a> {
..
}) => {
if self.semantic.match_typing_expr(subscript_value, "TypeVar") {
self.vars.push(TypeVar {
name,
restriction: None,
});
self.names.push(name);
}
}
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,
@@ -207,18 +168,9 @@ impl<'a> Visitor<'a> for TypeVarReferenceVisitor<'a> {
})
)
})
&& arguments.keywords.is_empty()
{
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 });
self.names.push(name);
}
}
_ => {}

View File

@@ -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 UTF-8 `encoding` argument to `encode`
UP012.py:32:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
31 | bar = "bar"
32 | f"foo{bar}".encode("utf-8")
@@ -235,7 +235,7 @@ UP012.py:32:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
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 UTF-8 `encoding` argument to `encode`
34 34 | "foo".encode(encoding)
35 35 | f"foo{bar}".encode(encoding)
UP012.py:36:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
UP012.py:36:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
34 | "foo".encode(encoding)
35 | f"foo{bar}".encode(encoding)
@@ -258,7 +258,7 @@ UP012.py:36:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
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 UTF-8 `encoding` argument to `encode`
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 UTF-8 `encoding` argument to `encode`
UP012.py:53:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
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 UTF-8 `encoding` argument to `encode`
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 UTF-8 `encoding` argument to `encode`
55 55 | "unicode text©".encode(encoding="UTF8") # "unicode text©".encode()
56 56 |
UP012.py:55:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
UP012.py:55:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
53 | "unicode text©".encode("utf-8") # "unicode text©".encode()
54 | "unicode text©".encode()
@@ -301,7 +301,7 @@ UP012.py:55:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
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 UTF-8 `encoding` argument to `encode`
UP012.py:74:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
72 | )).encode()
73 |
@@ -480,7 +480,7 @@ UP012.py:74:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
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 UTF-8 `encoding` argument to `encode`
76 76 | ("unicode text©").encode("utf-8")
77 77 | ("unicode text©").encode(encoding="utf-8")
UP012.py:75:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
UP012.py:75:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
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 UTF-8 `encoding` argument to `encode`
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 UTF-8 `encoding` argument to `encode`
76 76 | ("unicode text©").encode("utf-8")
77 77 | ("unicode text©").encode(encoding="utf-8")
UP012.py:76:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
UP012.py:76:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
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 UTF-8 `encoding` argument to `encode`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 UTF-8 `encoding` argument to `encode`
76 |+("unicode text©").encode()
77 77 | ("unicode text©").encode(encoding="utf-8")
UP012.py:77:1: UP012 [*] Unnecessary UTF-8 `encoding` argument to `encode`
UP012.py:77:1: UP012 [*] Unnecessary call to `encode` as UTF-8
|
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")

View File

@@ -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
16 | # UP040 bounded generic (todo)
|
= help: Use the `type` keyword
@@ -80,154 +80,153 @@ 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
16 16 | # UP040 bounded generic (todo)
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
16 | # UP040 bounded generic (todo)
17 | T = typing.TypeVar("T", bound=int)
18 | x: typing.TypeAlias = list[T]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
19 |
20 | # UP040 constrained generic
20 | T = typing.TypeVar("T", int, str)
|
= help: Use the `type` keyword
Fix
15 15 |
16 16 | # UP040 bounded generic
16 16 | # UP040 bounded generic (todo)
17 17 | T = typing.TypeVar("T", bound=int)
18 |-x: typing.TypeAlias = list[T]
18 |+type x[T: int] = list[T]
18 |+type x = list[T]
19 19 |
20 20 | # UP040 constrained generic
21 21 | T = typing.TypeVar("T", int, str)
20 20 | T = typing.TypeVar("T", int, str)
21 21 | x: typing.TypeAlias = list[T]
UP040.py:22:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
UP040.py:21:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
20 | # UP040 constrained generic
21 | T = typing.TypeVar("T", int, str)
22 | x: typing.TypeAlias = list[T]
20 | T = typing.TypeVar("T", int, str)
21 | x: typing.TypeAlias = list[T]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
23 |
24 | # UP040 contravariant generic
22 |
23 | # UP040 contravariant generic (todo)
|
= help: Use the `type` keyword
Fix
18 18 | x: typing.TypeAlias = list[T]
19 19 |
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)
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)
UP040.py:26:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
UP040.py:25:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
24 | # UP040 contravariant generic
25 | T = typing.TypeVar("T", contravariant=True)
26 | x: typing.TypeAlias = list[T]
23 | # UP040 contravariant generic (todo)
24 | T = typing.TypeVar("T", contravariant=True)
25 | x: typing.TypeAlias = list[T]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
27 |
28 | # UP040 covariant generic
26 |
27 | # UP040 covariant generic (todo)
|
= help: Use the `type` keyword
Fix
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)
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)
UP040.py:30:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
UP040.py:29:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
28 | # UP040 covariant generic
29 | T = typing.TypeVar("T", covariant=True)
30 | x: typing.TypeAlias = list[T]
27 | # UP040 covariant generic (todo)
28 | T = typing.TypeVar("T", covariant=True)
29 | x: typing.TypeAlias = list[T]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
31 |
32 | # UP040 in class scope
30 |
31 | # UP040 in class scope
|
= help: Use the `type` keyword
Fix
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"]
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"]
UP040.py:36:5: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
UP040.py:35:5: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
34 | class Foo:
35 | # reference to global variable
36 | x: typing.TypeAlias = list[T]
33 | class Foo:
34 | # reference to global variable
35 | x: typing.TypeAlias = list[T]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
37 |
38 | # reference to class variable
36 |
37 | # reference to class variable
|
= help: Use the `type` keyword
Fix
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"]
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"]
UP040.py:40:5: UP040 [*] Type alias `y` uses `TypeAlias` annotation instead of the `type` keyword
UP040.py:39:5: UP040 [*] Type alias `y` uses `TypeAlias` annotation instead of the `type` keyword
|
38 | # reference to class variable
39 | TCLS = typing.TypeVar["TCLS"]
40 | y: typing.TypeAlias = list[TCLS]
37 | # reference to class variable
38 | TCLS = typing.TypeVar["TCLS"]
39 | y: typing.TypeAlias = list[TCLS]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
41 |
42 | # UP040 wont add generics in fix
40 |
41 | # UP040 wont add generics in fix
|
= help: Use the `type` keyword
Fix
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)
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)
UP040.py:44:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
UP040.py:43:1: UP040 [*] Type alias `x` uses `TypeAlias` annotation instead of the `type` keyword
|
42 | # UP040 wont add generics in fix
43 | T = typing.TypeVar(*args)
44 | x: typing.TypeAlias = list[T]
41 | # UP040 wont add generics in fix
42 | T = typing.TypeVar(*args)
43 | x: typing.TypeAlias = list[T]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP040
45 |
46 | # OK
44 |
45 | # OK
|
= help: Use the `type` keyword
Fix
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
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

View File

@@ -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

View File

@@ -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

View File

@@ -10,6 +10,8 @@ pub(crate) use mutable_class_default::*;
pub(crate) use mutable_dataclass_default::*;
pub(crate) use pairwise_over_zipped::*;
pub(crate) use static_key_dict_comprehension::*;
#[cfg(feature = "test-rules")]
pub(crate) use test_rules::*;
pub(crate) use unnecessary_iterable_allocation_for_first_element::*;
#[cfg(feature = "unreachable-code")]
pub(crate) use unreachable::*;
@@ -29,6 +31,8 @@ mod mutable_class_default;
mod mutable_dataclass_default;
mod pairwise_over_zipped;
mod static_key_dict_comprehension;
#[cfg(feature = "test-rules")]
mod test_rules;
mod unnecessary_iterable_allocation_for_first_element;
#[cfg(feature = "unreachable-code")]
pub(crate) mod unreachable;

View File

@@ -0,0 +1,83 @@
use ruff_diagnostics::{AutofixKind, Violation};
use ruff_macros::{derive_message_formats, violation};
/// ## What it does
/// Fake rule for testing.
///
/// ## Why is this bad?
/// Tests must pass!
///
/// ## Example
/// ```python
/// foo
/// ```
///
/// Use instead:
/// ```python
/// bar
/// ```
#[violation]
pub struct StableTestRule;
impl Violation for StableTestRule {
const AUTOFIX: AutofixKind = AutofixKind::None;
#[derive_message_formats]
fn message(&self) -> String {
format!("Hey this is a stable test rule.")
}
}
/// ## What it does
/// Fake rule for testing.
///
/// ## Why is this bad?
/// Tests must pass!
///
/// ## Example
/// ```python
/// foo
/// ```
///
/// Use instead:
/// ```python
/// bar
/// ```
#[violation]
pub struct PreviewTestRule;
impl Violation for PreviewTestRule {
const AUTOFIX: AutofixKind = AutofixKind::None;
#[derive_message_formats]
fn message(&self) -> String {
format!("Hey this is a preview test rule.")
}
}
/// ## What it does
/// Fake rule for testing.
///
/// ## Why is this bad?
/// Tests must pass!
///
/// ## Example
/// ```python
/// foo
/// ```
///
/// Use instead:
/// ```python
/// bar
/// ```
#[violation]
pub struct NurseryTestRule;
impl Violation for NurseryTestRule {
const AUTOFIX: AutofixKind = AutofixKind::None;
#[derive_message_formats]
fn message(&self) -> String {
format!("Hey this is a nursery test rule.")
}
}

View File

@@ -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.39"
mimalloc = "0.1.34"
[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"

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_cli"
version = "0.0.290"
version = "0.0.289"
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 = "6.1.1" }
notify = { version = "5.1.0" }
path-absolutize = { workspace = true, features = ["once_cell_cache"] }
rayon = { version = "1.7.0" }
regex = { workspace = true }
@@ -69,6 +69,7 @@ walkdir = { version = "2.3.2" }
wild = { version = "2" }
[dev-dependencies]
ruff = { path = "../ruff", features = ["clap", "test-rules"] }
assert_cmd = { version = "2.0.8" }
# Avoid writing colored snapshots when running tests from the terminal
colored = { workspace = true, features = ["no-color"]}
@@ -79,7 +80,7 @@ test-case = { workspace = true }
ureq = { version = "2.6.2", features = [] }
[target.'cfg(target_os = "windows")'.dependencies]
mimalloc = "0.1.39"
mimalloc = "0.1.34"
[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"

View File

@@ -1,16 +1,17 @@
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, RuleSelectorParser};
use ruff::RuleSelector;
use ruff_workspace::configuration::{Configuration, RuleSelection};
use ruff_workspace::resolver::ConfigProcessor;
@@ -128,7 +129,7 @@ pub struct CheckCommand {
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
value_parser = parse_rule_selector,
help_heading = "Rule selection",
hide_possible_values = true
)]
@@ -138,7 +139,7 @@ pub struct CheckCommand {
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
value_parser = parse_rule_selector,
help_heading = "Rule selection",
hide_possible_values = true
)]
@@ -148,7 +149,7 @@ pub struct CheckCommand {
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
value_parser = parse_rule_selector,
help_heading = "Rule selection",
hide_possible_values = true
)]
@@ -158,7 +159,7 @@ pub struct CheckCommand {
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
value_parser = parse_rule_selector,
help_heading = "Rule selection",
hide = true
)]
@@ -190,7 +191,7 @@ pub struct CheckCommand {
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
value_parser = parse_rule_selector,
help_heading = "Rule selection",
hide_possible_values = true
)]
@@ -200,7 +201,7 @@ pub struct CheckCommand {
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
value_parser = parse_rule_selector,
help_heading = "Rule selection",
hide_possible_values = true
)]
@@ -210,7 +211,7 @@ pub struct CheckCommand {
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
value_parser = parse_rule_selector,
help_heading = "Rule selection",
hide_possible_values = true
)]
@@ -220,7 +221,7 @@ pub struct CheckCommand {
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
value_parser = parse_rule_selector,
help_heading = "Rule selection",
hide = true
)]
@@ -510,6 +511,11 @@ 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),

View File

@@ -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!
"#;
error!(
warn!(
"{}{}{} {message}\n{error}",
"Panicked while linting ".bold(),
"Linting panicked ".bold(),
fs::relativize_path(path).bold(),
":".bold()
);

View File

@@ -6,7 +6,6 @@ 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;
@@ -23,7 +22,6 @@ 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;
@@ -87,13 +85,7 @@ pub(crate) fn format(
.with_line_width(LineWidth::from(NonZeroU16::from(line_length)))
.with_preview(preview);
debug!("Formatting {} with {:?}", path.display(), options);
Some(match catch_unwind(|| format_path(path, options, mode)) {
Ok(inner) => inner,
Err(error) => {
Err(FormatCommandError::Panic(Some(path.to_path_buf()), error))
}
})
Some(format_path(path, options, mode))
}
Err(err) => Some(Err(FormatCommandError::Ignore(err))),
}
@@ -103,20 +95,22 @@ pub(crate) fn format(
Err(err) => Right(err),
});
let duration = start.elapsed();
debug!(
"Formatted {} files in {:.2?}",
results.len() + errors.len(),
duration
);
// Report on any errors.
for error in &errors {
error!("{error}");
}
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}");
}
}
// Report on the formatting changes.
if log_level >= LogLevel::Default {
#[allow(clippy::print_stdout)]
@@ -261,7 +255,6 @@ 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 {
@@ -327,29 +320,6 @@ 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()
)
}
}
}
}
}

View File

@@ -111,15 +111,13 @@ pub fn run(
{
eprintln!(
r#"
{}{} {} If you could open an issue at:
{}: `ruff` crashed. This indicates a bug in `ruff`. 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);

View File

@@ -249,16 +249,16 @@ fn show_statistics() {
#[test]
fn nursery_prefix() {
// `--select E` should detect E741, but not E225, which is in the nursery.
let args = ["--select", "E"];
// Should only detect RUF900, but not the unstable test rules
let args = ["--select", "RUF9"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
.pass_stdin(""), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `I`
-:1:1: RUF900 Hey this is a stable test rule.
Found 1 error.
----- stderr -----
@@ -267,17 +267,17 @@ fn nursery_prefix() {
#[test]
fn nursery_all() {
// `--select ALL` should detect E741, but not E225, which is in the nursery.
// Should detect RUF900, but not the unstable test rules
let args = ["--select", "ALL"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
.pass_stdin(""), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `I`
-:1:1: D100 Missing docstring in public module
-:1:1: RUF900 Hey this is a stable test rule.
Found 2 errors.
----- stderr -----
@@ -288,36 +288,35 @@ fn nursery_all() {
#[test]
fn nursery_direct() {
// `--select E225` should detect E225.
let args = ["--select", "E225"];
let args = ["--select", "RUF912"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
.pass_stdin(""), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:2: E225 Missing whitespace around operator
-:1:1: RUF912 Hey this is a nursery test rule.
Found 1 error.
----- stderr -----
warning: Selection of nursery rule `E225` without the `--preview` flag is deprecated.
warning: Selection of nursery rule `RUF912` without the `--preview` flag is deprecated.
"###);
}
#[test]
fn nursery_group_selector() {
// Only nursery rules should be detected e.g. E225 and a warning should be displayed
// Only nursery rules should be detected e.g. RUF912
let args = ["--select", "NURSERY"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
.pass_stdin(""), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: CPY001 Missing copyright notice at top of file
-:1:2: E225 Missing whitespace around operator
-:1:1: RUF912 Hey this is a nursery test rule.
Found 2 errors.
----- stderr -----
@@ -327,38 +326,39 @@ fn nursery_group_selector() {
#[test]
fn nursery_group_selector_preview_enabled() {
// Only nursery rules should be detected e.g. E225 and a warning should be displayed
// A warning should be displayed due to deprecated selector usage
let args = ["--select", "NURSERY", "--preview"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
.pass_stdin(""), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: CPY001 Missing copyright notice at top of file
-:1:2: E225 Missing whitespace around operator
-:1:1: RUF912 Hey this is a nursery test rule.
Found 2 errors.
----- stderr -----
warning: The `NURSERY` selector has been deprecated.
warning: The `NURSERY` selector has been deprecated. Use the `PREVIEW` selector instead.
"###);
}
#[test]
fn preview_enabled_prefix() {
// E741 and E225 (preview) should both be detected
let args = ["--select", "E", "--preview"];
// All the RUF9XX test rules should be triggered
let args = ["--select", "RUF9", "--preview"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
.pass_stdin(""), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `I`
-:1:2: E225 Missing whitespace around operator
Found 2 errors.
-:1:1: RUF900 Hey this is a stable test rule.
-:1:1: RUF911 Hey this is a preview test rule.
-:1:1: RUF912 Hey this is a nursery test rule.
Found 3 errors.
----- stderr -----
"###);
@@ -370,15 +370,16 @@ fn preview_enabled_all() {
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
.pass_stdin(""), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `I`
-:1:1: D100 Missing docstring in public module
-:1:1: CPY001 Missing copyright notice at top of file
-:1:2: E225 Missing whitespace around operator
Found 4 errors.
-:1:1: RUF900 Hey this is a stable test rule.
-:1:1: RUF911 Hey this is a preview test rule.
-:1:1: RUF912 Hey this is a nursery test rule.
Found 5 errors.
----- stderr -----
warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`.
@@ -388,16 +389,16 @@ fn preview_enabled_all() {
#[test]
fn preview_enabled_direct() {
// E225 should be detected without warning
let args = ["--select", "E225", "--preview"];
// Should be enabled without warning
let args = ["--select", "RUF911", "--preview"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
.pass_stdin(""), @r###"
success: false
exit_code: 1
----- stdout -----
-:1:2: E225 Missing whitespace around operator
-:1:1: RUF911 Hey this is a preview test rule.
Found 1 error.
----- stderr -----
@@ -406,73 +407,92 @@ fn preview_enabled_direct() {
#[test]
fn preview_disabled_direct() {
// FURB145 is preview not nursery so selecting should be empty
let args = ["--select", "FURB145"];
// RUF911 is preview not nursery so selecting should be empty
let args = ["--select", "RUF911"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("a = l[:]\n"), @r###"
.pass_stdin(""), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: Selection `FURB145` has no effect because the `--preview` flag was not included.
warning: Selection `RUF911` has no effect because the `--preview` flag was not included.
"###);
}
#[test]
fn preview_disabled_prefix_empty() {
// Warns that the selection is empty since all of the CPY rules are in preview
let args = ["--select", "CPY"];
// Warns that the selection is empty since all of the RUF91 rules are in preview
let args = ["--select", "RUF91"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
.pass_stdin(""), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: Selection `CPY` has no effect because the `--preview` flag was not included.
warning: Selection `RUF91` has no effect because the `--preview` flag was not included.
"###);
}
#[test]
fn preview_group_selector() {
// `--select PREVIEW` should error (selector was removed)
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(""), @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
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###"
.pass_stdin(""), @r###"
success: false
exit_code: 2
exit_code: 1
----- stdout -----
-:1:1: CPY001 Missing copyright notice at top of file
-:1:1: RUF911 Hey this is a preview test rule.
-:1:1: RUF912 Hey this is a nursery test rule.
Found 3 errors.
----- stderr -----
error: invalid value 'PREVIEW' for '--select <RULE_CODE>'
For more information, try '--help'.
"###);
}
#[test]
fn preview_enabled_group_ignore() {
// `--select E --ignore PREVIEW` should detect E741 and E225, which is in preview but "E" is more specific.
let args = ["--select", "E", "--ignore", "PREVIEW", "--preview"];
// Should detect stable and unstable rules, RUF9 is more specific than PREVIEW so ignore has no effect
let args = ["--select", "RUF9", "--ignore", "PREVIEW", "--preview"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
.pass_stdin(""), @r###"
success: false
exit_code: 2
exit_code: 1
----- stdout -----
-:1:1: RUF900 Hey this is a stable test rule.
-:1:1: RUF911 Hey this is a preview test rule.
-:1:1: RUF912 Hey this is a nursery test rule.
Found 3 errors.
----- stderr -----
error: invalid value 'PREVIEW' for '--ignore <RULE_CODE>'
For more information, try '--help'.
"###);
}

View File

@@ -1,23 +1,25 @@
//! 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 num_bigint::BigInt;
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,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub enum ComparableBoolOp {
@@ -663,32 +665,38 @@ 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)]
@@ -907,46 +915,50 @@ 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,

View File

@@ -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.
x10 = (
x1 = (
a
.b
# comment 1
@@ -46,27 +46,6 @@ x10 = (
.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
@@ -127,7 +106,7 @@ x6 = (
a.b
)
# Regression test for: https://github.com/astral-sh/ruff/issues/6181
# regression: https://github.com/astral-sh/ruff/issues/6181
(#
()).a
@@ -146,9 +125,3 @@ x6 = (
# dangling before dot own-line
.b
)
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
result = (
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
+ 1
).bit_length()

View File

@@ -206,11 +206,6 @@ 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)

View File

@@ -265,8 +265,3 @@ f( # a
kwargs,
)
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
result = (
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
+ 1
)()

View File

@@ -91,22 +91,3 @@ 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()),
)

View File

@@ -1,5 +0,0 @@
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
result = (
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
+ 1
)[0]

View File

@@ -466,11 +466,8 @@ impl Format<PyFormatContext<'_>> for BinaryLike<'_> {
}
}
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)
const fn is_simple_power_expression(left: &Expr, right: &Expr) -> bool {
is_simple_power_operand(left) && is_simple_power_operand(right)
}
/// 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)
@@ -664,17 +661,13 @@ 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)?;
}
match &left.0 {
[OperandOrOperator::Operand(operand)] => operand.fmt(f)?,
_ => in_parentheses_only_group(&left).fmt(f)?,
}
in_parentheses_only_group(&left).fmt(f)?;
if let Some(trailing) = left.last_operand().trailing_binary_comments() {
trailing_comments(trailing).fmt(f)?;
@@ -715,10 +708,7 @@ impl Format<PyFormatContext<'_>> for FlatBinaryExpressionSlice<'_> {
leading_comments(leading).fmt(f)?;
}
match &right.0 {
[OperandOrOperator::Operand(operand)] => operand.fmt(f),
_ => in_parentheses_only_group(&right).fmt(f),
}
in_parentheses_only_group(&right).fmt(f)
}
}

View File

@@ -150,8 +150,6 @@ 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)
}

View File

@@ -3,9 +3,7 @@ use ruff_python_ast::node::AnyNodeRef;
use ruff_python_ast::{Expr, ExprCall};
use crate::comments::{dangling_comments, SourceComment};
use crate::expression::parentheses::{
is_expression_parenthesized, NeedsParentheses, OptionalParentheses,
};
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
use crate::expression::CallChainLayout;
use crate::prelude::*;
@@ -88,8 +86,6 @@ 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)
}

View File

@@ -3,10 +3,9 @@ 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::{
is_expression_parenthesized, parenthesized, NeedsParentheses, OptionalParentheses,
};
use crate::expression::parentheses::{parenthesized, NeedsParentheses, OptionalParentheses};
use crate::expression::CallChainLayout;
use crate::prelude::*;
@@ -42,34 +41,34 @@ impl FormatNodeRule<ExprSubscript> for FormatExprSubscript {
"A subscript expression can only have a single dangling comment, the one after the bracket"
);
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)
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),
});
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)])
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 {
write!(f, [format_inner])
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()])
}
});
parenthesized("[", &format_slice, "]")
.with_dangling_comments(dangling_comments)
.fmt(f)
}
fn fmt_dangling_comments(
@@ -93,8 +92,6 @@ 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,

View File

@@ -385,21 +385,14 @@ 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| is_parenthesized(first, context));
let last_parenthesized = visitor
.last
.is_some_and(|last| is_parenthesized(last, context));
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)
});
first_parenthesized || last_parenthesized
}
}
@@ -509,7 +502,6 @@ 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;
}

View File

@@ -120,7 +120,7 @@ impl From<ParseError> for FormatModuleError {
}
}
#[tracing::instrument(level=Level::TRACE, skip_all)]
#[tracing::instrument(level=Level::TRACE, skip_all, err)]
pub fn format_module(
contents: &str,
options: PyFormatOptions,

View File

@@ -275,7 +275,23 @@ last_call()
flags & ~select.EPOLLIN and waiters.write_task is not None
lambda arg: None
lambda a=True: a
@@ -115,7 +115,7 @@
@@ -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 @@
arg,
another,
kwarg="hey",
@@ -330,10 +346,11 @@ 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)

View File

@@ -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.
x10 = (
x1 = (
a
.b
# comment 1
@@ -52,27 +52,6 @@ x10 = (
.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
@@ -133,7 +112,7 @@ x6 = (
a.b
)
# Regression test for: https://github.com/astral-sh/ruff/issues/6181
# regression: https://github.com/astral-sh/ruff/issues/6181
(#
()).a
@@ -152,12 +131,6 @@ 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
@@ -200,7 +173,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.
x10 = (
x1 = (
a.b
# comment 1
. # comment 2
@@ -208,22 +181,6 @@ x10 = (
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
@@ -270,7 +227,7 @@ x6 = (
a.b
)
# Regression test for: https://github.com/astral-sh/ruff/issues/6181
# regression: https://github.com/astral-sh/ruff/issues/6181
( #
()
).a
@@ -288,12 +245,6 @@ x6 = (
# dangling before dot own-line
.b
)
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
result = (
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
+ 1
).bit_length()
```

View File

@@ -212,11 +212,6 @@ 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)
@@ -662,11 +657,6 @@ 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)

View File

@@ -271,11 +271,6 @@ f( # a
kwargs,
)
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
result = (
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
+ 1
)()
```
## Output
@@ -542,12 +537,6 @@ f( # a
** # h
kwargs,
)
# Regression test for: https://github.com/astral-sh/ruff/issues/7370
result = (
f(111111111111111111111111111111111111111111111111111111111111111111111111111111111)
+ 1
)()
```

View File

@@ -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.

View File

@@ -97,25 +97,6 @@ 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
@@ -210,27 +191,6 @@ 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()),
)
```

View File

@@ -1,24 +0,0 @@
---
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]
```

View File

@@ -184,7 +184,7 @@ pub(crate) fn function_visibility(function: &ast::StmtFunctionDef) -> Visibility
}
}
pub fn method_visibility(function: &ast::StmtFunctionDef) -> Visibility {
pub(crate) 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| {

View File

@@ -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)?;

View File

@@ -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.2.0"
shlex = "1.1.0"
tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter"] }

View File

@@ -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
///

View File

@@ -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
///

View File

@@ -35,7 +35,7 @@ console_log = { version = "1.0.0" }
log = { workspace = true }
serde = { workspace = true }
serde-wasm-bindgen = { version = "0.6.0" }
serde-wasm-bindgen = { version = "0.5.0" }
wasm-bindgen = { version = "0.2.84" }
js-sys = { version = "0.3.61" }

View File

@@ -588,12 +588,11 @@ 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 {
// We have no suggested alternative since there is intentionally no "PREVIEW" selector
""
"Use the `PREVIEW` selector instead."
};
warn_user_once!("The `NURSERY` selector has been deprecated.{suggestion}");
warn_user_once!("The `NURSERY` selector has been deprecated. {suggestion}");
}
if preview.is_disabled() {
@@ -852,14 +851,7 @@ mod tests {
Rule::QuadraticListSummation,
];
const PREVIEW_RULES: &[Rule] = &[
Rule::DirectLoggerInstantiation,
Rule::ManualDictComprehension,
Rule::SliceCopy,
Rule::TooManyPublicMethods,
Rule::TooManyPublicMethods,
Rule::UndocumentedWarn,
];
const PREVIEW_RULES: &[Rule] = &[Rule::SliceCopy];
#[allow(clippy::needless_pass_by_value)]
fn resolve_rules(
@@ -1100,27 +1092,6 @@ 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(
@@ -1190,6 +1161,31 @@ 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

Some files were not shown because too many files have changed in this diff Show More