Compare commits

..

1 Commits

Author SHA1 Message Date
konstin
9f16822c56 Use tracing in ruff_cli 2023-09-19 10:35:50 +02:00
94 changed files with 585 additions and 1636 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

146
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]]
@@ -781,15 +783,6 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
[[package]]
name = "fern"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee"
dependencies = [
"log",
]
[[package]]
name = "filetime"
version = "0.2.22"
@@ -810,7 +803,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.290"
version = "0.0.289"
dependencies = [
"anyhow",
"clap",
@@ -881,7 +874,7 @@ dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
@@ -1120,7 +1113,7 @@ dependencies = [
"pmutil 0.6.1",
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -1275,9 +1268,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 +1336,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 +1366,7 @@ checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
"libc",
"log",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.48.0",
]
@@ -1418,21 +1411,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 +1712,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -1805,9 +1797,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 +1932,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 +1953,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 +2013,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.290"
version = "0.0.289"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2029,7 +2021,6 @@ dependencies = [
"chrono",
"clap",
"colored",
"fern",
"glob",
"globset",
"imperative",
@@ -2079,6 +2070,8 @@ dependencies = [
"test-case",
"thiserror",
"toml",
"tracing",
"tracing-subscriber",
"typed-arena",
"unicode-width",
"unicode_names2",
@@ -2119,7 +2112,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.290"
version = "0.0.289"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2259,7 +2252,7 @@ dependencies = [
"proc-macro2",
"quote",
"ruff_python_trivia",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -2673,9 +2666,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 +2683,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -2752,7 +2745,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -2775,9 +2768,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 +2840,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -2863,9 +2856,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 +2963,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 +3011,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 +3058,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 +3079,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 +3111,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -3310,7 +3314,7 @@ checksum = "f7e1ba1f333bd65ce3c9f27de592fcbc256dafe3af2717f56d7c87761fbaccf4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
]
[[package]]
@@ -3377,6 +3381,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 +3414,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.33",
"syn 2.0.29",
"wasm-bindgen-shared",
]
@@ -3438,7 +3448,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 }
@@ -37,7 +37,6 @@ bitflags = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true, features = ["derive", "string"], optional = true }
colored = { workspace = true }
fern = { version = "0.6.1" }
glob = { workspace = true }
globset = { workspace = true }
imperative = { version = "1.0.4" }
@@ -71,6 +70,8 @@ strum = { workspace = true }
strum_macros = { workspace = true }
thiserror = { workspace = true }
toml = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
typed-arena = { version = "2.0.2" }
unicode-width = { workspace = true }
unicode_names2 = { version = "0.6.0", git = "https://github.com/youknowone/unicode_names2.git", rev = "4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde" }

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

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

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

@@ -898,7 +898,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 +919,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

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

@@ -36,6 +36,9 @@ use crate::settings::{flags, Settings};
use crate::source_kind::SourceKind;
use crate::{directives, fs};
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
const CARGO_PKG_REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
/// A [`Result`]-like type that returns both data and an error. Used to return
/// diagnostics even in the face of parse errors, since many diagnostics can be
/// generated without a full AST.
@@ -540,9 +543,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 +553,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 +581,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 +591,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

@@ -4,16 +4,17 @@ use std::sync::Mutex;
use anyhow::Result;
use colored::Colorize;
use fern;
use log::Level;
use once_cell::sync::Lazy;
use ruff_python_parser::{ParseError, ParseErrorType};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
use ruff_notebook::Notebook;
use ruff_python_parser::{ParseError, ParseErrorType};
use ruff_source_file::{OneIndexed, SourceCode, SourceLocation};
use crate::fs;
use crate::source_kind::SourceKind;
use ruff_notebook::Notebook;
pub static WARNINGS: Lazy<Mutex<Vec<&'static str>>> = Lazy::new(Mutex::default);
@@ -90,49 +91,27 @@ pub enum LogLevel {
impl LogLevel {
#[allow(clippy::trivially_copy_pass_by_ref)]
const fn level_filter(&self) -> log::LevelFilter {
const fn tracing_level(&self) -> tracing::Level {
match self {
LogLevel::Default => log::LevelFilter::Info,
LogLevel::Verbose => log::LevelFilter::Debug,
LogLevel::Quiet => log::LevelFilter::Off,
LogLevel::Silent => log::LevelFilter::Off,
LogLevel::Default => tracing::Level::INFO,
LogLevel::Verbose => tracing::Level::DEBUG,
LogLevel::Quiet => tracing::Level::WARN,
LogLevel::Silent => tracing::Level::ERROR,
}
}
}
/// Log level priorities: 1. `RUST_LOG=`, 2. explicit CLI log level, 3. default to info
pub fn set_up_logging(level: &LogLevel) -> Result<()> {
fern::Dispatch::new()
.format(|out, message, record| match record.level() {
Level::Error => {
out.finish(format_args!(
"{}{} {}",
"error".red().bold(),
":".bold(),
message
));
}
Level::Warn => {
out.finish(format_args!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
message
));
}
Level::Info | Level::Debug | Level::Trace => {
out.finish(format_args!(
"{}[{}][{}] {}",
chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
record.target(),
record.level(),
message
));
}
})
.level(level.level_filter())
.level_for("globset", log::LevelFilter::Warn)
.chain(std::io::stderr())
.apply()?;
let filter_layer = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
EnvFilter::builder()
.with_default_directive(level.tracing_level().into())
.parse_lossy("")
});
tracing_subscriber::registry()
.with(filter_layer)
.with(tracing_subscriber::fmt::layer())
.init();
Ok(())
}

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

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

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

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

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

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

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

@@ -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 }
@@ -64,7 +64,7 @@ shellexpand = { workspace = true }
similar = { workspace = true }
strum = { workspace = true, features = [] }
thiserror = { workspace = true }
tracing = { workspace = true, features = ["log"] }
tracing = { workspace = true }
walkdir = { version = "2.3.2" }
wild = { version = "2" }
@@ -79,7 +79,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

@@ -341,7 +341,7 @@ fn nursery_group_selector_preview_enabled() {
Found 2 errors.
----- stderr -----
warning: The `NURSERY` selector has been deprecated.
warning: The `NURSERY` selector has been deprecated. Use the `PREVIEW` selector instead.
"###);
}
@@ -439,21 +439,38 @@ fn preview_disabled_prefix_empty() {
}
#[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("I=42\n"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: Selection `PREVIEW` has no effect because the `--preview` flag was not included.
"###);
}
#[test]
fn preview_enabled_group_selector() {
// `--select PREVIEW` is okay with the `--preview` flag and shouldn't warn
let args = ["--select", "PREVIEW", "--preview"];
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
.args(STDIN_BASE_OPTIONS)
.args(args)
.pass_stdin("I=42\n"), @r###"
success: false
exit_code: 2
exit_code: 1
----- stdout -----
-:1:1: CPY001 Missing copyright notice at top of file
-:1:2: E225 Missing whitespace around operator
Found 2 errors.
----- stderr -----
error: invalid value 'PREVIEW' for '--select <RULE_CODE>'
For more information, try '--help'.
"###);
}
@@ -466,13 +483,13 @@ fn preview_enabled_group_ignore() {
.args(args)
.pass_stdin("I=42\n"), @r###"
success: false
exit_code: 2
exit_code: 1
----- stdout -----
-:1:1: E741 Ambiguous variable name: `I`
-:1:2: E225 Missing whitespace around operator
Found 2 errors.
----- stderr -----
error: invalid value 'PREVIEW' for '--ignore <RULE_CODE>'
For more information, try '--help'.
"###);
}

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

@@ -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::TooManyPublicMethods, 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

View File

@@ -319,8 +319,7 @@ pub struct Options {
"#
)]
/// The line length to use when enforcing long-lines violations (like
/// `E501`). Must be greater than `0` and less than or equal to `320`.
#[cfg_attr(feature = "schemars", schemars(range(min = 1, max = 320)))]
/// `E501`). Must be greater than `0`.
pub line_length: Option<LineLength>,
#[option(
default = "4",

View File

@@ -476,7 +476,7 @@ Ruff supports two command-line flags that alter its exit code behavior:
`--exit-non-zero-on-fix` can result in a non-zero exit code even if no violations remain after
autofixing.
## Shell autocompletion
## Autocompletion
Ruff supports autocompletion for most shells. A shell-specific completion script can be generated
by `ruff generate-shell-completion <SHELL>`, where `<SHELL>` is one of `bash`, `elvish`, `fig`, `fish`,

View File

@@ -52,7 +52,6 @@ natively, 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-format/)
- [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/)
@@ -157,7 +156,6 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [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

@@ -247,7 +247,7 @@ This tutorial has focused on Ruff's command-line interface, but Ruff can also be
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.0.290
rev: v0.0.289
hooks:
- id: ruff
```

View File

@@ -23,7 +23,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
```
@@ -33,7 +33,7 @@ Or, to enable autofix:
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.0.290
rev: v0.0.289
hooks:
- id: ruff
args: [ --fix, --exit-non-zero-on-fix ]
@@ -44,7 +44,7 @@ Or, to run the hook on Jupyter Notebooks too:
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.0.290
rev: v0.0.289
hooks:
- id: ruff
types_or: [python, pyi, jupyter]

View File

@@ -5,7 +5,7 @@ build-backend = "maturin"
[project]
name = "ruff"
version = "0.0.290"
version = "0.0.289"
description = "An extremely fast Python linter, written in Rust."
authors = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }]
maintainers = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }]

13
ruff.schema.json generated
View File

@@ -376,7 +376,7 @@
]
},
"line-length": {
"description": "The line length to use when enforcing long-lines violations (like `E501`). Must be greater than `0` and less than or equal to `320`.",
"description": "The line length to use when enforcing long-lines violations (like `E501`). Must be greater than `0`.",
"anyOf": [
{
"$ref": "#/definitions/LineLength"
@@ -384,9 +384,7 @@
{
"type": "null"
}
],
"maximum": 320.0,
"minimum": 1.0
]
},
"logger-objects": {
"description": "A list of objects that should be treated equivalently to a `logging.Logger` object.\n\nThis is useful for ensuring proper diagnostics (e.g., to identify `logging` deprecations and other best-practices) for projects that re-export a `logging.Logger` object from a common module.\n\nFor example, if you have a module `logging_setup.py` with the following contents: ```python import logging\n\nlogger = logging.getLogger(__name__) ```\n\nAdding `\"logging_setup.logger\"` to `logger-objects` will ensure that `logging_setup.logger` is treated as a `logging.Logger` object when imported from other modules (e.g., `from logging_setup import logger`).",
@@ -2135,11 +2133,6 @@
"ISC001",
"ISC002",
"ISC003",
"LOG",
"LOG0",
"LOG00",
"LOG001",
"LOG009",
"N",
"N8",
"N80",
@@ -2168,7 +2161,6 @@
"NPY001",
"NPY002",
"NPY003",
"NURSERY",
"PD",
"PD0",
"PD00",
@@ -2202,7 +2194,6 @@
"PERF40",
"PERF401",
"PERF402",
"PERF403",
"PGH",
"PGH0",
"PGH00",