Compare commits
77 Commits
david/sqla
...
alex/panic
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
074904f90d | ||
|
|
f57917becd | ||
|
|
82a7598aa8 | ||
|
|
e2ec2bc306 | ||
|
|
b413a6dec4 | ||
|
|
e19c050386 | ||
|
|
5a2aba237b | ||
|
|
ca5f099481 | ||
|
|
a722df6a73 | ||
|
|
dec4154c8a | ||
|
|
69d1bfbebc | ||
|
|
90b29c9e87 | ||
|
|
0ebdebddd8 | ||
|
|
d5546508cf | ||
|
|
3ac58b47bd | ||
|
|
a2b138e789 | ||
|
|
ff0ed4e752 | ||
|
|
bc8efa2fd8 | ||
|
|
4249736d74 | ||
|
|
0181568fb5 | ||
|
|
8cc7c993de | ||
|
|
315bf80eed | ||
|
|
0138cd238a | ||
|
|
5e42926eee | ||
|
|
ddb7645e9d | ||
|
|
3f63ea4b50 | ||
|
|
c8851ecf70 | ||
|
|
d442433e93 | ||
|
|
c055d665ef | ||
|
|
7a578ce833 | ||
|
|
34f7a04ef7 | ||
|
|
c9fe4e2703 | ||
|
|
fbeeb050af | ||
|
|
4fdb4e8219 | ||
|
|
c548ef2027 | ||
|
|
5a9d6a91ea | ||
|
|
c9155d5e72 | ||
|
|
8647844572 | ||
|
|
1dcb7f89f1 | ||
|
|
c1c45a6a13 | ||
|
|
c51727708a | ||
|
|
27912d46b1 | ||
|
|
71540c03b6 | ||
|
|
aa27925e87 | ||
|
|
5c320990f7 | ||
|
|
24ed28e314 | ||
|
|
2d0681da08 | ||
|
|
29bf2cd201 | ||
|
|
1b44d7e2a7 | ||
|
|
a2fb2ee06c | ||
|
|
3e00221a6c | ||
|
|
5dc0079e78 | ||
|
|
f7528bd325 | ||
|
|
59b92b3522 | ||
|
|
9ceec359a0 | ||
|
|
2dd412c89a | ||
|
|
951766d1fb | ||
|
|
7bf50e70a7 | ||
|
|
ff7086d9ad | ||
|
|
d2aabeaaa2 | ||
|
|
8293afe2ae | ||
|
|
aaadf16b1b | ||
|
|
c343e94ac5 | ||
|
|
270b8d1d14 | ||
|
|
f3714fd3c1 | ||
|
|
a9be810c38 | ||
|
|
0bec5c0362 | ||
|
|
9490fbf1e1 | ||
|
|
8727a7b179 | ||
|
|
4e4d018344 | ||
|
|
a9899af98a | ||
|
|
aea2bc2308 | ||
|
|
c35bf8f441 | ||
|
|
426125f5c0 | ||
|
|
a0b18bc153 | ||
|
|
11901384b4 | ||
|
|
dc2f0a86fd |
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -298,7 +298,7 @@ jobs:
|
||||
# sync, not just public items. Eventually we should do this for all
|
||||
# crates; for now add crates here as they are warning-clean to prevent
|
||||
# regression.
|
||||
- run: cargo doc --no-deps -p ty_python_semantic -p ty -p ty_test -p ruff_db --document-private-items
|
||||
- run: cargo doc --no-deps -p ty_python_semantic -p ty -p ty_test -p ruff_db -p ruff_python_formatter --document-private-items
|
||||
env:
|
||||
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
|
||||
RUSTDOCFLAGS: "-D warnings"
|
||||
|
||||
53
.github/workflows/mypy_primer.yaml
vendored
53
.github/workflows/mypy_primer.yaml
vendored
@@ -47,6 +47,7 @@ jobs:
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
shared-key: "mypy-primer"
|
||||
workspaces: "ruff"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
@@ -86,6 +87,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
shared-key: "mypy-primer"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup show
|
||||
@@ -105,3 +107,54 @@ jobs:
|
||||
with:
|
||||
name: mypy_primer_memory_diff
|
||||
path: mypy_primer_memory.diff
|
||||
|
||||
# Runs mypy twice against the same ty version to catch any non-deterministic behavior (ideally).
|
||||
# The job is disabled for now because there are some non-deterministic diagnostics.
|
||||
mypy_primer_same_revision:
|
||||
name: Run mypy_primer on same revision
|
||||
runs-on: ${{ github.repository == 'astral-sh/ruff' && 'depot-ubuntu-22.04-32' || 'ubuntu-latest' }}
|
||||
timeout-minutes: 20
|
||||
# TODO: Enable once we fixed the non-deterministic diagnostics
|
||||
if: false
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
path: ruff
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
|
||||
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
|
||||
with:
|
||||
workspaces: "ruff"
|
||||
shared-key: "mypy-primer"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
run: rustup show
|
||||
|
||||
- name: Run determinism check
|
||||
env:
|
||||
BASE_REVISION: ${{ github.event.pull_request.head.sha }}
|
||||
PRIMER_SELECTOR: crates/ty_python_semantic/resources/primer/good.txt
|
||||
CLICOLOR_FORCE: "1"
|
||||
DIFF_FILE: mypy_primer_determinism.diff
|
||||
run: |
|
||||
cd ruff
|
||||
scripts/mypy_primer.sh
|
||||
|
||||
- name: Check for non-determinism
|
||||
run: |
|
||||
# Remove ANSI color codes for checking
|
||||
sed -e 's/\x1b\[[0-9;]*m//g' mypy_primer_determinism.diff > mypy_primer_determinism_clean.diff
|
||||
|
||||
# Check if there are any differences (non-determinism)
|
||||
if [ -s mypy_primer_determinism_clean.diff ]; then
|
||||
echo "ERROR: Non-deterministic output detected!"
|
||||
echo "The following differences were found when running ty twice on the same commit:"
|
||||
cat mypy_primer_determinism_clean.diff
|
||||
exit 1
|
||||
else
|
||||
echo "✓ Output is deterministic"
|
||||
fi
|
||||
|
||||
42
CHANGELOG.md
42
CHANGELOG.md
@@ -1,5 +1,47 @@
|
||||
# Changelog
|
||||
|
||||
## 0.14.9
|
||||
|
||||
Released on 2025-12-11.
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`ruff`\] New `RUF100` diagnostics for unused range suppressions ([#21783](https://github.com/astral-sh/ruff/pull/21783))
|
||||
- \[`pylint`\] Detect subclasses of builtin exceptions (`PLW0133`) ([#21382](https://github.com/astral-sh/ruff/pull/21382))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fix comment placement in lambda parameters ([#21868](https://github.com/astral-sh/ruff/pull/21868))
|
||||
- Skip over trivia tokens after re-lexing ([#21895](https://github.com/astral-sh/ruff/pull/21895))
|
||||
- \[`flake8-bandit`\] Fix false positive when using non-standard `CSafeLoader` path (S506). ([#21830](https://github.com/astral-sh/ruff/pull/21830))
|
||||
- \[`flake8-bugbear`\] Accept immutable slice default arguments (`B008`) ([#21823](https://github.com/astral-sh/ruff/pull/21823))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`pydocstyle`\] Suppress `D417` for parameters with `Unpack` annotations ([#21816](https://github.com/astral-sh/ruff/pull/21816))
|
||||
|
||||
### Performance
|
||||
|
||||
- Use `memchr` for computing line indexes ([#21838](https://github.com/astral-sh/ruff/pull/21838))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Document `*.pyw` is included by default in preview ([#21885](https://github.com/astral-sh/ruff/pull/21885))
|
||||
- Document range suppressions, reorganize suppression docs ([#21884](https://github.com/astral-sh/ruff/pull/21884))
|
||||
- Update mkdocs-material to 9.7.0 (Insiders now free) ([#21797](https://github.com/astral-sh/ruff/pull/21797))
|
||||
|
||||
### Contributors
|
||||
|
||||
- [@Avasam](https://github.com/Avasam)
|
||||
- [@MichaReiser](https://github.com/MichaReiser)
|
||||
- [@charliermarsh](https://github.com/charliermarsh)
|
||||
- [@amyreese](https://github.com/amyreese)
|
||||
- [@phongddo](https://github.com/phongddo)
|
||||
- [@prakhar1144](https://github.com/prakhar1144)
|
||||
- [@mahiro72](https://github.com/mahiro72)
|
||||
- [@ntBre](https://github.com/ntBre)
|
||||
- [@LoicRiegel](https://github.com/LoicRiegel)
|
||||
|
||||
## 0.14.8
|
||||
|
||||
Released on 2025-12-04.
|
||||
|
||||
95
Cargo.lock
generated
95
Cargo.lock
generated
@@ -254,6 +254,21 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -944,6 +959,18 @@ dependencies = [
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "datatest-stable"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a867d7322eb69cf3a68a5426387a25b45cb3b9c5ee41023ee6cea92e2afadd82"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"fancy-regex",
|
||||
"libtest-mimic 0.8.1",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive-where"
|
||||
version = "1.6.0"
|
||||
@@ -1016,7 +1043,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1108,7 +1135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1138,6 +1165,17 @@ dependencies = [
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
@@ -1238,9 +1276,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size-derive2"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff47daa61505c85af126e9dd64af6a342a33dc0cccfe1be74ceadc7d352e6efd"
|
||||
checksum = "ab21d7bd2c625f2064f04ce54bcb88bc57c45724cde45cba326d784e22d3f71a"
|
||||
dependencies = [
|
||||
"attribute-derive",
|
||||
"quote",
|
||||
@@ -1249,14 +1287,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "get-size2"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac7bb8710e1f09672102be7ddf39f764d8440ae74a9f4e30aaa4820dcdffa4af"
|
||||
checksum = "879272b0de109e2b67b39fcfe3d25fdbba96ac07e44a254f5a0b4d7ff55340cb"
|
||||
dependencies = [
|
||||
"compact_str",
|
||||
"get-size-derive2",
|
||||
"hashbrown 0.16.1",
|
||||
"indexmap",
|
||||
"ordermap",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@@ -1624,7 +1663,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0"
|
||||
dependencies = [
|
||||
"console 0.15.11",
|
||||
"globset",
|
||||
"once_cell",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
@@ -1632,7 +1670,6 @@ dependencies = [
|
||||
"ron",
|
||||
"serde",
|
||||
"similar",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1763,7 +1800,7 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1918,6 +1955,18 @@ dependencies = [
|
||||
"threadpool",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libtest-mimic"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5297962ef19edda4ce33aaa484386e0a5b3d7f2f4e037cbeee00503ef6b29d33"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap",
|
||||
"escape8259",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
@@ -2233,9 +2282,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordermap"
|
||||
version = "0.5.12"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b100f7dd605611822d30e182214d3c02fdefce2d801d23993f6b6ba6ca1392af"
|
||||
checksum = "ed637741ced8fb240855d22a2b4f208dab7a06bcce73380162e5253000c16758"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@@ -2859,7 +2908,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.14.8"
|
||||
version = "0.14.9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -3117,7 +3166,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.14.8"
|
||||
version = "0.14.9"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"anyhow",
|
||||
@@ -3277,6 +3326,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"countme",
|
||||
"datatest-stable",
|
||||
"insta",
|
||||
"itertools 0.14.0",
|
||||
"memchr",
|
||||
@@ -3346,8 +3396,10 @@ dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"bstr",
|
||||
"compact_str",
|
||||
"datatest-stable",
|
||||
"get-size2",
|
||||
"insta",
|
||||
"itertools 0.14.0",
|
||||
"memchr",
|
||||
"ruff_annotate_snippets",
|
||||
"ruff_python_ast",
|
||||
@@ -3473,7 +3525,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.14.8"
|
||||
version = "0.14.9"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
@@ -3571,7 +3623,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3589,7 +3641,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
[[package]]
|
||||
name = "salsa"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0#59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
|
||||
dependencies = [
|
||||
"boxcar",
|
||||
"compact_str",
|
||||
@@ -3600,6 +3652,7 @@ dependencies = [
|
||||
"indexmap",
|
||||
"intrusive-collections",
|
||||
"inventory",
|
||||
"ordermap",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"rustc-hash",
|
||||
@@ -3613,12 +3666,12 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "salsa-macro-rules"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0#59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
|
||||
|
||||
[[package]]
|
||||
name = "salsa-macros"
|
||||
version = "0.24.0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0#59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0"
|
||||
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3972,7 +4025,7 @@ dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4308,7 +4361,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fe242ee9e646acec9ab73a5c540e8543ed1b107f0ce42be831e0775d423c396"
|
||||
dependencies = [
|
||||
"ignore",
|
||||
"libtest-mimic",
|
||||
"libtest-mimic 0.7.3",
|
||||
"snapbox",
|
||||
]
|
||||
|
||||
@@ -5026,7 +5079,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -81,6 +81,7 @@ compact_str = "0.9.0"
|
||||
criterion = { version = "0.7.0", default-features = false }
|
||||
crossbeam = { version = "0.8.4" }
|
||||
dashmap = { version = "6.0.1" }
|
||||
datatest-stable = { version = "0.3.3" }
|
||||
dir-test = { version = "0.4.0" }
|
||||
dunce = { version = "1.0.5" }
|
||||
drop_bomb = { version = "0.1.5" }
|
||||
@@ -88,7 +89,7 @@ etcetera = { version = "0.11.0" }
|
||||
fern = { version = "0.7.0" }
|
||||
filetime = { version = "0.2.23" }
|
||||
getrandom = { version = "0.3.1" }
|
||||
get-size2 = { version = "0.7.0", features = [
|
||||
get-size2 = { version = "0.7.3", features = [
|
||||
"derive",
|
||||
"smallvec",
|
||||
"hashbrown",
|
||||
@@ -129,7 +130,7 @@ memchr = { version = "2.7.1" }
|
||||
mimalloc = { version = "0.1.39" }
|
||||
natord = { version = "1.0.9" }
|
||||
notify = { version = "8.0.0" }
|
||||
ordermap = { version = "0.5.0" }
|
||||
ordermap = { version = "1.0.0" }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
path-slash = { version = "0.2.1" }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
@@ -146,7 +147,7 @@ regex-automata = { version = "0.4.9" }
|
||||
rustc-hash = { version = "2.0.0" }
|
||||
rustc-stable-hash = { version = "0.1.2" }
|
||||
# When updating salsa, make sure to also update the revision in `fuzz/Cargo.toml`
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0", default-features = false, features = [
|
||||
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "55e5e7d32fa3fc189276f35bb04c9438f9aedbd1", default-features = false, features = [
|
||||
"compact_str",
|
||||
"macros",
|
||||
"salsa_unstable",
|
||||
|
||||
@@ -147,8 +147,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.14.8/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.14.8/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.14.9/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.14.9/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -181,7 +181,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.14.8
|
||||
rev: v0.14.9
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff-check
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.14.8"
|
||||
version = "0.14.9"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -194,7 +194,7 @@ static SYMPY: Benchmark = Benchmark::new(
|
||||
max_dep_date: "2025-06-17",
|
||||
python_version: PythonVersion::PY312,
|
||||
},
|
||||
13000,
|
||||
13030,
|
||||
);
|
||||
|
||||
static TANJUN: Benchmark = Benchmark::new(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.14.8"
|
||||
version = "0.14.9"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -2,15 +2,40 @@ from abc import ABC, abstractmethod
|
||||
from contextlib import suppress
|
||||
|
||||
|
||||
class MyError(Exception):
|
||||
...
|
||||
|
||||
|
||||
class MySubError(MyError):
|
||||
...
|
||||
|
||||
|
||||
class MyValueError(ValueError):
|
||||
...
|
||||
|
||||
|
||||
class MyUserWarning(UserWarning):
|
||||
...
|
||||
|
||||
|
||||
# Violation test cases with builtin errors: PLW0133
|
||||
|
||||
|
||||
# Test case 1: Useless exception statement
|
||||
def func():
|
||||
AssertionError("This is an assertion error") # PLW0133
|
||||
MyError("This is a custom error") # PLW0133
|
||||
MySubError("This is a custom error") # PLW0133
|
||||
MyValueError("This is a custom value error") # PLW0133
|
||||
|
||||
|
||||
# Test case 2: Useless exception statement in try-except block
|
||||
def func():
|
||||
try:
|
||||
Exception("This is an exception") # PLW0133
|
||||
MyError("This is an exception") # PLW0133
|
||||
MySubError("This is an exception") # PLW0133
|
||||
MyValueError("This is an exception") # PLW0133
|
||||
except Exception as err:
|
||||
pass
|
||||
|
||||
@@ -19,6 +44,9 @@ def func():
|
||||
def func():
|
||||
if True:
|
||||
RuntimeError("This is an exception") # PLW0133
|
||||
MyError("This is an exception") # PLW0133
|
||||
MySubError("This is an exception") # PLW0133
|
||||
MyValueError("This is an exception") # PLW0133
|
||||
|
||||
|
||||
# Test case 4: Useless exception statement in class
|
||||
@@ -26,12 +54,18 @@ def func():
|
||||
class Class:
|
||||
def __init__(self):
|
||||
TypeError("This is an exception") # PLW0133
|
||||
MyError("This is an exception") # PLW0133
|
||||
MySubError("This is an exception") # PLW0133
|
||||
MyValueError("This is an exception") # PLW0133
|
||||
|
||||
|
||||
# Test case 5: Useless exception statement in function
|
||||
def func():
|
||||
def inner():
|
||||
IndexError("This is an exception") # PLW0133
|
||||
MyError("This is an exception") # PLW0133
|
||||
MySubError("This is an exception") # PLW0133
|
||||
MyValueError("This is an exception") # PLW0133
|
||||
|
||||
inner()
|
||||
|
||||
@@ -40,6 +74,9 @@ def func():
|
||||
def func():
|
||||
while True:
|
||||
KeyError("This is an exception") # PLW0133
|
||||
MyError("This is an exception") # PLW0133
|
||||
MySubError("This is an exception") # PLW0133
|
||||
MyValueError("This is an exception") # PLW0133
|
||||
|
||||
|
||||
# Test case 7: Useless exception statement in abstract class
|
||||
@@ -48,27 +85,58 @@ def func():
|
||||
@abstractmethod
|
||||
def method(self):
|
||||
NotImplementedError("This is an exception") # PLW0133
|
||||
MyError("This is an exception") # PLW0133
|
||||
MySubError("This is an exception") # PLW0133
|
||||
MyValueError("This is an exception") # PLW0133
|
||||
|
||||
|
||||
# Test case 8: Useless exception statement inside context manager
|
||||
def func():
|
||||
with suppress(AttributeError):
|
||||
with suppress(Exception):
|
||||
AttributeError("This is an exception") # PLW0133
|
||||
MyError("This is an exception") # PLW0133
|
||||
MySubError("This is an exception") # PLW0133
|
||||
MyValueError("This is an exception") # PLW0133
|
||||
|
||||
|
||||
# Test case 9: Useless exception statement in parentheses
|
||||
def func():
|
||||
(RuntimeError("This is an exception")) # PLW0133
|
||||
(MyError("This is an exception")) # PLW0133
|
||||
(MySubError("This is an exception")) # PLW0133
|
||||
(MyValueError("This is an exception")) # PLW0133
|
||||
|
||||
|
||||
# Test case 10: Useless exception statement in continuation
|
||||
def func():
|
||||
x = 1; (RuntimeError("This is an exception")); y = 2 # PLW0133
|
||||
x = 1; (MyError("This is an exception")); y = 2 # PLW0133
|
||||
x = 1; (MySubError("This is an exception")); y = 2 # PLW0133
|
||||
x = 1; (MyValueError("This is an exception")); y = 2 # PLW0133
|
||||
|
||||
|
||||
# Test case 11: Useless warning statement
|
||||
def func():
|
||||
UserWarning("This is an assertion error") # PLW0133
|
||||
UserWarning("This is a user warning") # PLW0133
|
||||
MyUserWarning("This is a custom user warning") # PLW0133
|
||||
|
||||
|
||||
# Test case 12: Useless exception statement at module level
|
||||
import builtins
|
||||
|
||||
builtins.TypeError("still an exception even though it's an Attribute") # PLW0133
|
||||
|
||||
PythonFinalizationError("Added in Python 3.13") # PLW0133
|
||||
|
||||
MyError("This is an exception") # PLW0133
|
||||
|
||||
MySubError("This is an exception") # PLW0133
|
||||
|
||||
MyValueError("This is an exception") # PLW0133
|
||||
|
||||
UserWarning("This is a user warning") # PLW0133
|
||||
|
||||
MyUserWarning("This is a custom user warning") # PLW0133
|
||||
|
||||
|
||||
# Non-violation test cases: PLW0133
|
||||
@@ -119,10 +187,3 @@ def func():
|
||||
def func():
|
||||
with suppress(AttributeError):
|
||||
raise AttributeError("This is an exception") # OK
|
||||
|
||||
|
||||
import builtins
|
||||
|
||||
builtins.TypeError("still an exception even though it's an Attribute")
|
||||
|
||||
PythonFinalizationError("Added in Python 3.13")
|
||||
|
||||
@@ -54,3 +54,35 @@ def f():
|
||||
# ruff:disable[E741,F841]
|
||||
I = 1 # noqa: E741,F841
|
||||
# ruff:enable[E741,F841]
|
||||
|
||||
|
||||
def f():
|
||||
# TODO: Duplicate codes should be counted as duplicate, not unused
|
||||
# ruff: disable[F841, F841]
|
||||
foo = 0
|
||||
|
||||
|
||||
def f():
|
||||
# Overlapping range suppressions, one should be marked as used,
|
||||
# and the other should trigger an unused suppression diagnostic
|
||||
# ruff: disable[F841]
|
||||
# ruff: disable[F841]
|
||||
foo = 0
|
||||
|
||||
|
||||
def f():
|
||||
# Multiple codes but only one is used
|
||||
# ruff: disable[E741, F401, F841]
|
||||
foo = 0
|
||||
|
||||
|
||||
def f():
|
||||
# Multiple codes but only two are used
|
||||
# ruff: disable[E741, F401, F841]
|
||||
I = 0
|
||||
|
||||
|
||||
def f():
|
||||
# Multiple codes but none are used
|
||||
# ruff: disable[E741, F401, F841]
|
||||
print("hello")
|
||||
|
||||
@@ -437,6 +437,15 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`Tokens`] for the parsed source file.
|
||||
///
|
||||
///
|
||||
/// Unlike [`Self::tokens`], this method always returns
|
||||
/// the tokens for the current file, even when within a parsed type annotation.
|
||||
pub(crate) fn source_tokens(&self) -> &'a Tokens {
|
||||
self.parsed.tokens()
|
||||
}
|
||||
|
||||
/// The [`Locator`] for the current file, which enables extraction of source code from byte
|
||||
/// offsets.
|
||||
pub(crate) const fn locator(&self) -> &'a Locator<'a> {
|
||||
|
||||
@@ -119,6 +119,9 @@ pub(crate) fn check_noqa(
|
||||
}
|
||||
}
|
||||
|
||||
// Diagnostics for unused/invalid range suppressions
|
||||
suppressions.check_suppressions(context, locator);
|
||||
|
||||
// Enforce that the noqa directive was actually used (RUF100), unless RUF100 was itself
|
||||
// suppressed.
|
||||
if context.is_rule_enabled(Rule::UnusedNOQA)
|
||||
@@ -140,8 +143,13 @@ pub(crate) fn check_noqa(
|
||||
Directive::All(directive) => {
|
||||
if matches.is_empty() {
|
||||
let edit = delete_comment(directive.range(), locator);
|
||||
let mut diagnostic = context
|
||||
.report_diagnostic(UnusedNOQA { codes: None }, directive.range());
|
||||
let mut diagnostic = context.report_diagnostic(
|
||||
UnusedNOQA {
|
||||
codes: None,
|
||||
kind: ruff::rules::UnusedNOQAKind::Noqa,
|
||||
},
|
||||
directive.range(),
|
||||
);
|
||||
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Unnecessary);
|
||||
diagnostic.set_fix(Fix::safe_edit(edit));
|
||||
}
|
||||
@@ -236,6 +244,7 @@ pub(crate) fn check_noqa(
|
||||
.map(|code| (*code).to_string())
|
||||
.collect(),
|
||||
}),
|
||||
kind: ruff::rules::UnusedNOQAKind::Noqa,
|
||||
},
|
||||
directive.range(),
|
||||
);
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::{self, Tokens, parenthesized_range};
|
||||
use ruff_python_ast::{self as ast, Arguments, ExceptHandler, Expr, ExprList, Parameters, Stmt};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_trivia::textwrap::dedent_to;
|
||||
use ruff_python_trivia::{
|
||||
CommentRanges, PythonWhitespace, SimpleTokenKind, SimpleTokenizer, has_leading_content,
|
||||
is_python_whitespace,
|
||||
PythonWhitespace, SimpleTokenKind, SimpleTokenizer, has_leading_content, is_python_whitespace,
|
||||
};
|
||||
use ruff_source_file::{LineRanges, NewlineWithTrailingNewline, UniversalNewlines};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
@@ -209,7 +208,7 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
arguments: &Arguments,
|
||||
parentheses: Parentheses,
|
||||
source: &str,
|
||||
comment_ranges: &CommentRanges,
|
||||
tokens: &Tokens,
|
||||
) -> Result<Edit> {
|
||||
// Partition into arguments before and after the argument to remove.
|
||||
let (before, after): (Vec<_>, Vec<_>) = arguments
|
||||
@@ -224,7 +223,7 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
.context("Unable to find argument")?;
|
||||
|
||||
let parenthesized_range =
|
||||
parenthesized_range(arg.value().into(), arguments.into(), comment_ranges, source)
|
||||
token::parenthesized_range(arg.value().into(), arguments.into(), tokens)
|
||||
.unwrap_or(arg.range());
|
||||
|
||||
if !after.is_empty() {
|
||||
@@ -270,25 +269,14 @@ pub(crate) fn remove_argument<T: Ranged>(
|
||||
///
|
||||
/// The new argument will be inserted before the first existing keyword argument in `arguments`, if
|
||||
/// there are any present. Otherwise, the new argument is added to the end of the argument list.
|
||||
pub(crate) fn add_argument(
|
||||
argument: &str,
|
||||
arguments: &Arguments,
|
||||
comment_ranges: &CommentRanges,
|
||||
source: &str,
|
||||
) -> Edit {
|
||||
pub(crate) fn add_argument(argument: &str, arguments: &Arguments, tokens: &Tokens) -> Edit {
|
||||
if let Some(ast::Keyword { range, value, .. }) = arguments.keywords.first() {
|
||||
let keyword = parenthesized_range(value.into(), arguments.into(), comment_ranges, source)
|
||||
.unwrap_or(*range);
|
||||
let keyword = parenthesized_range(value.into(), arguments.into(), tokens).unwrap_or(*range);
|
||||
Edit::insertion(format!("{argument}, "), keyword.start())
|
||||
} else if let Some(last) = arguments.arguments_source_order().last() {
|
||||
// Case 1: existing arguments, so append after the last argument.
|
||||
let last = parenthesized_range(
|
||||
last.value().into(),
|
||||
arguments.into(),
|
||||
comment_ranges,
|
||||
source,
|
||||
)
|
||||
.unwrap_or(last.range());
|
||||
let last = parenthesized_range(last.value().into(), arguments.into(), tokens)
|
||||
.unwrap_or(last.range());
|
||||
Edit::insertion(format!(", {argument}"), last.end())
|
||||
} else {
|
||||
// Case 2: no arguments. Add argument, without any trailing comma.
|
||||
|
||||
@@ -879,7 +879,7 @@ fn find_noqa_comments<'a>(
|
||||
exemption: &'a FileExemption,
|
||||
directives: &'a NoqaDirectives,
|
||||
noqa_line_for: &NoqaMapping,
|
||||
suppressions: &Suppressions,
|
||||
suppressions: &'a Suppressions,
|
||||
) -> Vec<Option<NoqaComment<'a>>> {
|
||||
// List of noqa comments, ordered to match up with `messages`
|
||||
let mut comments_by_line: Vec<Option<NoqaComment<'a>>> = vec![];
|
||||
|
||||
@@ -9,6 +9,11 @@ use crate::settings::LinterSettings;
|
||||
|
||||
// Rule-specific behavior
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/21382
|
||||
pub(crate) const fn is_custom_exception_checking_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
}
|
||||
|
||||
// https://github.com/astral-sh/ruff/pull/15541
|
||||
pub(crate) const fn is_suspicious_function_reference_enabled(settings: &LinterSettings) -> bool {
|
||||
settings.preview.is_enabled()
|
||||
|
||||
@@ -91,8 +91,8 @@ pub(crate) fn fastapi_redundant_response_model(checker: &Checker, function_def:
|
||||
response_model_arg,
|
||||
&call.arguments,
|
||||
Parentheses::Preserve,
|
||||
checker.locator().contents(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.map(Fix::unsafe_edit)
|
||||
});
|
||||
|
||||
@@ -74,12 +74,7 @@ pub(crate) fn map_without_explicit_strict(checker: &Checker, call: &ast::ExprCal
|
||||
checker
|
||||
.report_diagnostic(MapWithoutExplicitStrict, call.range())
|
||||
.set_fix(Fix::applicable_edit(
|
||||
add_argument(
|
||||
"strict=False",
|
||||
&call.arguments,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
),
|
||||
add_argument("strict=False", &call.arguments, checker.tokens()),
|
||||
Applicability::Unsafe,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::fmt::Write;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::helpers::is_docstring_stmt;
|
||||
use ruff_python_ast::name::QualifiedName;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Expr, ParameterWithDefault};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_semantic::analyze::function_type::is_stub;
|
||||
@@ -166,12 +166,7 @@ fn move_initialization(
|
||||
return None;
|
||||
}
|
||||
|
||||
let range = match parenthesized_range(
|
||||
default.into(),
|
||||
parameter.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
) {
|
||||
let range = match parenthesized_range(default.into(), parameter.into(), checker.tokens()) {
|
||||
Some(range) => range,
|
||||
None => default.range(),
|
||||
};
|
||||
@@ -194,13 +189,8 @@ fn move_initialization(
|
||||
"{} = {}",
|
||||
parameter.parameter.name(),
|
||||
locator.slice(
|
||||
parenthesized_range(
|
||||
default.into(),
|
||||
parameter.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source()
|
||||
)
|
||||
.unwrap_or(default.range())
|
||||
parenthesized_range(default.into(), parameter.into(), checker.tokens())
|
||||
.unwrap_or(default.range())
|
||||
)
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -92,12 +92,7 @@ pub(crate) fn no_explicit_stacklevel(checker: &Checker, call: &ast::ExprCall) {
|
||||
}
|
||||
let mut diagnostic = checker.report_diagnostic(NoExplicitStacklevel, call.func.range());
|
||||
|
||||
let edit = add_argument(
|
||||
"stacklevel=2",
|
||||
&call.arguments,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
);
|
||||
let edit = add_argument("stacklevel=2", &call.arguments, checker.tokens());
|
||||
|
||||
diagnostic.set_fix(Fix::unsafe_edit(edit));
|
||||
}
|
||||
|
||||
@@ -70,12 +70,7 @@ pub(crate) fn zip_without_explicit_strict(checker: &Checker, call: &ast::ExprCal
|
||||
checker
|
||||
.report_diagnostic(ZipWithoutExplicitStrict, call.range())
|
||||
.set_fix(Fix::applicable_edit(
|
||||
add_argument(
|
||||
"strict=False",
|
||||
&call.arguments,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
),
|
||||
add_argument("strict=False", &call.arguments, checker.tokens()),
|
||||
Applicability::Unsafe,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::ExprGenerator;
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::TokenKind;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -142,13 +142,9 @@ pub(crate) fn unnecessary_generator_list(checker: &Checker, call: &ast::ExprCall
|
||||
if *parenthesized {
|
||||
// The generator's range will include the innermost parentheses, but it could be
|
||||
// surrounded by additional parentheses.
|
||||
let range = parenthesized_range(
|
||||
argument.into(),
|
||||
(&call.arguments).into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(argument.range());
|
||||
let range =
|
||||
parenthesized_range(argument.into(), (&call.arguments).into(), checker.tokens())
|
||||
.unwrap_or(argument.range());
|
||||
|
||||
// The generator always parenthesizes the expression; trim the parentheses.
|
||||
let generator = checker.generator().expr(argument);
|
||||
|
||||
@@ -2,8 +2,8 @@ use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::ExprGenerator;
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::TokenKind;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -147,13 +147,9 @@ pub(crate) fn unnecessary_generator_set(checker: &Checker, call: &ast::ExprCall)
|
||||
if *parenthesized {
|
||||
// The generator's range will include the innermost parentheses, but it could be
|
||||
// surrounded by additional parentheses.
|
||||
let range = parenthesized_range(
|
||||
argument.into(),
|
||||
(&call.arguments).into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(argument.range());
|
||||
let range =
|
||||
parenthesized_range(argument.into(), (&call.arguments).into(), checker.tokens())
|
||||
.unwrap_or(argument.range());
|
||||
|
||||
// The generator always parenthesizes the expression; trim the parentheses.
|
||||
let generator = checker.generator().expr(argument);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::TokenKind;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -89,13 +89,9 @@ pub(crate) fn unnecessary_list_comprehension_set(checker: &Checker, call: &ast::
|
||||
|
||||
// If the list comprehension is parenthesized, remove the parentheses in addition to
|
||||
// removing the brackets.
|
||||
let replacement_range = parenthesized_range(
|
||||
argument.into(),
|
||||
(&call.arguments).into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or_else(|| argument.range());
|
||||
let replacement_range =
|
||||
parenthesized_range(argument.into(), (&call.arguments).into(), checker.tokens())
|
||||
.unwrap_or_else(|| argument.range());
|
||||
|
||||
let span = argument.range().add_start(one).sub_end(one);
|
||||
let replacement =
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Expr, Operator};
|
||||
use ruff_python_trivia::is_python_whitespace;
|
||||
use ruff_source_file::LineRanges;
|
||||
@@ -88,13 +88,7 @@ pub(crate) fn explicit(checker: &Checker, expr: &Expr) {
|
||||
checker.report_diagnostic(ExplicitStringConcatenation, expr.range());
|
||||
|
||||
let is_parenthesized = |expr: &Expr| {
|
||||
parenthesized_range(
|
||||
expr.into(),
|
||||
bin_op.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
)
|
||||
.is_some()
|
||||
parenthesized_range(expr.into(), bin_op.into(), checker.tokens()).is_some()
|
||||
};
|
||||
// If either `left` or `right` is parenthesized, generating
|
||||
// a fix would be too involved. Just report the diagnostic.
|
||||
|
||||
@@ -111,7 +111,6 @@ pub(crate) fn exc_info_outside_except_handler(checker: &Checker, call: &ExprCall
|
||||
}
|
||||
|
||||
let arguments = &call.arguments;
|
||||
let source = checker.source();
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(ExcInfoOutsideExceptHandler, exc_info.range);
|
||||
|
||||
@@ -120,8 +119,8 @@ pub(crate) fn exc_info_outside_except_handler(checker: &Checker, call: &ExprCall
|
||||
exc_info,
|
||||
arguments,
|
||||
Parentheses::Preserve,
|
||||
source,
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
checker.tokens(),
|
||||
)?;
|
||||
Ok(Fix::unsafe_edit(edit))
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ use itertools::Itertools;
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_stdlib::identifiers::is_identifier;
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -129,8 +129,8 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &Checker, call: &ast::ExprCall) {
|
||||
keyword,
|
||||
&call.arguments,
|
||||
Parentheses::Preserve,
|
||||
checker.locator().contents(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.map(Fix::safe_edit)
|
||||
});
|
||||
@@ -158,8 +158,7 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &Checker, call: &ast::ExprCall) {
|
||||
parenthesized_range(
|
||||
value.into(),
|
||||
dict.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
checker.tokens()
|
||||
)
|
||||
.unwrap_or(value.range())
|
||||
)
|
||||
|
||||
@@ -73,11 +73,11 @@ pub(crate) fn unnecessary_range_start(checker: &Checker, call: &ast::ExprCall) {
|
||||
let mut diagnostic = checker.report_diagnostic(UnnecessaryRangeStart, start.range());
|
||||
diagnostic.try_set_fix(|| {
|
||||
remove_argument(
|
||||
&start,
|
||||
start,
|
||||
&call.arguments,
|
||||
Parentheses::Preserve,
|
||||
checker.locator().contents(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.map(Fix::safe_edit)
|
||||
});
|
||||
|
||||
@@ -160,20 +160,16 @@ fn generate_fix(
|
||||
) -> anyhow::Result<Fix> {
|
||||
let locator = checker.locator();
|
||||
let source = locator.contents();
|
||||
let tokens = checker.tokens();
|
||||
|
||||
let deletion = remove_argument(
|
||||
generic_base,
|
||||
arguments,
|
||||
Parentheses::Preserve,
|
||||
source,
|
||||
checker.comment_ranges(),
|
||||
tokens,
|
||||
)?;
|
||||
let insertion = add_argument(
|
||||
locator.slice(generic_base),
|
||||
arguments,
|
||||
checker.comment_ranges(),
|
||||
source,
|
||||
);
|
||||
let insertion = add_argument(locator.slice(generic_base), arguments, tokens);
|
||||
|
||||
Ok(Fix::unsafe_edits(deletion, [insertion]))
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use ruff_python_ast::{
|
||||
helpers::{pep_604_union, typing_optional},
|
||||
name::Name,
|
||||
operator_precedence::OperatorPrecedence,
|
||||
parenthesize::parenthesized_range,
|
||||
token::{Tokens, parenthesized_range},
|
||||
};
|
||||
use ruff_python_semantic::analyze::typing::{traverse_literal, traverse_union};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
@@ -243,16 +243,12 @@ fn create_fix(
|
||||
let union_expr = pep_604_union(&[new_literal_expr, none_expr]);
|
||||
|
||||
// Check if we need parentheses to preserve operator precedence
|
||||
let content = if needs_parentheses_for_precedence(
|
||||
semantic,
|
||||
literal_expr,
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
) {
|
||||
format!("({})", checker.generator().expr(&union_expr))
|
||||
} else {
|
||||
checker.generator().expr(&union_expr)
|
||||
};
|
||||
let content =
|
||||
if needs_parentheses_for_precedence(semantic, literal_expr, checker.tokens()) {
|
||||
format!("({})", checker.generator().expr(&union_expr))
|
||||
} else {
|
||||
checker.generator().expr(&union_expr)
|
||||
};
|
||||
|
||||
let union_edit = Edit::range_replacement(content, literal_expr.range());
|
||||
Fix::applicable_edit(union_edit, applicability)
|
||||
@@ -278,8 +274,7 @@ enum UnionKind {
|
||||
fn needs_parentheses_for_precedence(
|
||||
semantic: &ruff_python_semantic::SemanticModel,
|
||||
literal_expr: &Expr,
|
||||
comment_ranges: &ruff_python_trivia::CommentRanges,
|
||||
source: &str,
|
||||
tokens: &Tokens,
|
||||
) -> bool {
|
||||
// Get the parent expression to check if we're in a context that needs parentheses
|
||||
let Some(parent_expr) = semantic.current_expression_parent() else {
|
||||
@@ -287,14 +282,7 @@ fn needs_parentheses_for_precedence(
|
||||
};
|
||||
|
||||
// Check if the literal expression is already parenthesized
|
||||
if parenthesized_range(
|
||||
literal_expr.into(),
|
||||
parent_expr.into(),
|
||||
comment_ranges,
|
||||
source,
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
if parenthesized_range(literal_expr.into(), parent_expr.into(), tokens).is_some() {
|
||||
return false; // Already parenthesized, don't add more
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use libcst_native::{
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::helpers::Truthiness;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{
|
||||
self as ast, AnyNodeRef, Arguments, BoolOp, ExceptHandler, Expr, Keyword, Stmt, UnaryOp,
|
||||
@@ -303,8 +303,7 @@ pub(crate) fn unittest_assertion(
|
||||
parenthesized_range(
|
||||
expr.into(),
|
||||
checker.semantic().current_statement().into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.unwrap_or(expr.range()),
|
||||
)));
|
||||
|
||||
@@ -768,8 +768,8 @@ fn check_fixture_decorator(checker: &Checker, func_name: &str, decorator: &Decor
|
||||
keyword,
|
||||
arguments,
|
||||
edits::Parentheses::Preserve,
|
||||
checker.locator().contents(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.map(Fix::unsafe_edit)
|
||||
});
|
||||
|
||||
@@ -2,10 +2,9 @@ use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::{Tokens, parenthesized_range};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprCall, ExprContext, StringLiteralFlags};
|
||||
use ruff_python_codegen::Generator;
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
@@ -322,18 +321,8 @@ fn elts_to_csv(elts: &[Expr], generator: Generator, flags: StringLiteralFlags) -
|
||||
/// ```
|
||||
///
|
||||
/// This method assumes that the first argument is a string.
|
||||
fn get_parametrize_name_range(
|
||||
call: &ExprCall,
|
||||
expr: &Expr,
|
||||
comment_ranges: &CommentRanges,
|
||||
source: &str,
|
||||
) -> Option<TextRange> {
|
||||
parenthesized_range(
|
||||
expr.into(),
|
||||
(&call.arguments).into(),
|
||||
comment_ranges,
|
||||
source,
|
||||
)
|
||||
fn get_parametrize_name_range(call: &ExprCall, expr: &Expr, tokens: &Tokens) -> Option<TextRange> {
|
||||
parenthesized_range(expr.into(), (&call.arguments).into(), tokens)
|
||||
}
|
||||
|
||||
/// PT006
|
||||
@@ -349,13 +338,8 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr
|
||||
if names.len() > 1 {
|
||||
match names_type {
|
||||
types::ParametrizeNameType::Tuple => {
|
||||
let name_range = get_parametrize_name_range(
|
||||
call,
|
||||
expr,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(expr.range());
|
||||
let name_range = get_parametrize_name_range(call, expr, checker.tokens())
|
||||
.unwrap_or(expr.range());
|
||||
let mut diagnostic = checker.report_diagnostic(
|
||||
PytestParametrizeNamesWrongType {
|
||||
single_argument: false,
|
||||
@@ -386,13 +370,8 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr
|
||||
)));
|
||||
}
|
||||
types::ParametrizeNameType::List => {
|
||||
let name_range = get_parametrize_name_range(
|
||||
call,
|
||||
expr,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(expr.range());
|
||||
let name_range = get_parametrize_name_range(call, expr, checker.tokens())
|
||||
.unwrap_or(expr.range());
|
||||
let mut diagnostic = checker.report_diagnostic(
|
||||
PytestParametrizeNamesWrongType {
|
||||
single_argument: false,
|
||||
|
||||
@@ -10,7 +10,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::helpers::{Truthiness, contains_effect};
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_codegen::Generator;
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
|
||||
@@ -800,14 +800,9 @@ fn is_short_circuit(
|
||||
edit = Some(get_short_circuit_edit(
|
||||
value,
|
||||
TextRange::new(
|
||||
parenthesized_range(
|
||||
furthest.into(),
|
||||
expr.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(furthest.range())
|
||||
.start(),
|
||||
parenthesized_range(furthest.into(), expr.into(), checker.tokens())
|
||||
.unwrap_or(furthest.range())
|
||||
.start(),
|
||||
expr.end(),
|
||||
),
|
||||
short_circuit_truthiness,
|
||||
@@ -828,14 +823,9 @@ fn is_short_circuit(
|
||||
edit = Some(get_short_circuit_edit(
|
||||
next_value,
|
||||
TextRange::new(
|
||||
parenthesized_range(
|
||||
furthest.into(),
|
||||
expr.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(furthest.range())
|
||||
.start(),
|
||||
parenthesized_range(furthest.into(), expr.into(), checker.tokens())
|
||||
.unwrap_or(furthest.range())
|
||||
.start(),
|
||||
expr.end(),
|
||||
),
|
||||
short_circuit_truthiness,
|
||||
|
||||
@@ -4,7 +4,7 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::helpers::{is_const_false, is_const_true};
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation};
|
||||
@@ -171,13 +171,8 @@ pub(crate) fn if_expr_with_true_false(
|
||||
checker
|
||||
.locator()
|
||||
.slice(
|
||||
parenthesized_range(
|
||||
test.into(),
|
||||
expr.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(test.range()),
|
||||
parenthesized_range(test.into(), expr.into(), checker.tokens())
|
||||
.unwrap_or(test.range()),
|
||||
)
|
||||
.to_string(),
|
||||
expr.range(),
|
||||
|
||||
@@ -4,10 +4,10 @@ use anyhow::Result;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::comparable::ComparableStmt;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::stmt_if::{IfElifBranch, if_elif_branches};
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_trivia::{CommentRanges, SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_source_file::LineRanges;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
@@ -99,7 +99,7 @@ pub(crate) fn if_with_same_arms(checker: &Checker, stmt_if: &ast::StmtIf) {
|
||||
¤t_branch,
|
||||
following_branch,
|
||||
checker.locator(),
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
)
|
||||
});
|
||||
}
|
||||
@@ -111,7 +111,7 @@ fn merge_branches(
|
||||
current_branch: &IfElifBranch,
|
||||
following_branch: &IfElifBranch,
|
||||
locator: &Locator,
|
||||
comment_ranges: &CommentRanges,
|
||||
tokens: &ruff_python_ast::token::Tokens,
|
||||
) -> Result<Fix> {
|
||||
// Identify the colon (`:`) at the end of the current branch's test.
|
||||
let Some(current_branch_colon) =
|
||||
@@ -127,12 +127,9 @@ fn merge_branches(
|
||||
);
|
||||
|
||||
// If the following test isn't parenthesized, consider parenthesizing it.
|
||||
let following_branch_test = if let Some(range) = parenthesized_range(
|
||||
following_branch.test.into(),
|
||||
stmt_if.into(),
|
||||
comment_ranges,
|
||||
locator.contents(),
|
||||
) {
|
||||
let following_branch_test = if let Some(range) =
|
||||
parenthesized_range(following_branch.test.into(), stmt_if.into(), tokens)
|
||||
{
|
||||
Cow::Borrowed(locator.slice(range))
|
||||
} else if matches!(
|
||||
following_branch.test,
|
||||
@@ -153,24 +150,19 @@ fn merge_branches(
|
||||
//
|
||||
// For example, if the current test is `x if x else y`, we should parenthesize it to
|
||||
// `(x if x else y) or ...`.
|
||||
let parenthesize_edit = if matches!(
|
||||
current_branch.test,
|
||||
Expr::Lambda(_) | Expr::Named(_) | Expr::If(_)
|
||||
) && parenthesized_range(
|
||||
current_branch.test.into(),
|
||||
stmt_if.into(),
|
||||
comment_ranges,
|
||||
locator.contents(),
|
||||
)
|
||||
.is_none()
|
||||
{
|
||||
Some(Edit::range_replacement(
|
||||
format!("({})", locator.slice(current_branch.test)),
|
||||
current_branch.test.range(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let parenthesize_edit =
|
||||
if matches!(
|
||||
current_branch.test,
|
||||
Expr::Lambda(_) | Expr::Named(_) | Expr::If(_)
|
||||
) && parenthesized_range(current_branch.test.into(), stmt_if.into(), tokens).is_none()
|
||||
{
|
||||
Some(Edit::range_replacement(
|
||||
format!("({})", locator.slice(current_branch.test)),
|
||||
current_branch.test.range(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Fix::safe_edits(
|
||||
deletion_edit,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Arguments, CmpOp, Comprehension, Expr};
|
||||
use ruff_python_semantic::analyze::typing;
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
@@ -90,20 +90,10 @@ fn key_in_dict(checker: &Checker, left: &Expr, right: &Expr, operator: CmpOp, pa
|
||||
}
|
||||
|
||||
// Extract the exact range of the left and right expressions.
|
||||
let left_range = parenthesized_range(
|
||||
left.into(),
|
||||
parent,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(left.range());
|
||||
let right_range = parenthesized_range(
|
||||
right.into(),
|
||||
parent,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(right.range());
|
||||
let left_range =
|
||||
parenthesized_range(left.into(), parent, checker.tokens()).unwrap_or(left.range());
|
||||
let right_range =
|
||||
parenthesized_range(right.into(), parent, checker.tokens()).unwrap_or(right.range());
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(
|
||||
InDictKeys {
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::registry::Rule;
|
||||
use crate::rules::flake8_type_checking::helpers::quote_type_expression;
|
||||
use crate::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks if [PEP 613] explicit type aliases contain references to
|
||||
@@ -295,21 +295,20 @@ pub(crate) fn quoted_type_alias(
|
||||
let range = annotation_expr.range();
|
||||
let mut diagnostic = checker.report_diagnostic(QuotedTypeAlias, range);
|
||||
let fix_string = annotation_expr.value.to_string();
|
||||
|
||||
let fix_string = if (fix_string.contains('\n') || fix_string.contains('\r'))
|
||||
&& parenthesized_range(
|
||||
// Check for parenthesis outside string ("""...""")
|
||||
// Check for parentheses outside the string ("""...""")
|
||||
annotation_expr.into(),
|
||||
checker.semantic().current_statement().into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
checker.source_tokens(),
|
||||
)
|
||||
.is_none()
|
||||
&& parenthesized_range(
|
||||
// Check for parenthesis inside string """(...)"""
|
||||
// Check for parentheses inside the string """(...)"""
|
||||
expr.into(),
|
||||
annotation_expr.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.is_none()
|
||||
{
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::{Expr, ExprBinOp, ExprCall, Operator};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -89,11 +88,7 @@ pub(crate) fn path_constructor_current_directory(
|
||||
|
||||
let mut diagnostic = checker.report_diagnostic(PathConstructorCurrentDirectory, arg.range());
|
||||
|
||||
match parent_and_next_path_fragment_range(
|
||||
checker.semantic(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
) {
|
||||
match parent_and_next_path_fragment_range(checker.semantic(), checker.tokens()) {
|
||||
Some((parent_range, next_fragment_range)) => {
|
||||
let next_fragment_expr = checker.locator().slice(next_fragment_range);
|
||||
let call_expr = checker.locator().slice(call.range());
|
||||
@@ -116,7 +111,7 @@ pub(crate) fn path_constructor_current_directory(
|
||||
arguments,
|
||||
Parentheses::Preserve,
|
||||
checker.source(),
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
)?;
|
||||
Ok(Fix::applicable_edit(edit, applicability(call.range())))
|
||||
}),
|
||||
@@ -125,8 +120,7 @@ pub(crate) fn path_constructor_current_directory(
|
||||
|
||||
fn parent_and_next_path_fragment_range(
|
||||
semantic: &SemanticModel,
|
||||
comment_ranges: &CommentRanges,
|
||||
source: &str,
|
||||
tokens: &ruff_python_ast::token::Tokens,
|
||||
) -> Option<(TextRange, TextRange)> {
|
||||
let parent = semantic.current_expression_parent()?;
|
||||
|
||||
@@ -142,6 +136,6 @@ fn parent_and_next_path_fragment_range(
|
||||
|
||||
Some((
|
||||
parent.range(),
|
||||
parenthesized_range(right.into(), parent.into(), comment_ranges, source).unwrap_or(range),
|
||||
parenthesized_range(right.into(), parent.into(), tokens).unwrap_or(range),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::helpers::is_const_true;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::{Tokens, parenthesized_range};
|
||||
use ruff_python_ast::{self as ast, Keyword, Stmt};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Locator;
|
||||
@@ -91,7 +90,7 @@ pub(crate) fn inplace_argument(checker: &Checker, call: &ast::ExprCall) {
|
||||
call,
|
||||
keyword,
|
||||
statement,
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
checker.locator(),
|
||||
) {
|
||||
diagnostic.set_fix(fix);
|
||||
@@ -111,21 +110,16 @@ fn convert_inplace_argument_to_assignment(
|
||||
call: &ast::ExprCall,
|
||||
keyword: &Keyword,
|
||||
statement: &Stmt,
|
||||
comment_ranges: &CommentRanges,
|
||||
tokens: &Tokens,
|
||||
locator: &Locator,
|
||||
) -> Option<Fix> {
|
||||
// Add the assignment.
|
||||
let attr = call.func.as_attribute_expr()?;
|
||||
let insert_assignment = Edit::insertion(
|
||||
format!("{name} = ", name = locator.slice(attr.value.range())),
|
||||
parenthesized_range(
|
||||
call.into(),
|
||||
statement.into(),
|
||||
comment_ranges,
|
||||
locator.contents(),
|
||||
)
|
||||
.unwrap_or(call.range())
|
||||
.start(),
|
||||
parenthesized_range(call.into(), statement.into(), tokens)
|
||||
.unwrap_or(call.range())
|
||||
.start(),
|
||||
);
|
||||
|
||||
// Remove the `inplace` argument.
|
||||
@@ -134,7 +128,7 @@ fn convert_inplace_argument_to_assignment(
|
||||
&call.arguments,
|
||||
Parentheses::Preserve,
|
||||
locator.contents(),
|
||||
comment_ranges,
|
||||
tokens,
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::{
|
||||
self as ast, Expr, ExprEllipsisLiteral, ExprLambda, Identifier, Parameter,
|
||||
ParameterWithDefault, Parameters, Stmt,
|
||||
@@ -265,29 +265,19 @@ fn replace_trailing_ellipsis_with_original_expr(
|
||||
stmt: &Stmt,
|
||||
checker: &Checker,
|
||||
) -> String {
|
||||
let original_expr_range = parenthesized_range(
|
||||
(&lambda.body).into(),
|
||||
lambda.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
)
|
||||
.unwrap_or(lambda.body.range());
|
||||
let original_expr_range =
|
||||
parenthesized_range((&lambda.body).into(), lambda.into(), checker.tokens())
|
||||
.unwrap_or(lambda.body.range());
|
||||
|
||||
// This prevents the autofix of introducing a syntax error if the lambda's body is an
|
||||
// expression spanned across multiple lines. To avoid the syntax error we preserve
|
||||
// the parenthesis around the body.
|
||||
let original_expr_in_source = if parenthesized_range(
|
||||
lambda.into(),
|
||||
stmt.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
format!("({})", checker.locator().slice(original_expr_range))
|
||||
} else {
|
||||
checker.locator().slice(original_expr_range).to_string()
|
||||
};
|
||||
let original_expr_in_source =
|
||||
if parenthesized_range(lambda.into(), stmt.into(), checker.tokens()).is_some() {
|
||||
format!("({})", checker.locator().slice(original_expr_range))
|
||||
} else {
|
||||
checker.locator().slice(original_expr_range).to_string()
|
||||
};
|
||||
|
||||
let placeholder_ellipsis_start = generated.rfind("...").unwrap();
|
||||
let placeholder_ellipsis_end = placeholder_ellipsis_start + "...".len();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::{Tokens, parenthesized_range};
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
@@ -179,15 +179,14 @@ fn is_redundant_boolean_comparison(op: CmpOp, comparator: &Expr) -> Option<bool>
|
||||
|
||||
fn generate_redundant_comparison(
|
||||
compare: &ast::ExprCompare,
|
||||
comment_ranges: &ruff_python_trivia::CommentRanges,
|
||||
tokens: &Tokens,
|
||||
source: &str,
|
||||
comparator: &Expr,
|
||||
kind: bool,
|
||||
needs_wrap: bool,
|
||||
) -> String {
|
||||
let comparator_range =
|
||||
parenthesized_range(comparator.into(), compare.into(), comment_ranges, source)
|
||||
.unwrap_or(comparator.range());
|
||||
let comparator_range = parenthesized_range(comparator.into(), compare.into(), tokens)
|
||||
.unwrap_or(comparator.range());
|
||||
|
||||
let comparator_str = &source[comparator_range];
|
||||
|
||||
@@ -379,7 +378,7 @@ pub(crate) fn literal_comparisons(checker: &Checker, compare: &ast::ExprCompare)
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let comment_ranges = checker.comment_ranges();
|
||||
let tokens = checker.tokens();
|
||||
let source = checker.source();
|
||||
|
||||
let content = match (&*compare.ops, &*compare.comparators) {
|
||||
@@ -387,18 +386,13 @@ pub(crate) fn literal_comparisons(checker: &Checker, compare: &ast::ExprCompare)
|
||||
if let Some(kind) = is_redundant_boolean_comparison(*op, &compare.left) {
|
||||
let needs_wrap = compare.left.range().start() != compare.range().start();
|
||||
generate_redundant_comparison(
|
||||
compare,
|
||||
comment_ranges,
|
||||
source,
|
||||
comparator,
|
||||
kind,
|
||||
needs_wrap,
|
||||
compare, tokens, source, comparator, kind, needs_wrap,
|
||||
)
|
||||
} else if let Some(kind) = is_redundant_boolean_comparison(*op, comparator) {
|
||||
let needs_wrap = comparator.range().end() != compare.range().end();
|
||||
generate_redundant_comparison(
|
||||
compare,
|
||||
comment_ranges,
|
||||
tokens,
|
||||
source,
|
||||
&compare.left,
|
||||
kind,
|
||||
@@ -410,7 +404,7 @@ pub(crate) fn literal_comparisons(checker: &Checker, compare: &ast::ExprCompare)
|
||||
&ops,
|
||||
&compare.comparators,
|
||||
compare.into(),
|
||||
comment_ranges,
|
||||
tokens,
|
||||
source,
|
||||
)
|
||||
}
|
||||
@@ -420,7 +414,7 @@ pub(crate) fn literal_comparisons(checker: &Checker, compare: &ast::ExprCompare)
|
||||
&ops,
|
||||
&compare.comparators,
|
||||
compare.into(),
|
||||
comment_ranges,
|
||||
tokens,
|
||||
source,
|
||||
),
|
||||
};
|
||||
|
||||
@@ -107,7 +107,7 @@ pub(crate) fn not_tests(checker: &Checker, unary_op: &ast::ExprUnaryOp) {
|
||||
&[CmpOp::NotIn],
|
||||
comparators,
|
||||
unary_op.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
checker.source(),
|
||||
),
|
||||
unary_op.range(),
|
||||
@@ -127,7 +127,7 @@ pub(crate) fn not_tests(checker: &Checker, unary_op: &ast::ExprUnaryOp) {
|
||||
&[CmpOp::IsNot],
|
||||
comparators,
|
||||
unary_op.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
checker.source(),
|
||||
),
|
||||
unary_op.range(),
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::collections::hash_map::Entry;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::comparable::{ComparableExpr, HashableExpr};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -193,16 +193,14 @@ pub(crate) fn repeated_keys(checker: &Checker, dict: &ast::ExprDict) {
|
||||
parenthesized_range(
|
||||
dict.value(i - 1).into(),
|
||||
dict.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.unwrap_or_else(|| dict.value(i - 1).range())
|
||||
.end(),
|
||||
parenthesized_range(
|
||||
dict.value(i).into(),
|
||||
dict.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.unwrap_or_else(|| dict.value(i).range())
|
||||
.end(),
|
||||
@@ -224,16 +222,14 @@ pub(crate) fn repeated_keys(checker: &Checker, dict: &ast::ExprDict) {
|
||||
parenthesized_range(
|
||||
dict.value(i - 1).into(),
|
||||
dict.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.unwrap_or_else(|| dict.value(i - 1).range())
|
||||
.end(),
|
||||
parenthesized_range(
|
||||
dict.value(i).into(),
|
||||
dict.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.unwrap_or_else(|| dict.value(i).range())
|
||||
.end(),
|
||||
|
||||
@@ -2,7 +2,7 @@ use itertools::Itertools;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::helpers::contains_effect;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::token::{TokenKind, Tokens};
|
||||
use ruff_python_ast::{self as ast, Stmt};
|
||||
use ruff_python_semantic::Binding;
|
||||
@@ -172,14 +172,10 @@ fn remove_unused_variable(binding: &Binding, checker: &Checker) -> Option<Fix> {
|
||||
{
|
||||
// If the expression is complex (`x = foo()`), remove the assignment,
|
||||
// but preserve the right-hand side.
|
||||
let start = parenthesized_range(
|
||||
target.into(),
|
||||
statement.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(target.range())
|
||||
.start();
|
||||
let start =
|
||||
parenthesized_range(target.into(), statement.into(), checker.tokens())
|
||||
.unwrap_or(target.range())
|
||||
.start();
|
||||
let end = match_token_after(checker.tokens(), target.end(), |token| {
|
||||
token == TokenKind::Equal
|
||||
})?
|
||||
|
||||
@@ -16,10 +16,10 @@ mod tests {
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::{flake8_tidy_imports, pylint};
|
||||
|
||||
use crate::assert_diagnostics;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::settings::types::PreviewMode;
|
||||
use crate::test::test_path;
|
||||
use crate::{assert_diagnostics, assert_diagnostics_diff};
|
||||
|
||||
#[test_case(Rule::SingledispatchMethod, Path::new("singledispatch_method.py"))]
|
||||
#[test_case(
|
||||
@@ -253,6 +253,32 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test_case(
|
||||
Rule::UselessExceptionStatement,
|
||||
Path::new("useless_exception_statement.py")
|
||||
)]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
rule_code.noqa_code(),
|
||||
path.to_string_lossy()
|
||||
);
|
||||
|
||||
assert_diagnostics_diff!(
|
||||
snapshot,
|
||||
Path::new("pylint").join(path).as_path(),
|
||||
&LinterSettings {
|
||||
preview: PreviewMode::Disabled,
|
||||
..LinterSettings::for_rule(rule_code)
|
||||
},
|
||||
&LinterSettings {
|
||||
preview: PreviewMode::Enabled,
|
||||
..LinterSettings::for_rule(rule_code)
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn continue_in_finally() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -2,7 +2,7 @@ use itertools::Itertools;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{
|
||||
BoolOp, CmpOp, Expr, ExprBoolOp, ExprCompare,
|
||||
parenthesize::{parentheses_iterator, parenthesized_range},
|
||||
token::{parentheses_iterator, parenthesized_range},
|
||||
};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
@@ -62,7 +62,7 @@ pub(crate) fn boolean_chained_comparison(checker: &Checker, expr_bool_op: &ExprB
|
||||
}
|
||||
|
||||
let locator = checker.locator();
|
||||
let comment_ranges = checker.comment_ranges();
|
||||
let tokens = checker.tokens();
|
||||
|
||||
// retrieve all compare expressions from boolean expression
|
||||
let compare_expressions = expr_bool_op
|
||||
@@ -89,40 +89,22 @@ pub(crate) fn boolean_chained_comparison(checker: &Checker, expr_bool_op: &ExprB
|
||||
continue;
|
||||
}
|
||||
|
||||
let left_paren_count = parentheses_iterator(
|
||||
left_compare.into(),
|
||||
Some(expr_bool_op.into()),
|
||||
comment_ranges,
|
||||
locator.contents(),
|
||||
)
|
||||
.count();
|
||||
let left_paren_count =
|
||||
parentheses_iterator(left_compare.into(), Some(expr_bool_op.into()), tokens).count();
|
||||
|
||||
let right_paren_count = parentheses_iterator(
|
||||
right_compare.into(),
|
||||
Some(expr_bool_op.into()),
|
||||
comment_ranges,
|
||||
locator.contents(),
|
||||
)
|
||||
.count();
|
||||
let right_paren_count =
|
||||
parentheses_iterator(right_compare.into(), Some(expr_bool_op.into()), tokens).count();
|
||||
|
||||
// Create the edit that removes the comparison operator
|
||||
|
||||
// In `a<(b) and ((b))<c`, we need to handle the
|
||||
// parentheses when specifying the fix range.
|
||||
let left_compare_right_range = parenthesized_range(
|
||||
left_compare_right.into(),
|
||||
left_compare.into(),
|
||||
comment_ranges,
|
||||
locator.contents(),
|
||||
)
|
||||
.unwrap_or(left_compare_right.range());
|
||||
let right_compare_left_range = parenthesized_range(
|
||||
right_compare_left.into(),
|
||||
right_compare.into(),
|
||||
comment_ranges,
|
||||
locator.contents(),
|
||||
)
|
||||
.unwrap_or(right_compare_left.range());
|
||||
let left_compare_right_range =
|
||||
parenthesized_range(left_compare_right.into(), left_compare.into(), tokens)
|
||||
.unwrap_or(left_compare_right.range());
|
||||
let right_compare_left_range =
|
||||
parenthesized_range(right_compare_left.into(), right_compare.into(), tokens)
|
||||
.unwrap_or(right_compare_left.range());
|
||||
let edit = Edit::range_replacement(
|
||||
locator.slice(left_compare_right_range).to_string(),
|
||||
TextRange::new(
|
||||
|
||||
@@ -99,7 +99,7 @@ pub(crate) fn duplicate_bases(checker: &Checker, name: &str, arguments: Option<&
|
||||
arguments,
|
||||
Parentheses::Remove,
|
||||
checker.locator().contents(),
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.map(|edit| {
|
||||
Fix::applicable_edit(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, CmpOp, Stmt};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -166,13 +166,8 @@ pub(crate) fn if_stmt_min_max(checker: &Checker, stmt_if: &ast::StmtIf) {
|
||||
let replacement = format!(
|
||||
"{} = {min_max}({}, {})",
|
||||
checker.locator().slice(
|
||||
parenthesized_range(
|
||||
body_target.into(),
|
||||
body.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents()
|
||||
)
|
||||
.unwrap_or(body_target.range())
|
||||
parenthesized_range(body_target.into(), body.into(), checker.tokens())
|
||||
.unwrap_or(body_target.range())
|
||||
),
|
||||
checker.locator().slice(arg1),
|
||||
checker.locator().slice(arg2),
|
||||
|
||||
@@ -174,12 +174,8 @@ pub(crate) fn missing_maxsplit_arg(checker: &Checker, value: &Expr, slice: &Expr
|
||||
SliceBoundary::Last => "rsplit",
|
||||
};
|
||||
|
||||
let maxsplit_argument_edit = fix::edits::add_argument(
|
||||
"maxsplit=1",
|
||||
arguments,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
);
|
||||
let maxsplit_argument_edit =
|
||||
fix::edits::add_argument("maxsplit=1", arguments, checker.tokens());
|
||||
|
||||
// Only change `actual_split_type` if it doesn't match `suggested_split_type`
|
||||
let split_type_edit: Option<Edit> = if actual_split_type == suggested_split_type {
|
||||
|
||||
@@ -2,7 +2,7 @@ use ast::Expr;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::{ExprBinOp, ExprRef, Operator};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
@@ -150,12 +150,10 @@ fn augmented_assignment(
|
||||
|
||||
let right_operand_ref = ExprRef::from(right_operand);
|
||||
let parent = original_expr.into();
|
||||
let comment_ranges = checker.comment_ranges();
|
||||
let source = checker.source();
|
||||
let tokens = checker.tokens();
|
||||
|
||||
let right_operand_range =
|
||||
parenthesized_range(right_operand_ref, parent, comment_ranges, source)
|
||||
.unwrap_or(right_operand.range());
|
||||
parenthesized_range(right_operand_ref, parent, tokens).unwrap_or(right_operand.range());
|
||||
let right_operand_expr = locator.slice(right_operand_range);
|
||||
|
||||
let target_expr = locator.slice(target);
|
||||
|
||||
@@ -75,12 +75,7 @@ pub(crate) fn subprocess_run_without_check(checker: &Checker, call: &ast::ExprCa
|
||||
let mut diagnostic =
|
||||
checker.report_diagnostic(SubprocessRunWithoutCheck, call.func.range());
|
||||
diagnostic.set_fix(Fix::applicable_edit(
|
||||
add_argument(
|
||||
"check=False",
|
||||
&call.arguments,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
),
|
||||
add_argument("check=False", &call.arguments, checker.tokens()),
|
||||
// If the function call contains `**kwargs`, mark the fix as unsafe.
|
||||
if call
|
||||
.arguments
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::name::QualifiedName;
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_ast::{self as ast, Expr, name::QualifiedName};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_semantic::analyze::typing;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
@@ -193,8 +192,7 @@ fn generate_keyword_fix(checker: &Checker, call: &ast::ExprCall) -> Fix {
|
||||
}))
|
||||
),
|
||||
&call.arguments,
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
checker.tokens(),
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_semantic::{SemanticModel, analyze};
|
||||
use ruff_python_stdlib::builtins;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::preview::is_custom_exception_checking_enabled;
|
||||
use crate::{Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
@@ -20,6 +21,9 @@ use ruff_python_ast::PythonVersion;
|
||||
/// This rule only detects built-in exceptions, like `ValueError`, and does
|
||||
/// not catch user-defined exceptions.
|
||||
///
|
||||
/// In [preview], this rule will also detect user-defined exceptions, but only
|
||||
/// the ones defined in the file being checked.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// ValueError("...")
|
||||
@@ -32,7 +36,8 @@ use ruff_python_ast::PythonVersion;
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe, as converting a useless exception
|
||||
/// statement to a `raise` statement will change the program's behavior.
|
||||
///
|
||||
/// [preview]: https://docs.astral.sh/ruff/preview/
|
||||
#[derive(ViolationMetadata)]
|
||||
#[violation_metadata(stable_since = "0.5.0")]
|
||||
pub(crate) struct UselessExceptionStatement;
|
||||
@@ -56,7 +61,10 @@ pub(crate) fn useless_exception_statement(checker: &Checker, expr: &ast::StmtExp
|
||||
return;
|
||||
};
|
||||
|
||||
if is_builtin_exception(func, checker.semantic(), checker.target_version()) {
|
||||
if is_builtin_exception(func, checker.semantic(), checker.target_version())
|
||||
|| (is_custom_exception_checking_enabled(checker.settings())
|
||||
&& is_custom_exception(func, checker.semantic(), checker.target_version()))
|
||||
{
|
||||
let mut diagnostic = checker.report_diagnostic(UselessExceptionStatement, expr.range());
|
||||
diagnostic.set_fix(Fix::unsafe_edit(Edit::insertion(
|
||||
"raise ".to_string(),
|
||||
@@ -78,3 +86,34 @@ fn is_builtin_exception(
|
||||
if builtins::is_exception(name, target_version.minor))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if the given expression is a custom exception.
|
||||
fn is_custom_exception(
|
||||
expr: &Expr,
|
||||
semantic: &SemanticModel,
|
||||
target_version: PythonVersion,
|
||||
) -> bool {
|
||||
let Some(qualified_name) = semantic.resolve_qualified_name(expr) else {
|
||||
return false;
|
||||
};
|
||||
let Some(symbol) = qualified_name.segments().last() else {
|
||||
return false;
|
||||
};
|
||||
let Some(binding_id) = semantic.lookup_symbol(symbol) else {
|
||||
return false;
|
||||
};
|
||||
let binding = semantic.binding(binding_id);
|
||||
let Some(source) = binding.source else {
|
||||
return false;
|
||||
};
|
||||
let statement = semantic.statement(source);
|
||||
if let ast::Stmt::ClassDef(class_def) = statement {
|
||||
return analyze::class::any_qualified_base_class(class_def, semantic, &|qualified_name| {
|
||||
if let ["" | "builtins", name] = qualified_name.segments() {
|
||||
return builtins::is_exception(name, target_version.minor);
|
||||
}
|
||||
false
|
||||
});
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
@@ -2,250 +2,294 @@
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:7:5
|
||||
|
|
||||
5 | # Test case 1: Useless exception statement
|
||||
6 | def func():
|
||||
7 | AssertionError("This is an assertion error") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
4 |
|
||||
5 | # Test case 1: Useless exception statement
|
||||
6 | def func():
|
||||
- AssertionError("This is an assertion error") # PLW0133
|
||||
7 + raise AssertionError("This is an assertion error") # PLW0133
|
||||
8 |
|
||||
9 |
|
||||
10 | # Test case 2: Useless exception statement in try-except block
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:13:9
|
||||
--> useless_exception_statement.py:26:5
|
||||
|
|
||||
11 | def func():
|
||||
12 | try:
|
||||
13 | Exception("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
14 | except Exception as err:
|
||||
15 | pass
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
10 | # Test case 2: Useless exception statement in try-except block
|
||||
11 | def func():
|
||||
12 | try:
|
||||
- Exception("This is an exception") # PLW0133
|
||||
13 + raise Exception("This is an exception") # PLW0133
|
||||
14 | except Exception as err:
|
||||
15 | pass
|
||||
16 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:21:9
|
||||
|
|
||||
19 | def func():
|
||||
20 | if True:
|
||||
21 | RuntimeError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
18 | # Test case 3: Useless exception statement in if statement
|
||||
19 | def func():
|
||||
20 | if True:
|
||||
- RuntimeError("This is an exception") # PLW0133
|
||||
21 + raise RuntimeError("This is an exception") # PLW0133
|
||||
22 |
|
||||
23 |
|
||||
24 | # Test case 4: Useless exception statement in class
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:28:13
|
||||
|
|
||||
26 | class Class:
|
||||
27 | def __init__(self):
|
||||
28 | TypeError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
24 | # Test case 1: Useless exception statement
|
||||
25 | def func():
|
||||
26 | class Class:
|
||||
27 | def __init__(self):
|
||||
26 | AssertionError("This is an assertion error") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
27 | MyError("This is a custom error") # PLW0133
|
||||
28 | MySubError("This is a custom error") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
23 |
|
||||
24 | # Test case 1: Useless exception statement
|
||||
25 | def func():
|
||||
- AssertionError("This is an assertion error") # PLW0133
|
||||
26 + raise AssertionError("This is an assertion error") # PLW0133
|
||||
27 | MyError("This is a custom error") # PLW0133
|
||||
28 | MySubError("This is a custom error") # PLW0133
|
||||
29 | MyValueError("This is a custom value error") # PLW0133
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:35:9
|
||||
|
|
||||
33 | def func():
|
||||
34 | try:
|
||||
35 | Exception("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
36 | MyError("This is an exception") # PLW0133
|
||||
37 | MySubError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
32 | # Test case 2: Useless exception statement in try-except block
|
||||
33 | def func():
|
||||
34 | try:
|
||||
- Exception("This is an exception") # PLW0133
|
||||
35 + raise Exception("This is an exception") # PLW0133
|
||||
36 | MyError("This is an exception") # PLW0133
|
||||
37 | MySubError("This is an exception") # PLW0133
|
||||
38 | MyValueError("This is an exception") # PLW0133
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:46:9
|
||||
|
|
||||
44 | def func():
|
||||
45 | if True:
|
||||
46 | RuntimeError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
47 | MyError("This is an exception") # PLW0133
|
||||
48 | MySubError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
43 | # Test case 3: Useless exception statement in if statement
|
||||
44 | def func():
|
||||
45 | if True:
|
||||
- RuntimeError("This is an exception") # PLW0133
|
||||
46 + raise RuntimeError("This is an exception") # PLW0133
|
||||
47 | MyError("This is an exception") # PLW0133
|
||||
48 | MySubError("This is an exception") # PLW0133
|
||||
49 | MyValueError("This is an exception") # PLW0133
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:56:13
|
||||
|
|
||||
54 | class Class:
|
||||
55 | def __init__(self):
|
||||
56 | TypeError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
57 | MyError("This is an exception") # PLW0133
|
||||
58 | MySubError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
53 | def func():
|
||||
54 | class Class:
|
||||
55 | def __init__(self):
|
||||
- TypeError("This is an exception") # PLW0133
|
||||
28 + raise TypeError("This is an exception") # PLW0133
|
||||
29 |
|
||||
30 |
|
||||
31 | # Test case 5: Useless exception statement in function
|
||||
56 + raise TypeError("This is an exception") # PLW0133
|
||||
57 | MyError("This is an exception") # PLW0133
|
||||
58 | MySubError("This is an exception") # PLW0133
|
||||
59 | MyValueError("This is an exception") # PLW0133
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:34:9
|
||||
--> useless_exception_statement.py:65:9
|
||||
|
|
||||
32 | def func():
|
||||
33 | def inner():
|
||||
34 | IndexError("This is an exception") # PLW0133
|
||||
63 | def func():
|
||||
64 | def inner():
|
||||
65 | IndexError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
35 |
|
||||
36 | inner()
|
||||
66 | MyError("This is an exception") # PLW0133
|
||||
67 | MySubError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
31 | # Test case 5: Useless exception statement in function
|
||||
32 | def func():
|
||||
33 | def inner():
|
||||
62 | # Test case 5: Useless exception statement in function
|
||||
63 | def func():
|
||||
64 | def inner():
|
||||
- IndexError("This is an exception") # PLW0133
|
||||
34 + raise IndexError("This is an exception") # PLW0133
|
||||
35 |
|
||||
36 | inner()
|
||||
37 |
|
||||
65 + raise IndexError("This is an exception") # PLW0133
|
||||
66 | MyError("This is an exception") # PLW0133
|
||||
67 | MySubError("This is an exception") # PLW0133
|
||||
68 | MyValueError("This is an exception") # PLW0133
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:42:9
|
||||
--> useless_exception_statement.py:76:9
|
||||
|
|
||||
40 | def func():
|
||||
41 | while True:
|
||||
42 | KeyError("This is an exception") # PLW0133
|
||||
74 | def func():
|
||||
75 | while True:
|
||||
76 | KeyError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
77 | MyError("This is an exception") # PLW0133
|
||||
78 | MySubError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
39 | # Test case 6: Useless exception statement in while loop
|
||||
40 | def func():
|
||||
41 | while True:
|
||||
73 | # Test case 6: Useless exception statement in while loop
|
||||
74 | def func():
|
||||
75 | while True:
|
||||
- KeyError("This is an exception") # PLW0133
|
||||
42 + raise KeyError("This is an exception") # PLW0133
|
||||
43 |
|
||||
44 |
|
||||
45 | # Test case 7: Useless exception statement in abstract class
|
||||
76 + raise KeyError("This is an exception") # PLW0133
|
||||
77 | MyError("This is an exception") # PLW0133
|
||||
78 | MySubError("This is an exception") # PLW0133
|
||||
79 | MyValueError("This is an exception") # PLW0133
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:50:13
|
||||
--> useless_exception_statement.py:87:13
|
||||
|
|
||||
48 | @abstractmethod
|
||||
49 | def method(self):
|
||||
50 | NotImplementedError("This is an exception") # PLW0133
|
||||
85 | @abstractmethod
|
||||
86 | def method(self):
|
||||
87 | NotImplementedError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
88 | MyError("This is an exception") # PLW0133
|
||||
89 | MySubError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
47 | class Class(ABC):
|
||||
48 | @abstractmethod
|
||||
49 | def method(self):
|
||||
84 | class Class(ABC):
|
||||
85 | @abstractmethod
|
||||
86 | def method(self):
|
||||
- NotImplementedError("This is an exception") # PLW0133
|
||||
50 + raise NotImplementedError("This is an exception") # PLW0133
|
||||
51 |
|
||||
52 |
|
||||
53 | # Test case 8: Useless exception statement inside context manager
|
||||
87 + raise NotImplementedError("This is an exception") # PLW0133
|
||||
88 | MyError("This is an exception") # PLW0133
|
||||
89 | MySubError("This is an exception") # PLW0133
|
||||
90 | MyValueError("This is an exception") # PLW0133
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:56:9
|
||||
--> useless_exception_statement.py:96:9
|
||||
|
|
||||
54 | def func():
|
||||
55 | with suppress(AttributeError):
|
||||
56 | AttributeError("This is an exception") # PLW0133
|
||||
94 | def func():
|
||||
95 | with suppress(Exception):
|
||||
96 | AttributeError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
97 | MyError("This is an exception") # PLW0133
|
||||
98 | MySubError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
53 | # Test case 8: Useless exception statement inside context manager
|
||||
54 | def func():
|
||||
55 | with suppress(AttributeError):
|
||||
93 | # Test case 8: Useless exception statement inside context manager
|
||||
94 | def func():
|
||||
95 | with suppress(Exception):
|
||||
- AttributeError("This is an exception") # PLW0133
|
||||
56 + raise AttributeError("This is an exception") # PLW0133
|
||||
57 |
|
||||
58 |
|
||||
59 | # Test case 9: Useless exception statement in parentheses
|
||||
96 + raise AttributeError("This is an exception") # PLW0133
|
||||
97 | MyError("This is an exception") # PLW0133
|
||||
98 | MySubError("This is an exception") # PLW0133
|
||||
99 | MyValueError("This is an exception") # PLW0133
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:61:5
|
||||
|
|
||||
59 | # Test case 9: Useless exception statement in parentheses
|
||||
60 | def func():
|
||||
61 | (RuntimeError("This is an exception")) # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
58 |
|
||||
59 | # Test case 9: Useless exception statement in parentheses
|
||||
60 | def func():
|
||||
- (RuntimeError("This is an exception")) # PLW0133
|
||||
61 + raise (RuntimeError("This is an exception")) # PLW0133
|
||||
62 |
|
||||
63 |
|
||||
64 | # Test case 10: Useless exception statement in continuation
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:66:12
|
||||
|
|
||||
64 | # Test case 10: Useless exception statement in continuation
|
||||
65 | def func():
|
||||
66 | x = 1; (RuntimeError("This is an exception")); y = 2 # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
63 |
|
||||
64 | # Test case 10: Useless exception statement in continuation
|
||||
65 | def func():
|
||||
- x = 1; (RuntimeError("This is an exception")); y = 2 # PLW0133
|
||||
66 + x = 1; raise (RuntimeError("This is an exception")); y = 2 # PLW0133
|
||||
67 |
|
||||
68 |
|
||||
69 | # Test case 11: Useless warning statement
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:71:5
|
||||
|
|
||||
69 | # Test case 11: Useless warning statement
|
||||
70 | def func():
|
||||
71 | UserWarning("This is an assertion error") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
68 |
|
||||
69 | # Test case 11: Useless warning statement
|
||||
70 | def func():
|
||||
- UserWarning("This is an assertion error") # PLW0133
|
||||
71 + raise UserWarning("This is an assertion error") # PLW0133
|
||||
72 |
|
||||
73 |
|
||||
74 | # Non-violation test cases: PLW0133
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:126:1
|
||||
--> useless_exception_statement.py:104:5
|
||||
|
|
||||
124 | import builtins
|
||||
125 |
|
||||
126 | builtins.TypeError("still an exception even though it's an Attribute")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
127 |
|
||||
128 | PythonFinalizationError("Added in Python 3.13")
|
||||
102 | # Test case 9: Useless exception statement in parentheses
|
||||
103 | def func():
|
||||
104 | (RuntimeError("This is an exception")) # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
105 | (MyError("This is an exception")) # PLW0133
|
||||
106 | (MySubError("This is an exception")) # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
101 |
|
||||
102 | # Test case 9: Useless exception statement in parentheses
|
||||
103 | def func():
|
||||
- (RuntimeError("This is an exception")) # PLW0133
|
||||
104 + raise (RuntimeError("This is an exception")) # PLW0133
|
||||
105 | (MyError("This is an exception")) # PLW0133
|
||||
106 | (MySubError("This is an exception")) # PLW0133
|
||||
107 | (MyValueError("This is an exception")) # PLW0133
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:112:12
|
||||
|
|
||||
110 | # Test case 10: Useless exception statement in continuation
|
||||
111 | def func():
|
||||
112 | x = 1; (RuntimeError("This is an exception")); y = 2 # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
113 | x = 1; (MyError("This is an exception")); y = 2 # PLW0133
|
||||
114 | x = 1; (MySubError("This is an exception")); y = 2 # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
109 |
|
||||
110 | # Test case 10: Useless exception statement in continuation
|
||||
111 | def func():
|
||||
- x = 1; (RuntimeError("This is an exception")); y = 2 # PLW0133
|
||||
112 + x = 1; raise (RuntimeError("This is an exception")); y = 2 # PLW0133
|
||||
113 | x = 1; (MyError("This is an exception")); y = 2 # PLW0133
|
||||
114 | x = 1; (MySubError("This is an exception")); y = 2 # PLW0133
|
||||
115 | x = 1; (MyValueError("This is an exception")); y = 2 # PLW0133
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:120:5
|
||||
|
|
||||
118 | # Test case 11: Useless warning statement
|
||||
119 | def func():
|
||||
120 | UserWarning("This is a user warning") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
121 | MyUserWarning("This is a custom user warning") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
117 |
|
||||
118 | # Test case 11: Useless warning statement
|
||||
119 | def func():
|
||||
- UserWarning("This is a user warning") # PLW0133
|
||||
120 + raise UserWarning("This is a user warning") # PLW0133
|
||||
121 | MyUserWarning("This is a custom user warning") # PLW0133
|
||||
122 |
|
||||
123 |
|
||||
124 | import builtins
|
||||
125 |
|
||||
- builtins.TypeError("still an exception even though it's an Attribute")
|
||||
126 + raise builtins.TypeError("still an exception even though it's an Attribute")
|
||||
127 |
|
||||
128 | PythonFinalizationError("Added in Python 3.13")
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:128:1
|
||||
--> useless_exception_statement.py:127:1
|
||||
|
|
||||
126 | builtins.TypeError("still an exception even though it's an Attribute")
|
||||
127 |
|
||||
128 | PythonFinalizationError("Added in Python 3.13")
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
125 | import builtins
|
||||
126 |
|
||||
127 | builtins.TypeError("still an exception even though it's an Attribute") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
128 |
|
||||
129 | PythonFinalizationError("Added in Python 3.13") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
125 |
|
||||
126 | builtins.TypeError("still an exception even though it's an Attribute")
|
||||
127 |
|
||||
- PythonFinalizationError("Added in Python 3.13")
|
||||
128 + raise PythonFinalizationError("Added in Python 3.13")
|
||||
124 | # Test case 12: Useless exception statement at module level
|
||||
125 | import builtins
|
||||
126 |
|
||||
- builtins.TypeError("still an exception even though it's an Attribute") # PLW0133
|
||||
127 + raise builtins.TypeError("still an exception even though it's an Attribute") # PLW0133
|
||||
128 |
|
||||
129 | PythonFinalizationError("Added in Python 3.13") # PLW0133
|
||||
130 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:129:1
|
||||
|
|
||||
127 | builtins.TypeError("still an exception even though it's an Attribute") # PLW0133
|
||||
128 |
|
||||
129 | PythonFinalizationError("Added in Python 3.13") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
130 |
|
||||
131 | MyError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
126 |
|
||||
127 | builtins.TypeError("still an exception even though it's an Attribute") # PLW0133
|
||||
128 |
|
||||
- PythonFinalizationError("Added in Python 3.13") # PLW0133
|
||||
129 + raise PythonFinalizationError("Added in Python 3.13") # PLW0133
|
||||
130 |
|
||||
131 | MyError("This is an exception") # PLW0133
|
||||
132 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:137:1
|
||||
|
|
||||
135 | MyValueError("This is an exception") # PLW0133
|
||||
136 |
|
||||
137 | UserWarning("This is a user warning") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
138 |
|
||||
139 | MyUserWarning("This is a custom user warning") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
134 |
|
||||
135 | MyValueError("This is an exception") # PLW0133
|
||||
136 |
|
||||
- UserWarning("This is a user warning") # PLW0133
|
||||
137 + raise UserWarning("This is a user warning") # PLW0133
|
||||
138 |
|
||||
139 | MyUserWarning("This is a custom user warning") # PLW0133
|
||||
140 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
@@ -0,0 +1,751 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pylint/mod.rs
|
||||
---
|
||||
--- Linter settings ---
|
||||
-linter.preview = disabled
|
||||
+linter.preview = enabled
|
||||
|
||||
--- Summary ---
|
||||
Removed: 0
|
||||
Added: 35
|
||||
|
||||
--- Added ---
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:27:5
|
||||
|
|
||||
25 | def func():
|
||||
26 | AssertionError("This is an assertion error") # PLW0133
|
||||
27 | MyError("This is a custom error") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
28 | MySubError("This is a custom error") # PLW0133
|
||||
29 | MyValueError("This is a custom value error") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
24 | # Test case 1: Useless exception statement
|
||||
25 | def func():
|
||||
26 | AssertionError("This is an assertion error") # PLW0133
|
||||
- MyError("This is a custom error") # PLW0133
|
||||
27 + raise MyError("This is a custom error") # PLW0133
|
||||
28 | MySubError("This is a custom error") # PLW0133
|
||||
29 | MyValueError("This is a custom value error") # PLW0133
|
||||
30 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:28:5
|
||||
|
|
||||
26 | AssertionError("This is an assertion error") # PLW0133
|
||||
27 | MyError("This is a custom error") # PLW0133
|
||||
28 | MySubError("This is a custom error") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
29 | MyValueError("This is a custom value error") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
25 | def func():
|
||||
26 | AssertionError("This is an assertion error") # PLW0133
|
||||
27 | MyError("This is a custom error") # PLW0133
|
||||
- MySubError("This is a custom error") # PLW0133
|
||||
28 + raise MySubError("This is a custom error") # PLW0133
|
||||
29 | MyValueError("This is a custom value error") # PLW0133
|
||||
30 |
|
||||
31 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:29:5
|
||||
|
|
||||
27 | MyError("This is a custom error") # PLW0133
|
||||
28 | MySubError("This is a custom error") # PLW0133
|
||||
29 | MyValueError("This is a custom value error") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
26 | AssertionError("This is an assertion error") # PLW0133
|
||||
27 | MyError("This is a custom error") # PLW0133
|
||||
28 | MySubError("This is a custom error") # PLW0133
|
||||
- MyValueError("This is a custom value error") # PLW0133
|
||||
29 + raise MyValueError("This is a custom value error") # PLW0133
|
||||
30 |
|
||||
31 |
|
||||
32 | # Test case 2: Useless exception statement in try-except block
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:36:9
|
||||
|
|
||||
34 | try:
|
||||
35 | Exception("This is an exception") # PLW0133
|
||||
36 | MyError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
37 | MySubError("This is an exception") # PLW0133
|
||||
38 | MyValueError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
33 | def func():
|
||||
34 | try:
|
||||
35 | Exception("This is an exception") # PLW0133
|
||||
- MyError("This is an exception") # PLW0133
|
||||
36 + raise MyError("This is an exception") # PLW0133
|
||||
37 | MySubError("This is an exception") # PLW0133
|
||||
38 | MyValueError("This is an exception") # PLW0133
|
||||
39 | except Exception as err:
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:37:9
|
||||
|
|
||||
35 | Exception("This is an exception") # PLW0133
|
||||
36 | MyError("This is an exception") # PLW0133
|
||||
37 | MySubError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
38 | MyValueError("This is an exception") # PLW0133
|
||||
39 | except Exception as err:
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
34 | try:
|
||||
35 | Exception("This is an exception") # PLW0133
|
||||
36 | MyError("This is an exception") # PLW0133
|
||||
- MySubError("This is an exception") # PLW0133
|
||||
37 + raise MySubError("This is an exception") # PLW0133
|
||||
38 | MyValueError("This is an exception") # PLW0133
|
||||
39 | except Exception as err:
|
||||
40 | pass
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:38:9
|
||||
|
|
||||
36 | MyError("This is an exception") # PLW0133
|
||||
37 | MySubError("This is an exception") # PLW0133
|
||||
38 | MyValueError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
39 | except Exception as err:
|
||||
40 | pass
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
35 | Exception("This is an exception") # PLW0133
|
||||
36 | MyError("This is an exception") # PLW0133
|
||||
37 | MySubError("This is an exception") # PLW0133
|
||||
- MyValueError("This is an exception") # PLW0133
|
||||
38 + raise MyValueError("This is an exception") # PLW0133
|
||||
39 | except Exception as err:
|
||||
40 | pass
|
||||
41 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:47:9
|
||||
|
|
||||
45 | if True:
|
||||
46 | RuntimeError("This is an exception") # PLW0133
|
||||
47 | MyError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
48 | MySubError("This is an exception") # PLW0133
|
||||
49 | MyValueError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
44 | def func():
|
||||
45 | if True:
|
||||
46 | RuntimeError("This is an exception") # PLW0133
|
||||
- MyError("This is an exception") # PLW0133
|
||||
47 + raise MyError("This is an exception") # PLW0133
|
||||
48 | MySubError("This is an exception") # PLW0133
|
||||
49 | MyValueError("This is an exception") # PLW0133
|
||||
50 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:48:9
|
||||
|
|
||||
46 | RuntimeError("This is an exception") # PLW0133
|
||||
47 | MyError("This is an exception") # PLW0133
|
||||
48 | MySubError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
49 | MyValueError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
45 | if True:
|
||||
46 | RuntimeError("This is an exception") # PLW0133
|
||||
47 | MyError("This is an exception") # PLW0133
|
||||
- MySubError("This is an exception") # PLW0133
|
||||
48 + raise MySubError("This is an exception") # PLW0133
|
||||
49 | MyValueError("This is an exception") # PLW0133
|
||||
50 |
|
||||
51 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:49:9
|
||||
|
|
||||
47 | MyError("This is an exception") # PLW0133
|
||||
48 | MySubError("This is an exception") # PLW0133
|
||||
49 | MyValueError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
46 | RuntimeError("This is an exception") # PLW0133
|
||||
47 | MyError("This is an exception") # PLW0133
|
||||
48 | MySubError("This is an exception") # PLW0133
|
||||
- MyValueError("This is an exception") # PLW0133
|
||||
49 + raise MyValueError("This is an exception") # PLW0133
|
||||
50 |
|
||||
51 |
|
||||
52 | # Test case 4: Useless exception statement in class
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:57:13
|
||||
|
|
||||
55 | def __init__(self):
|
||||
56 | TypeError("This is an exception") # PLW0133
|
||||
57 | MyError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
58 | MySubError("This is an exception") # PLW0133
|
||||
59 | MyValueError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
54 | class Class:
|
||||
55 | def __init__(self):
|
||||
56 | TypeError("This is an exception") # PLW0133
|
||||
- MyError("This is an exception") # PLW0133
|
||||
57 + raise MyError("This is an exception") # PLW0133
|
||||
58 | MySubError("This is an exception") # PLW0133
|
||||
59 | MyValueError("This is an exception") # PLW0133
|
||||
60 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:58:13
|
||||
|
|
||||
56 | TypeError("This is an exception") # PLW0133
|
||||
57 | MyError("This is an exception") # PLW0133
|
||||
58 | MySubError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
59 | MyValueError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
55 | def __init__(self):
|
||||
56 | TypeError("This is an exception") # PLW0133
|
||||
57 | MyError("This is an exception") # PLW0133
|
||||
- MySubError("This is an exception") # PLW0133
|
||||
58 + raise MySubError("This is an exception") # PLW0133
|
||||
59 | MyValueError("This is an exception") # PLW0133
|
||||
60 |
|
||||
61 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:59:13
|
||||
|
|
||||
57 | MyError("This is an exception") # PLW0133
|
||||
58 | MySubError("This is an exception") # PLW0133
|
||||
59 | MyValueError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
56 | TypeError("This is an exception") # PLW0133
|
||||
57 | MyError("This is an exception") # PLW0133
|
||||
58 | MySubError("This is an exception") # PLW0133
|
||||
- MyValueError("This is an exception") # PLW0133
|
||||
59 + raise MyValueError("This is an exception") # PLW0133
|
||||
60 |
|
||||
61 |
|
||||
62 | # Test case 5: Useless exception statement in function
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:66:9
|
||||
|
|
||||
64 | def inner():
|
||||
65 | IndexError("This is an exception") # PLW0133
|
||||
66 | MyError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
67 | MySubError("This is an exception") # PLW0133
|
||||
68 | MyValueError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
63 | def func():
|
||||
64 | def inner():
|
||||
65 | IndexError("This is an exception") # PLW0133
|
||||
- MyError("This is an exception") # PLW0133
|
||||
66 + raise MyError("This is an exception") # PLW0133
|
||||
67 | MySubError("This is an exception") # PLW0133
|
||||
68 | MyValueError("This is an exception") # PLW0133
|
||||
69 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:67:9
|
||||
|
|
||||
65 | IndexError("This is an exception") # PLW0133
|
||||
66 | MyError("This is an exception") # PLW0133
|
||||
67 | MySubError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
68 | MyValueError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
64 | def inner():
|
||||
65 | IndexError("This is an exception") # PLW0133
|
||||
66 | MyError("This is an exception") # PLW0133
|
||||
- MySubError("This is an exception") # PLW0133
|
||||
67 + raise MySubError("This is an exception") # PLW0133
|
||||
68 | MyValueError("This is an exception") # PLW0133
|
||||
69 |
|
||||
70 | inner()
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:68:9
|
||||
|
|
||||
66 | MyError("This is an exception") # PLW0133
|
||||
67 | MySubError("This is an exception") # PLW0133
|
||||
68 | MyValueError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
69 |
|
||||
70 | inner()
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
65 | IndexError("This is an exception") # PLW0133
|
||||
66 | MyError("This is an exception") # PLW0133
|
||||
67 | MySubError("This is an exception") # PLW0133
|
||||
- MyValueError("This is an exception") # PLW0133
|
||||
68 + raise MyValueError("This is an exception") # PLW0133
|
||||
69 |
|
||||
70 | inner()
|
||||
71 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:77:9
|
||||
|
|
||||
75 | while True:
|
||||
76 | KeyError("This is an exception") # PLW0133
|
||||
77 | MyError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
78 | MySubError("This is an exception") # PLW0133
|
||||
79 | MyValueError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
74 | def func():
|
||||
75 | while True:
|
||||
76 | KeyError("This is an exception") # PLW0133
|
||||
- MyError("This is an exception") # PLW0133
|
||||
77 + raise MyError("This is an exception") # PLW0133
|
||||
78 | MySubError("This is an exception") # PLW0133
|
||||
79 | MyValueError("This is an exception") # PLW0133
|
||||
80 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:78:9
|
||||
|
|
||||
76 | KeyError("This is an exception") # PLW0133
|
||||
77 | MyError("This is an exception") # PLW0133
|
||||
78 | MySubError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
79 | MyValueError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
75 | while True:
|
||||
76 | KeyError("This is an exception") # PLW0133
|
||||
77 | MyError("This is an exception") # PLW0133
|
||||
- MySubError("This is an exception") # PLW0133
|
||||
78 + raise MySubError("This is an exception") # PLW0133
|
||||
79 | MyValueError("This is an exception") # PLW0133
|
||||
80 |
|
||||
81 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:79:9
|
||||
|
|
||||
77 | MyError("This is an exception") # PLW0133
|
||||
78 | MySubError("This is an exception") # PLW0133
|
||||
79 | MyValueError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
76 | KeyError("This is an exception") # PLW0133
|
||||
77 | MyError("This is an exception") # PLW0133
|
||||
78 | MySubError("This is an exception") # PLW0133
|
||||
- MyValueError("This is an exception") # PLW0133
|
||||
79 + raise MyValueError("This is an exception") # PLW0133
|
||||
80 |
|
||||
81 |
|
||||
82 | # Test case 7: Useless exception statement in abstract class
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:88:13
|
||||
|
|
||||
86 | def method(self):
|
||||
87 | NotImplementedError("This is an exception") # PLW0133
|
||||
88 | MyError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
89 | MySubError("This is an exception") # PLW0133
|
||||
90 | MyValueError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
85 | @abstractmethod
|
||||
86 | def method(self):
|
||||
87 | NotImplementedError("This is an exception") # PLW0133
|
||||
- MyError("This is an exception") # PLW0133
|
||||
88 + raise MyError("This is an exception") # PLW0133
|
||||
89 | MySubError("This is an exception") # PLW0133
|
||||
90 | MyValueError("This is an exception") # PLW0133
|
||||
91 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:89:13
|
||||
|
|
||||
87 | NotImplementedError("This is an exception") # PLW0133
|
||||
88 | MyError("This is an exception") # PLW0133
|
||||
89 | MySubError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
90 | MyValueError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
86 | def method(self):
|
||||
87 | NotImplementedError("This is an exception") # PLW0133
|
||||
88 | MyError("This is an exception") # PLW0133
|
||||
- MySubError("This is an exception") # PLW0133
|
||||
89 + raise MySubError("This is an exception") # PLW0133
|
||||
90 | MyValueError("This is an exception") # PLW0133
|
||||
91 |
|
||||
92 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:90:13
|
||||
|
|
||||
88 | MyError("This is an exception") # PLW0133
|
||||
89 | MySubError("This is an exception") # PLW0133
|
||||
90 | MyValueError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
87 | NotImplementedError("This is an exception") # PLW0133
|
||||
88 | MyError("This is an exception") # PLW0133
|
||||
89 | MySubError("This is an exception") # PLW0133
|
||||
- MyValueError("This is an exception") # PLW0133
|
||||
90 + raise MyValueError("This is an exception") # PLW0133
|
||||
91 |
|
||||
92 |
|
||||
93 | # Test case 8: Useless exception statement inside context manager
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:97:9
|
||||
|
|
||||
95 | with suppress(Exception):
|
||||
96 | AttributeError("This is an exception") # PLW0133
|
||||
97 | MyError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
98 | MySubError("This is an exception") # PLW0133
|
||||
99 | MyValueError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
94 | def func():
|
||||
95 | with suppress(Exception):
|
||||
96 | AttributeError("This is an exception") # PLW0133
|
||||
- MyError("This is an exception") # PLW0133
|
||||
97 + raise MyError("This is an exception") # PLW0133
|
||||
98 | MySubError("This is an exception") # PLW0133
|
||||
99 | MyValueError("This is an exception") # PLW0133
|
||||
100 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:98:9
|
||||
|
|
||||
96 | AttributeError("This is an exception") # PLW0133
|
||||
97 | MyError("This is an exception") # PLW0133
|
||||
98 | MySubError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
99 | MyValueError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
95 | with suppress(Exception):
|
||||
96 | AttributeError("This is an exception") # PLW0133
|
||||
97 | MyError("This is an exception") # PLW0133
|
||||
- MySubError("This is an exception") # PLW0133
|
||||
98 + raise MySubError("This is an exception") # PLW0133
|
||||
99 | MyValueError("This is an exception") # PLW0133
|
||||
100 |
|
||||
101 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:99:9
|
||||
|
|
||||
97 | MyError("This is an exception") # PLW0133
|
||||
98 | MySubError("This is an exception") # PLW0133
|
||||
99 | MyValueError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
96 | AttributeError("This is an exception") # PLW0133
|
||||
97 | MyError("This is an exception") # PLW0133
|
||||
98 | MySubError("This is an exception") # PLW0133
|
||||
- MyValueError("This is an exception") # PLW0133
|
||||
99 + raise MyValueError("This is an exception") # PLW0133
|
||||
100 |
|
||||
101 |
|
||||
102 | # Test case 9: Useless exception statement in parentheses
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:105:5
|
||||
|
|
||||
103 | def func():
|
||||
104 | (RuntimeError("This is an exception")) # PLW0133
|
||||
105 | (MyError("This is an exception")) # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
106 | (MySubError("This is an exception")) # PLW0133
|
||||
107 | (MyValueError("This is an exception")) # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
102 | # Test case 9: Useless exception statement in parentheses
|
||||
103 | def func():
|
||||
104 | (RuntimeError("This is an exception")) # PLW0133
|
||||
- (MyError("This is an exception")) # PLW0133
|
||||
105 + raise (MyError("This is an exception")) # PLW0133
|
||||
106 | (MySubError("This is an exception")) # PLW0133
|
||||
107 | (MyValueError("This is an exception")) # PLW0133
|
||||
108 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:106:5
|
||||
|
|
||||
104 | (RuntimeError("This is an exception")) # PLW0133
|
||||
105 | (MyError("This is an exception")) # PLW0133
|
||||
106 | (MySubError("This is an exception")) # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
107 | (MyValueError("This is an exception")) # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
103 | def func():
|
||||
104 | (RuntimeError("This is an exception")) # PLW0133
|
||||
105 | (MyError("This is an exception")) # PLW0133
|
||||
- (MySubError("This is an exception")) # PLW0133
|
||||
106 + raise (MySubError("This is an exception")) # PLW0133
|
||||
107 | (MyValueError("This is an exception")) # PLW0133
|
||||
108 |
|
||||
109 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:107:5
|
||||
|
|
||||
105 | (MyError("This is an exception")) # PLW0133
|
||||
106 | (MySubError("This is an exception")) # PLW0133
|
||||
107 | (MyValueError("This is an exception")) # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
104 | (RuntimeError("This is an exception")) # PLW0133
|
||||
105 | (MyError("This is an exception")) # PLW0133
|
||||
106 | (MySubError("This is an exception")) # PLW0133
|
||||
- (MyValueError("This is an exception")) # PLW0133
|
||||
107 + raise (MyValueError("This is an exception")) # PLW0133
|
||||
108 |
|
||||
109 |
|
||||
110 | # Test case 10: Useless exception statement in continuation
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:113:12
|
||||
|
|
||||
111 | def func():
|
||||
112 | x = 1; (RuntimeError("This is an exception")); y = 2 # PLW0133
|
||||
113 | x = 1; (MyError("This is an exception")); y = 2 # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
114 | x = 1; (MySubError("This is an exception")); y = 2 # PLW0133
|
||||
115 | x = 1; (MyValueError("This is an exception")); y = 2 # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
110 | # Test case 10: Useless exception statement in continuation
|
||||
111 | def func():
|
||||
112 | x = 1; (RuntimeError("This is an exception")); y = 2 # PLW0133
|
||||
- x = 1; (MyError("This is an exception")); y = 2 # PLW0133
|
||||
113 + x = 1; raise (MyError("This is an exception")); y = 2 # PLW0133
|
||||
114 | x = 1; (MySubError("This is an exception")); y = 2 # PLW0133
|
||||
115 | x = 1; (MyValueError("This is an exception")); y = 2 # PLW0133
|
||||
116 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:114:12
|
||||
|
|
||||
112 | x = 1; (RuntimeError("This is an exception")); y = 2 # PLW0133
|
||||
113 | x = 1; (MyError("This is an exception")); y = 2 # PLW0133
|
||||
114 | x = 1; (MySubError("This is an exception")); y = 2 # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
115 | x = 1; (MyValueError("This is an exception")); y = 2 # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
111 | def func():
|
||||
112 | x = 1; (RuntimeError("This is an exception")); y = 2 # PLW0133
|
||||
113 | x = 1; (MyError("This is an exception")); y = 2 # PLW0133
|
||||
- x = 1; (MySubError("This is an exception")); y = 2 # PLW0133
|
||||
114 + x = 1; raise (MySubError("This is an exception")); y = 2 # PLW0133
|
||||
115 | x = 1; (MyValueError("This is an exception")); y = 2 # PLW0133
|
||||
116 |
|
||||
117 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:115:12
|
||||
|
|
||||
113 | x = 1; (MyError("This is an exception")); y = 2 # PLW0133
|
||||
114 | x = 1; (MySubError("This is an exception")); y = 2 # PLW0133
|
||||
115 | x = 1; (MyValueError("This is an exception")); y = 2 # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
112 | x = 1; (RuntimeError("This is an exception")); y = 2 # PLW0133
|
||||
113 | x = 1; (MyError("This is an exception")); y = 2 # PLW0133
|
||||
114 | x = 1; (MySubError("This is an exception")); y = 2 # PLW0133
|
||||
- x = 1; (MyValueError("This is an exception")); y = 2 # PLW0133
|
||||
115 + x = 1; raise (MyValueError("This is an exception")); y = 2 # PLW0133
|
||||
116 |
|
||||
117 |
|
||||
118 | # Test case 11: Useless warning statement
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:121:5
|
||||
|
|
||||
119 | def func():
|
||||
120 | UserWarning("This is a user warning") # PLW0133
|
||||
121 | MyUserWarning("This is a custom user warning") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
118 | # Test case 11: Useless warning statement
|
||||
119 | def func():
|
||||
120 | UserWarning("This is a user warning") # PLW0133
|
||||
- MyUserWarning("This is a custom user warning") # PLW0133
|
||||
121 + raise MyUserWarning("This is a custom user warning") # PLW0133
|
||||
122 |
|
||||
123 |
|
||||
124 | # Test case 12: Useless exception statement at module level
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:131:1
|
||||
|
|
||||
129 | PythonFinalizationError("Added in Python 3.13") # PLW0133
|
||||
130 |
|
||||
131 | MyError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
132 |
|
||||
133 | MySubError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
128 |
|
||||
129 | PythonFinalizationError("Added in Python 3.13") # PLW0133
|
||||
130 |
|
||||
- MyError("This is an exception") # PLW0133
|
||||
131 + raise MyError("This is an exception") # PLW0133
|
||||
132 |
|
||||
133 | MySubError("This is an exception") # PLW0133
|
||||
134 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:133:1
|
||||
|
|
||||
131 | MyError("This is an exception") # PLW0133
|
||||
132 |
|
||||
133 | MySubError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
134 |
|
||||
135 | MyValueError("This is an exception") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
130 |
|
||||
131 | MyError("This is an exception") # PLW0133
|
||||
132 |
|
||||
- MySubError("This is an exception") # PLW0133
|
||||
133 + raise MySubError("This is an exception") # PLW0133
|
||||
134 |
|
||||
135 | MyValueError("This is an exception") # PLW0133
|
||||
136 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:135:1
|
||||
|
|
||||
133 | MySubError("This is an exception") # PLW0133
|
||||
134 |
|
||||
135 | MyValueError("This is an exception") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
136 |
|
||||
137 | UserWarning("This is a user warning") # PLW0133
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
132 |
|
||||
133 | MySubError("This is an exception") # PLW0133
|
||||
134 |
|
||||
- MyValueError("This is an exception") # PLW0133
|
||||
135 + raise MyValueError("This is an exception") # PLW0133
|
||||
136 |
|
||||
137 | UserWarning("This is a user warning") # PLW0133
|
||||
138 |
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
PLW0133 [*] Missing `raise` statement on exception
|
||||
--> useless_exception_statement.py:139:1
|
||||
|
|
||||
137 | UserWarning("This is a user warning") # PLW0133
|
||||
138 |
|
||||
139 | MyUserWarning("This is a custom user warning") # PLW0133
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Add `raise` keyword
|
||||
136 |
|
||||
137 | UserWarning("This is a user warning") # PLW0133
|
||||
138 |
|
||||
- MyUserWarning("This is a custom user warning") # PLW0133
|
||||
139 + raise MyUserWarning("This is a custom user warning") # PLW0133
|
||||
140 |
|
||||
141 |
|
||||
142 | # Non-violation test cases: PLW0133
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
@@ -204,7 +204,7 @@ pub(crate) fn non_pep695_generic_class(checker: &Checker, class_def: &StmtClassD
|
||||
arguments,
|
||||
Parentheses::Remove,
|
||||
checker.source(),
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
)?;
|
||||
Ok(Fix::unsafe_edits(
|
||||
Edit::insertion(type_params.to_string(), name.end()),
|
||||
|
||||
@@ -2,7 +2,7 @@ use itertools::Itertools;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::visitor::Visitor;
|
||||
use ruff_python_ast::{Expr, ExprCall, ExprName, Keyword, StmtAnnAssign, StmtAssign, StmtRef};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
@@ -261,11 +261,11 @@ fn create_diagnostic(
|
||||
type_alias_kind: TypeAliasKind,
|
||||
) {
|
||||
let source = checker.source();
|
||||
let tokens = checker.tokens();
|
||||
let comment_ranges = checker.comment_ranges();
|
||||
|
||||
let range_with_parentheses =
|
||||
parenthesized_range(value.into(), stmt.into(), comment_ranges, source)
|
||||
.unwrap_or(value.range());
|
||||
parenthesized_range(value.into(), stmt.into(), tokens).unwrap_or(value.range());
|
||||
|
||||
let content = format!(
|
||||
"type {name}{type_params} = {value}",
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::{self as ast, Keyword};
|
||||
use ruff_python_ast::{self as ast, Keyword, token::Tokens};
|
||||
use ruff_python_semantic::Modules;
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -104,7 +103,7 @@ pub(crate) fn replace_stdout_stderr(checker: &Checker, call: &ast::ExprCall) {
|
||||
stderr,
|
||||
call,
|
||||
checker.locator().contents(),
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
)
|
||||
});
|
||||
}
|
||||
@@ -117,7 +116,7 @@ fn generate_fix(
|
||||
stderr: &Keyword,
|
||||
call: &ast::ExprCall,
|
||||
source: &str,
|
||||
comment_ranges: &CommentRanges,
|
||||
tokens: &Tokens,
|
||||
) -> Result<Fix> {
|
||||
let (first, second) = if stdout.start() < stderr.start() {
|
||||
(stdout, stderr)
|
||||
@@ -132,7 +131,7 @@ fn generate_fix(
|
||||
&call.arguments,
|
||||
Parentheses::Preserve,
|
||||
source,
|
||||
comment_ranges,
|
||||
tokens,
|
||||
)?],
|
||||
))
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ pub(crate) fn replace_universal_newlines(checker: &Checker, call: &ast::ExprCall
|
||||
&call.arguments,
|
||||
Parentheses::Preserve,
|
||||
checker.locator().contents(),
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.map(Fix::safe_edit)
|
||||
});
|
||||
|
||||
@@ -188,7 +188,7 @@ pub(crate) fn unnecessary_encode_utf8(checker: &Checker, call: &ast::ExprCall) {
|
||||
&call.arguments,
|
||||
Parentheses::Preserve,
|
||||
checker.locator().contents(),
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.map(Fix::safe_edit)
|
||||
});
|
||||
@@ -206,7 +206,7 @@ pub(crate) fn unnecessary_encode_utf8(checker: &Checker, call: &ast::ExprCall) {
|
||||
&call.arguments,
|
||||
Parentheses::Preserve,
|
||||
checker.locator().contents(),
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.map(Fix::safe_edit)
|
||||
});
|
||||
@@ -231,7 +231,7 @@ pub(crate) fn unnecessary_encode_utf8(checker: &Checker, call: &ast::ExprCall) {
|
||||
&call.arguments,
|
||||
Parentheses::Preserve,
|
||||
checker.locator().contents(),
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.map(Fix::safe_edit)
|
||||
});
|
||||
@@ -249,7 +249,7 @@ pub(crate) fn unnecessary_encode_utf8(checker: &Checker, call: &ast::ExprCall) {
|
||||
&call.arguments,
|
||||
Parentheses::Preserve,
|
||||
checker.locator().contents(),
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.map(Fix::safe_edit)
|
||||
});
|
||||
|
||||
@@ -70,7 +70,7 @@ pub(crate) fn useless_class_metaclass_type(checker: &Checker, class_def: &StmtCl
|
||||
arguments,
|
||||
Parentheses::Remove,
|
||||
checker.locator().contents(),
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
)?;
|
||||
|
||||
let range = edit.range();
|
||||
|
||||
@@ -73,7 +73,7 @@ pub(crate) fn useless_object_inheritance(checker: &Checker, class_def: &ast::Stm
|
||||
arguments,
|
||||
Parentheses::Remove,
|
||||
checker.locator().contents(),
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
)?;
|
||||
|
||||
let range = edit.range();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -139,13 +139,8 @@ pub(crate) fn yield_in_for_loop(checker: &Checker, stmt_for: &ast::StmtFor) {
|
||||
let mut diagnostic = checker.report_diagnostic(YieldInForLoop, stmt_for.range());
|
||||
|
||||
let contents = checker.locator().slice(
|
||||
parenthesized_range(
|
||||
iter.as_ref().into(),
|
||||
stmt_for.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(iter.range()),
|
||||
parenthesized_range(iter.as_ref().into(), stmt_for.into(), checker.tokens())
|
||||
.unwrap_or(iter.range()),
|
||||
);
|
||||
let contents = if iter.as_tuple_expr().is_some_and(|it| !it.parenthesized) {
|
||||
format!("yield from ({contents})")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ruff_python_ast::{self as ast, Expr, name::Name, parenthesize::parenthesized_range};
|
||||
use ruff_python_ast::{self as ast, Expr, name::Name, token::parenthesized_range};
|
||||
use ruff_python_codegen::Generator;
|
||||
use ruff_python_semantic::{BindingId, ResolvedReference, SemanticModel};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
@@ -330,12 +330,8 @@ pub(super) fn parenthesize_loop_iter_if_necessary<'a>(
|
||||
let locator = checker.locator();
|
||||
let iter = for_stmt.iter.as_ref();
|
||||
|
||||
let original_parenthesized_range = parenthesized_range(
|
||||
iter.into(),
|
||||
for_stmt.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
);
|
||||
let original_parenthesized_range =
|
||||
parenthesized_range(iter.into(), for_stmt.into(), checker.tokens());
|
||||
|
||||
if let Some(range) = original_parenthesized_range {
|
||||
return Cow::Borrowed(locator.slice(range));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::{
|
||||
Expr, ExprAttribute, ExprBinOp, ExprCall, ExprStringLiteral, ExprSubscript, ExprUnaryOp,
|
||||
Number, Operator, PythonVersion, UnaryOp,
|
||||
@@ -112,8 +112,7 @@ pub(crate) fn fromisoformat_replace_z(checker: &Checker, call: &ExprCall) {
|
||||
let value_full_range = parenthesized_range(
|
||||
replace_time_zone.date.into(),
|
||||
replace_time_zone.parent.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.unwrap_or(replace_time_zone.date.range());
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@ use ruff_python_ast as ast;
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::helpers::contains_effect;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_python_ast::token::{Tokens, parenthesized_range};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Locator;
|
||||
@@ -76,8 +75,8 @@ pub(crate) fn if_exp_instead_of_or_operator(checker: &Checker, if_expr: &ast::Ex
|
||||
Edit::range_replacement(
|
||||
format!(
|
||||
"{} or {}",
|
||||
parenthesize_test(test, if_expr, checker.comment_ranges(), checker.locator()),
|
||||
parenthesize_test(orelse, if_expr, checker.comment_ranges(), checker.locator()),
|
||||
parenthesize_test(test, if_expr, checker.tokens(), checker.locator()),
|
||||
parenthesize_test(orelse, if_expr, checker.tokens(), checker.locator()),
|
||||
),
|
||||
if_expr.range(),
|
||||
),
|
||||
@@ -99,15 +98,10 @@ pub(crate) fn if_exp_instead_of_or_operator(checker: &Checker, if_expr: &ast::Ex
|
||||
fn parenthesize_test<'a>(
|
||||
expr: &Expr,
|
||||
if_expr: &ast::ExprIf,
|
||||
comment_ranges: &CommentRanges,
|
||||
tokens: &Tokens,
|
||||
locator: &Locator<'a>,
|
||||
) -> Cow<'a, str> {
|
||||
if let Some(range) = parenthesized_range(
|
||||
expr.into(),
|
||||
if_expr.into(),
|
||||
comment_ranges,
|
||||
locator.contents(),
|
||||
) {
|
||||
if let Some(range) = parenthesized_range(expr.into(), if_expr.into(), tokens) {
|
||||
Cow::Borrowed(locator.slice(range))
|
||||
} else if matches!(expr, Expr::If(_) | Expr::Lambda(_) | Expr::Named(_)) {
|
||||
Cow::Owned(format!("({})", locator.slice(expr.range())))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_diagnostics::Applicability;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::{Comprehension, Expr, StmtFor};
|
||||
use ruff_python_semantic::analyze::typing;
|
||||
use ruff_python_semantic::analyze::typing::is_io_base_expr;
|
||||
@@ -104,8 +104,7 @@ fn readlines_in_iter(checker: &Checker, iter_expr: &Expr) {
|
||||
let deletion_range = if let Some(parenthesized_range) = parenthesized_range(
|
||||
expr_attr.value.as_ref().into(),
|
||||
expr_attr.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
checker.tokens(),
|
||||
) {
|
||||
expr_call.range().add_start(parenthesized_range.len())
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::Result;
|
||||
use ruff_diagnostics::Applicability;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Expr, Number};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -152,13 +152,8 @@ fn generate_fix(checker: &Checker, call: &ast::ExprCall, base: Base, arg: &Expr)
|
||||
checker.semantic(),
|
||||
)?;
|
||||
|
||||
let arg_range = parenthesized_range(
|
||||
arg.into(),
|
||||
call.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
)
|
||||
.unwrap_or(arg.range());
|
||||
let arg_range =
|
||||
parenthesized_range(arg.into(), call.into(), checker.tokens()).unwrap_or(arg.range());
|
||||
let arg_str = checker.locator().slice(arg_range);
|
||||
|
||||
Ok(Fix::applicable_edits(
|
||||
|
||||
@@ -95,7 +95,7 @@ pub(crate) fn single_item_membership_test(
|
||||
&[membership_test.replacement_op()],
|
||||
std::slice::from_ref(item),
|
||||
expr.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
checker.source(),
|
||||
),
|
||||
expr.range(),
|
||||
|
||||
@@ -163,7 +163,7 @@ fn convert_type_vars(
|
||||
class_arguments,
|
||||
Parentheses::Remove,
|
||||
source,
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
)?;
|
||||
let replace_type_params =
|
||||
Edit::range_replacement(new_type_params.to_string(), type_params.range);
|
||||
|
||||
@@ -3,8 +3,8 @@ use anyhow::Result;
|
||||
use ast::Keyword;
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::helpers::is_constant;
|
||||
use ruff_python_ast::token::Tokens;
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::Locator;
|
||||
@@ -108,9 +108,8 @@ pub(crate) fn default_factory_kwarg(checker: &Checker, call: &ast::ExprCall) {
|
||||
},
|
||||
call.range(),
|
||||
);
|
||||
diagnostic.try_set_fix(|| {
|
||||
convert_to_positional(call, keyword, checker.locator(), checker.comment_ranges())
|
||||
});
|
||||
diagnostic
|
||||
.try_set_fix(|| convert_to_positional(call, keyword, checker.locator(), checker.tokens()));
|
||||
}
|
||||
|
||||
/// Returns `true` if a value is definitively not callable (e.g., `1` or `[]`).
|
||||
@@ -136,7 +135,7 @@ fn convert_to_positional(
|
||||
call: &ast::ExprCall,
|
||||
default_factory: &Keyword,
|
||||
locator: &Locator,
|
||||
comment_ranges: &CommentRanges,
|
||||
tokens: &Tokens,
|
||||
) -> Result<Fix> {
|
||||
if call.arguments.len() == 1 {
|
||||
// Ex) `defaultdict(default_factory=list)`
|
||||
@@ -153,7 +152,7 @@ fn convert_to_positional(
|
||||
&call.arguments,
|
||||
Parentheses::Preserve,
|
||||
locator.contents(),
|
||||
comment_ranges,
|
||||
tokens,
|
||||
)?;
|
||||
|
||||
// Second, insert the value as the first positional argument.
|
||||
|
||||
@@ -128,7 +128,7 @@ pub(crate) fn falsy_dict_get_fallback(checker: &Checker, expr: &Expr) {
|
||||
&call.arguments,
|
||||
Parentheses::Preserve,
|
||||
checker.locator().contents(),
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.map(|edit| Fix::applicable_edit(edit, applicability))
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -77,14 +77,7 @@ pub(crate) fn parenthesize_chained_logical_operators(checker: &Checker, expr: &a
|
||||
) => {
|
||||
let locator = checker.locator();
|
||||
let source_range = bool_op.range();
|
||||
if parenthesized_range(
|
||||
bool_op.into(),
|
||||
expr.into(),
|
||||
checker.comment_ranges(),
|
||||
locator.contents(),
|
||||
)
|
||||
.is_none()
|
||||
{
|
||||
if parenthesized_range(bool_op.into(), expr.into(), checker.tokens()).is_none() {
|
||||
let new_source = format!("({})", locator.slice(source_range));
|
||||
let edit = Edit::range_replacement(new_source, source_range);
|
||||
checker
|
||||
|
||||
@@ -2,7 +2,7 @@ use anyhow::Context;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_semantic::{Scope, ScopeKind};
|
||||
use ruff_python_trivia::{indentation_at_offset, textwrap};
|
||||
use ruff_source_file::LineRanges;
|
||||
@@ -159,8 +159,7 @@ fn use_initvar(
|
||||
let default_loc = parenthesized_range(
|
||||
default.into(),
|
||||
parameter_with_default.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
checker.tokens(),
|
||||
)
|
||||
.unwrap_or(default.range());
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Arguments, Expr};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::Ranged;
|
||||
@@ -116,13 +116,8 @@ fn convert_to_reduce(iterable: &Expr, call: &ast::ExprCall, checker: &Checker) -
|
||||
)?;
|
||||
|
||||
let iterable = checker.locator().slice(
|
||||
parenthesized_range(
|
||||
iterable.into(),
|
||||
(&call.arguments).into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(iterable.range()),
|
||||
parenthesized_range(iterable.into(), (&call.arguments).into(), checker.tokens())
|
||||
.unwrap_or(iterable.range()),
|
||||
);
|
||||
|
||||
Ok(Fix::unsafe_edits(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
use ruff_python_ast::token::TokenKind;
|
||||
use ruff_python_ast::{Expr, ExprCall, parenthesize::parenthesized_range};
|
||||
use ruff_python_ast::{Expr, ExprCall, token::parenthesized_range};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -124,13 +124,8 @@ fn replace_with_map(starmap: &ExprCall, zip: &ExprCall, checker: &Checker) -> Op
|
||||
|
||||
let mut remove_zip = vec![];
|
||||
|
||||
let full_zip_range = parenthesized_range(
|
||||
zip.into(),
|
||||
starmap.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
)
|
||||
.unwrap_or(zip.range());
|
||||
let full_zip_range =
|
||||
parenthesized_range(zip.into(), starmap.into(), checker.tokens()).unwrap_or(zip.range());
|
||||
|
||||
// Delete any parentheses around the `zip` call to prevent that the argument turns into a tuple.
|
||||
remove_zip.push(Edit::range_deletion(TextRange::new(
|
||||
@@ -138,13 +133,8 @@ fn replace_with_map(starmap: &ExprCall, zip: &ExprCall, checker: &Checker) -> Op
|
||||
zip.start(),
|
||||
)));
|
||||
|
||||
let full_zip_func_range = parenthesized_range(
|
||||
(&zip.func).into(),
|
||||
zip.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
)
|
||||
.unwrap_or(zip.func.range());
|
||||
let full_zip_func_range = parenthesized_range((&zip.func).into(), zip.into(), checker.tokens())
|
||||
.unwrap_or(zip.func.range());
|
||||
|
||||
// Delete the `zip` callee
|
||||
remove_zip.push(Edit::range_deletion(full_zip_func_range));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::{Tokens, parenthesized_range};
|
||||
use ruff_python_ast::{Arguments, Expr, ExprCall};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_semantic::analyze::type_inference::{NumberLike, PythonType, ResolvedPythonType};
|
||||
@@ -86,6 +86,7 @@ pub(crate) fn unnecessary_cast_to_int(checker: &Checker, call: &ExprCall) {
|
||||
applicability,
|
||||
checker.semantic(),
|
||||
checker.locator(),
|
||||
checker.tokens(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
);
|
||||
@@ -95,27 +96,26 @@ pub(crate) fn unnecessary_cast_to_int(checker: &Checker, call: &ExprCall) {
|
||||
}
|
||||
|
||||
/// Creates a fix that replaces `int(expression)` with `expression`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn unwrap_int_expression(
|
||||
call: &ExprCall,
|
||||
argument: &Expr,
|
||||
applicability: Applicability,
|
||||
semantic: &SemanticModel,
|
||||
locator: &Locator,
|
||||
tokens: &Tokens,
|
||||
comment_ranges: &CommentRanges,
|
||||
source: &str,
|
||||
) -> Fix {
|
||||
let content = if let Some(range) = parenthesized_range(
|
||||
argument.into(),
|
||||
(&call.arguments).into(),
|
||||
comment_ranges,
|
||||
source,
|
||||
) {
|
||||
let content = if let Some(range) =
|
||||
parenthesized_range(argument.into(), (&call.arguments).into(), tokens)
|
||||
{
|
||||
locator.slice(range).to_string()
|
||||
} else {
|
||||
let parenthesize = semantic.current_expression_parent().is_some()
|
||||
|| argument.is_named_expr()
|
||||
|| locator.count_lines(argument.range()) > 0;
|
||||
if parenthesize && !has_own_parentheses(argument, comment_ranges, source) {
|
||||
if parenthesize && !has_own_parentheses(argument, tokens, source) {
|
||||
format!("({})", locator.slice(argument.range()))
|
||||
} else {
|
||||
locator.slice(argument.range()).to_string()
|
||||
@@ -255,7 +255,7 @@ fn round_applicability(arguments: &Arguments, semantic: &SemanticModel) -> Optio
|
||||
}
|
||||
|
||||
/// Returns `true` if the given [`Expr`] has its own parentheses (e.g., `()`, `[]`, `{}`).
|
||||
fn has_own_parentheses(expr: &Expr, comment_ranges: &CommentRanges, source: &str) -> bool {
|
||||
fn has_own_parentheses(expr: &Expr, tokens: &Tokens, source: &str) -> bool {
|
||||
match expr {
|
||||
Expr::ListComp(_)
|
||||
| Expr::SetComp(_)
|
||||
@@ -276,14 +276,10 @@ fn has_own_parentheses(expr: &Expr, comment_ranges: &CommentRanges, source: &str
|
||||
// f
|
||||
// (10)
|
||||
// ```
|
||||
let func_end = parenthesized_range(
|
||||
call_expr.func.as_ref().into(),
|
||||
call_expr.into(),
|
||||
comment_ranges,
|
||||
source,
|
||||
)
|
||||
.unwrap_or(call_expr.func.range())
|
||||
.end();
|
||||
let func_end =
|
||||
parenthesized_range(call_expr.func.as_ref().into(), call_expr.into(), tokens)
|
||||
.unwrap_or(call_expr.func.range())
|
||||
.end();
|
||||
lines_after_ignoring_trivia(func_end, source) == 0
|
||||
}
|
||||
Expr::Subscript(subscript_expr) => {
|
||||
@@ -291,8 +287,7 @@ fn has_own_parentheses(expr: &Expr, comment_ranges: &CommentRanges, source: &str
|
||||
let subscript_end = parenthesized_range(
|
||||
subscript_expr.value.as_ref().into(),
|
||||
subscript_expr.into(),
|
||||
comment_ranges,
|
||||
source,
|
||||
tokens,
|
||||
)
|
||||
.unwrap_or(subscript_expr.value.range())
|
||||
.end();
|
||||
|
||||
@@ -3,7 +3,7 @@ use ruff_python_ast::{self as ast, BoolOp, CmpOp, Expr};
|
||||
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
use ruff_python_ast::helpers::contains_effect;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -108,22 +108,12 @@ pub(crate) fn unnecessary_key_check(checker: &Checker, expr: &Expr) {
|
||||
format!(
|
||||
"{}.get({})",
|
||||
checker.locator().slice(
|
||||
parenthesized_range(
|
||||
obj_right.into(),
|
||||
right.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(obj_right.range())
|
||||
parenthesized_range(obj_right.into(), right.into(), checker.tokens(),)
|
||||
.unwrap_or(obj_right.range())
|
||||
),
|
||||
checker.locator().slice(
|
||||
parenthesized_range(
|
||||
key_right.into(),
|
||||
right.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.locator().contents(),
|
||||
)
|
||||
.unwrap_or(key_right.range())
|
||||
parenthesized_range(key_right.into(), right.into(), checker.tokens(),)
|
||||
.unwrap_or(key_right.range())
|
||||
),
|
||||
),
|
||||
expr.range(),
|
||||
|
||||
@@ -2,7 +2,7 @@ use ruff_diagnostics::{Applicability, Edit};
|
||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
|
||||
use ruff_python_ast::helpers::is_empty_f_string;
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::token::parenthesized_range;
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -140,31 +140,19 @@ fn fix_unnecessary_literal_in_deque(
|
||||
// call. otherwise, we only delete the `iterable` argument and leave the others untouched.
|
||||
let edit = if let Some(maxlen) = maxlen {
|
||||
let deque_name = checker.locator().slice(
|
||||
parenthesized_range(
|
||||
deque.func.as_ref().into(),
|
||||
deque.into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
)
|
||||
.unwrap_or(deque.func.range()),
|
||||
parenthesized_range(deque.func.as_ref().into(), deque.into(), checker.tokens())
|
||||
.unwrap_or(deque.func.range()),
|
||||
);
|
||||
let len_str = checker.locator().slice(maxlen);
|
||||
let deque_str = format!("{deque_name}(maxlen={len_str})");
|
||||
Edit::range_replacement(deque_str, deque.range)
|
||||
} else {
|
||||
let range = parenthesized_range(
|
||||
iterable.value().into(),
|
||||
(&deque.arguments).into(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
)
|
||||
.unwrap_or(iterable.range());
|
||||
remove_argument(
|
||||
&range,
|
||||
&iterable,
|
||||
&deque.arguments,
|
||||
Parentheses::Preserve,
|
||||
checker.source(),
|
||||
checker.comment_ranges(),
|
||||
checker.tokens(),
|
||||
)?
|
||||
};
|
||||
let has_comments = checker.comment_ranges().intersects(edit.range());
|
||||
|
||||
@@ -4,7 +4,7 @@ use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||
|
||||
use crate::AlwaysFixableViolation;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq, Default)]
|
||||
pub(crate) struct UnusedCodes {
|
||||
pub disabled: Vec<String>,
|
||||
pub duplicated: Vec<String>,
|
||||
@@ -12,6 +12,21 @@ pub(crate) struct UnusedCodes {
|
||||
pub unmatched: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) enum UnusedNOQAKind {
|
||||
Noqa,
|
||||
Suppression,
|
||||
}
|
||||
|
||||
impl UnusedNOQAKind {
|
||||
fn as_str(&self) -> &str {
|
||||
match self {
|
||||
UnusedNOQAKind::Noqa => "`noqa` directive",
|
||||
UnusedNOQAKind::Suppression => "suppression",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `noqa` directives that are no longer applicable.
|
||||
///
|
||||
@@ -46,6 +61,7 @@ pub(crate) struct UnusedCodes {
|
||||
#[violation_metadata(stable_since = "v0.0.155")]
|
||||
pub(crate) struct UnusedNOQA {
|
||||
pub codes: Option<UnusedCodes>,
|
||||
pub kind: UnusedNOQAKind,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for UnusedNOQA {
|
||||
@@ -95,16 +111,20 @@ impl AlwaysFixableViolation for UnusedNOQA {
|
||||
));
|
||||
}
|
||||
if codes_by_reason.is_empty() {
|
||||
"Unused `noqa` directive".to_string()
|
||||
format!("Unused {}", self.kind.as_str())
|
||||
} else {
|
||||
format!("Unused `noqa` directive ({})", codes_by_reason.join("; "))
|
||||
format!(
|
||||
"Unused {} ({})",
|
||||
self.kind.as_str(),
|
||||
codes_by_reason.join("; ")
|
||||
)
|
||||
}
|
||||
}
|
||||
None => "Unused blanket `noqa` directive".to_string(),
|
||||
None => format!("Unused blanket {}", self.kind.as_str()),
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Remove unused `noqa` directive".to_string()
|
||||
format!("Remove unused {}", self.kind.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
+linter.preview = enabled
|
||||
|
||||
--- Summary ---
|
||||
Removed: 9
|
||||
Added: 1
|
||||
Removed: 14
|
||||
Added: 11
|
||||
|
||||
--- Removed ---
|
||||
E741 Ambiguous variable name: `I`
|
||||
@@ -148,8 +148,136 @@ help: Remove assignment to unused variable `I`
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
F841 [*] Local variable `foo` is assigned to but never used
|
||||
--> suppressions.py:62:5
|
||||
|
|
||||
60 | # TODO: Duplicate codes should be counted as duplicate, not unused
|
||||
61 | # ruff: disable[F841, F841]
|
||||
62 | foo = 0
|
||||
| ^^^
|
||||
|
|
||||
help: Remove assignment to unused variable `foo`
|
||||
59 | def f():
|
||||
60 | # TODO: Duplicate codes should be counted as duplicate, not unused
|
||||
61 | # ruff: disable[F841, F841]
|
||||
- foo = 0
|
||||
62 + pass
|
||||
63 |
|
||||
64 |
|
||||
65 | def f():
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
F841 [*] Local variable `foo` is assigned to but never used
|
||||
--> suppressions.py:70:5
|
||||
|
|
||||
68 | # ruff: disable[F841]
|
||||
69 | # ruff: disable[F841]
|
||||
70 | foo = 0
|
||||
| ^^^
|
||||
|
|
||||
help: Remove assignment to unused variable `foo`
|
||||
67 | # and the other should trigger an unused suppression diagnostic
|
||||
68 | # ruff: disable[F841]
|
||||
69 | # ruff: disable[F841]
|
||||
- foo = 0
|
||||
70 + pass
|
||||
71 |
|
||||
72 |
|
||||
73 | def f():
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
F841 [*] Local variable `foo` is assigned to but never used
|
||||
--> suppressions.py:76:5
|
||||
|
|
||||
74 | # Multiple codes but only one is used
|
||||
75 | # ruff: disable[E741, F401, F841]
|
||||
76 | foo = 0
|
||||
| ^^^
|
||||
|
|
||||
help: Remove assignment to unused variable `foo`
|
||||
73 | def f():
|
||||
74 | # Multiple codes but only one is used
|
||||
75 | # ruff: disable[E741, F401, F841]
|
||||
- foo = 0
|
||||
76 + pass
|
||||
77 |
|
||||
78 |
|
||||
79 | def f():
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
E741 Ambiguous variable name: `I`
|
||||
--> suppressions.py:82:5
|
||||
|
|
||||
80 | # Multiple codes but only two are used
|
||||
81 | # ruff: disable[E741, F401, F841]
|
||||
82 | I = 0
|
||||
| ^
|
||||
|
|
||||
|
||||
|
||||
F841 [*] Local variable `I` is assigned to but never used
|
||||
--> suppressions.py:82:5
|
||||
|
|
||||
80 | # Multiple codes but only two are used
|
||||
81 | # ruff: disable[E741, F401, F841]
|
||||
82 | I = 0
|
||||
| ^
|
||||
|
|
||||
help: Remove assignment to unused variable `I`
|
||||
79 | def f():
|
||||
80 | # Multiple codes but only two are used
|
||||
81 | # ruff: disable[E741, F401, F841]
|
||||
- I = 0
|
||||
82 + pass
|
||||
83 |
|
||||
84 |
|
||||
85 | def f():
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
|
||||
|
||||
--- Added ---
|
||||
RUF100 [*] Unused suppression (non-enabled: `E501`)
|
||||
--> suppressions.py:46:5
|
||||
|
|
||||
44 | # Neither of these are ignored and warnings are
|
||||
45 | # logged to user
|
||||
46 | # ruff: disable[E501]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
47 | I = 1
|
||||
48 | # ruff: enable[E501]
|
||||
|
|
||||
help: Remove unused suppression
|
||||
43 | def f():
|
||||
44 | # Neither of these are ignored and warnings are
|
||||
45 | # logged to user
|
||||
- # ruff: disable[E501]
|
||||
46 | I = 1
|
||||
47 | # ruff: enable[E501]
|
||||
48 |
|
||||
|
||||
|
||||
RUF100 [*] Unused suppression (non-enabled: `E501`)
|
||||
--> suppressions.py:48:5
|
||||
|
|
||||
46 | # ruff: disable[E501]
|
||||
47 | I = 1
|
||||
48 | # ruff: enable[E501]
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Remove unused suppression
|
||||
45 | # logged to user
|
||||
46 | # ruff: disable[E501]
|
||||
47 | I = 1
|
||||
- # ruff: enable[E501]
|
||||
48 |
|
||||
49 |
|
||||
50 | def f():
|
||||
|
||||
|
||||
RUF100 [*] Unused `noqa` directive (unused: `E741`, `F841`)
|
||||
--> suppressions.py:55:12
|
||||
|
|
||||
@@ -166,3 +294,158 @@ help: Remove unused `noqa` directive
|
||||
- I = 1 # noqa: E741,F841
|
||||
55 + I = 1
|
||||
56 | # ruff:enable[E741,F841]
|
||||
57 |
|
||||
58 |
|
||||
|
||||
|
||||
RUF100 [*] Unused suppression (unused: `F841`)
|
||||
--> suppressions.py:61:21
|
||||
|
|
||||
59 | def f():
|
||||
60 | # TODO: Duplicate codes should be counted as duplicate, not unused
|
||||
61 | # ruff: disable[F841, F841]
|
||||
| ^^^^
|
||||
62 | foo = 0
|
||||
|
|
||||
help: Remove unused suppression
|
||||
58 |
|
||||
59 | def f():
|
||||
60 | # TODO: Duplicate codes should be counted as duplicate, not unused
|
||||
- # ruff: disable[F841, F841]
|
||||
61 + # ruff: disable[F841]
|
||||
62 | foo = 0
|
||||
63 |
|
||||
64 |
|
||||
|
||||
|
||||
RUF100 [*] Unused suppression (unused: `F841`)
|
||||
--> suppressions.py:69:5
|
||||
|
|
||||
67 | # and the other should trigger an unused suppression diagnostic
|
||||
68 | # ruff: disable[F841]
|
||||
69 | # ruff: disable[F841]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
70 | foo = 0
|
||||
|
|
||||
help: Remove unused suppression
|
||||
66 | # Overlapping range suppressions, one should be marked as used,
|
||||
67 | # and the other should trigger an unused suppression diagnostic
|
||||
68 | # ruff: disable[F841]
|
||||
- # ruff: disable[F841]
|
||||
69 | foo = 0
|
||||
70 |
|
||||
71 |
|
||||
|
||||
|
||||
RUF100 [*] Unused suppression (unused: `E741`)
|
||||
--> suppressions.py:75:21
|
||||
|
|
||||
73 | def f():
|
||||
74 | # Multiple codes but only one is used
|
||||
75 | # ruff: disable[E741, F401, F841]
|
||||
| ^^^^
|
||||
76 | foo = 0
|
||||
|
|
||||
help: Remove unused suppression
|
||||
72 |
|
||||
73 | def f():
|
||||
74 | # Multiple codes but only one is used
|
||||
- # ruff: disable[E741, F401, F841]
|
||||
75 + # ruff: disable[F401, F841]
|
||||
76 | foo = 0
|
||||
77 |
|
||||
78 |
|
||||
|
||||
|
||||
RUF100 [*] Unused suppression (non-enabled: `F401`)
|
||||
--> suppressions.py:75:27
|
||||
|
|
||||
73 | def f():
|
||||
74 | # Multiple codes but only one is used
|
||||
75 | # ruff: disable[E741, F401, F841]
|
||||
| ^^^^
|
||||
76 | foo = 0
|
||||
|
|
||||
help: Remove unused suppression
|
||||
72 |
|
||||
73 | def f():
|
||||
74 | # Multiple codes but only one is used
|
||||
- # ruff: disable[E741, F401, F841]
|
||||
75 + # ruff: disable[E741, F841]
|
||||
76 | foo = 0
|
||||
77 |
|
||||
78 |
|
||||
|
||||
|
||||
RUF100 [*] Unused suppression (non-enabled: `F401`)
|
||||
--> suppressions.py:81:27
|
||||
|
|
||||
79 | def f():
|
||||
80 | # Multiple codes but only two are used
|
||||
81 | # ruff: disable[E741, F401, F841]
|
||||
| ^^^^
|
||||
82 | I = 0
|
||||
|
|
||||
help: Remove unused suppression
|
||||
78 |
|
||||
79 | def f():
|
||||
80 | # Multiple codes but only two are used
|
||||
- # ruff: disable[E741, F401, F841]
|
||||
81 + # ruff: disable[E741, F841]
|
||||
82 | I = 0
|
||||
83 |
|
||||
84 |
|
||||
|
||||
|
||||
RUF100 [*] Unused suppression (unused: `E741`)
|
||||
--> suppressions.py:87:21
|
||||
|
|
||||
85 | def f():
|
||||
86 | # Multiple codes but none are used
|
||||
87 | # ruff: disable[E741, F401, F841]
|
||||
| ^^^^
|
||||
88 | print("hello")
|
||||
|
|
||||
help: Remove unused suppression
|
||||
84 |
|
||||
85 | def f():
|
||||
86 | # Multiple codes but none are used
|
||||
- # ruff: disable[E741, F401, F841]
|
||||
87 + # ruff: disable[F401, F841]
|
||||
88 | print("hello")
|
||||
|
||||
|
||||
RUF100 [*] Unused suppression (non-enabled: `F401`)
|
||||
--> suppressions.py:87:27
|
||||
|
|
||||
85 | def f():
|
||||
86 | # Multiple codes but none are used
|
||||
87 | # ruff: disable[E741, F401, F841]
|
||||
| ^^^^
|
||||
88 | print("hello")
|
||||
|
|
||||
help: Remove unused suppression
|
||||
84 |
|
||||
85 | def f():
|
||||
86 | # Multiple codes but none are used
|
||||
- # ruff: disable[E741, F401, F841]
|
||||
87 + # ruff: disable[E741, F841]
|
||||
88 | print("hello")
|
||||
|
||||
|
||||
RUF100 [*] Unused suppression (unused: `F841`)
|
||||
--> suppressions.py:87:33
|
||||
|
|
||||
85 | def f():
|
||||
86 | # Multiple codes but none are used
|
||||
87 | # ruff: disable[E741, F401, F841]
|
||||
| ^^^^
|
||||
88 | print("hello")
|
||||
|
|
||||
help: Remove unused suppression
|
||||
84 |
|
||||
85 | def f():
|
||||
86 | # Multiple codes but none are used
|
||||
- # ruff: disable[E741, F401, F841]
|
||||
87 + # ruff: disable[E741, F401]
|
||||
88 | print("hello")
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use compact_str::CompactString;
|
||||
use core::fmt;
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_diagnostics::{Edit, Fix};
|
||||
use ruff_python_ast::token::{TokenKind, Tokens};
|
||||
use ruff_python_ast::whitespace::indentation;
|
||||
use std::cell::Cell;
|
||||
use std::{error::Error, fmt::Formatter};
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -10,10 +12,14 @@ use ruff_python_trivia::Cursor;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize, TextSlice};
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
|
||||
use crate::Locator;
|
||||
use crate::checkers::ast::LintContext;
|
||||
use crate::codes::Rule;
|
||||
use crate::fix::edits::delete_comment;
|
||||
use crate::preview::is_range_suppressions_enabled;
|
||||
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA, UnusedNOQAKind};
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
enum SuppressionAction {
|
||||
Disable,
|
||||
@@ -35,7 +41,6 @@ pub(crate) struct SuppressionComment {
|
||||
reason: TextRange,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl SuppressionComment {
|
||||
/// Return the suppressed codes as strings
|
||||
fn codes_as_str<'src>(&self, source: &'src str) -> impl Iterator<Item = &'src str> {
|
||||
@@ -52,7 +57,6 @@ pub(crate) struct PendingSuppressionComment<'a> {
|
||||
comment: SuppressionComment,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl PendingSuppressionComment<'_> {
|
||||
/// Whether the comment "matches" another comment, based on indentation and suppressed codes
|
||||
/// Expects a "forward search" for matches, ie, will only match if the current comment is a
|
||||
@@ -68,8 +72,7 @@ impl PendingSuppressionComment<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Suppression {
|
||||
/// The lint code being suppressed
|
||||
code: CompactString,
|
||||
@@ -79,9 +82,11 @@ pub(crate) struct Suppression {
|
||||
|
||||
/// Any comments associated with the suppression
|
||||
comments: SmallVec<[SuppressionComment; 2]>,
|
||||
|
||||
/// Whether this suppression actually suppressed a diagnostic
|
||||
used: Cell<bool>,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) enum InvalidSuppressionKind {
|
||||
/// Trailing suppression not supported
|
||||
@@ -114,7 +119,6 @@ pub struct Suppressions {
|
||||
errors: Vec<ParseError>,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl Suppressions {
|
||||
pub fn from_tokens(settings: &LinterSettings, source: &str, tokens: &Tokens) -> Suppressions {
|
||||
if is_range_suppressions_enabled(settings) {
|
||||
@@ -147,11 +151,90 @@ impl Suppressions {
|
||||
|
||||
for suppression in &self.valid {
|
||||
if *code == suppression.code.as_str() && suppression.range.contains_range(range) {
|
||||
suppression.used.set(true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn check_suppressions(&self, context: &LintContext, locator: &Locator) {
|
||||
if !context.any_rule_enabled(&[Rule::UnusedNOQA, Rule::InvalidRuleCode]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let unused = self
|
||||
.valid
|
||||
.iter()
|
||||
.filter(|suppression| !suppression.used.get());
|
||||
|
||||
for suppression in unused {
|
||||
let Ok(rule) = Rule::from_code(&suppression.code) else {
|
||||
continue; // TODO: invalid code
|
||||
};
|
||||
for comment in &suppression.comments {
|
||||
let mut range = comment.range;
|
||||
let edit = if comment.codes.len() == 1 {
|
||||
delete_comment(comment.range, locator)
|
||||
} else {
|
||||
let code_index = comment
|
||||
.codes
|
||||
.iter()
|
||||
.position(|range| locator.slice(range) == suppression.code)
|
||||
.unwrap();
|
||||
range = comment.codes[code_index];
|
||||
let code_range = if code_index < (comment.codes.len() - 1) {
|
||||
TextRange::new(
|
||||
comment.codes[code_index].start(),
|
||||
comment.codes[code_index + 1].start(),
|
||||
)
|
||||
} else {
|
||||
TextRange::new(
|
||||
comment.codes[code_index - 1].end(),
|
||||
comment.codes[code_index].end(),
|
||||
)
|
||||
};
|
||||
Edit::range_deletion(code_range)
|
||||
};
|
||||
|
||||
let codes = if context.is_rule_enabled(rule) {
|
||||
UnusedCodes {
|
||||
unmatched: vec![suppression.code.to_string()],
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
UnusedCodes {
|
||||
disabled: vec![suppression.code.to_string()],
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
|
||||
let mut diagnostic = context.report_diagnostic(
|
||||
UnusedNOQA {
|
||||
codes: Some(codes),
|
||||
kind: UnusedNOQAKind::Suppression,
|
||||
},
|
||||
range,
|
||||
);
|
||||
diagnostic.set_fix(Fix::safe_edit(edit));
|
||||
}
|
||||
}
|
||||
|
||||
for error in self
|
||||
.errors
|
||||
.iter()
|
||||
.filter(|error| error.kind == ParseErrorKind::MissingCodes)
|
||||
{
|
||||
let mut diagnostic = context.report_diagnostic(
|
||||
UnusedNOQA {
|
||||
codes: Some(UnusedCodes::default()),
|
||||
kind: UnusedNOQAKind::Suppression,
|
||||
},
|
||||
error.range,
|
||||
);
|
||||
diagnostic.set_fix(Fix::safe_edit(delete_comment(error.range, locator)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -276,6 +359,7 @@ impl<'a> SuppressionsBuilder<'a> {
|
||||
code: code.into(),
|
||||
range: combined_range,
|
||||
comments: smallvec![comment.comment.clone(), other.comment.clone()],
|
||||
used: false.into(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -292,6 +376,7 @@ impl<'a> SuppressionsBuilder<'a> {
|
||||
code: code.into(),
|
||||
range: implicit_range,
|
||||
comments: smallvec![comment.comment.clone()],
|
||||
used: false.into(),
|
||||
});
|
||||
}
|
||||
self.pending.remove(comment_index);
|
||||
@@ -405,8 +490,10 @@ impl<'src> SuppressionParser<'src> {
|
||||
} else if self.cursor.as_str().starts_with("enable") {
|
||||
self.cursor.skip_bytes("enable".len());
|
||||
Ok(SuppressionAction::Enable)
|
||||
} else if self.cursor.as_str().starts_with("noqa") {
|
||||
// file-level "noqa" variant, ignore for now
|
||||
} else if self.cursor.as_str().starts_with("noqa")
|
||||
|| self.cursor.as_str().starts_with("isort")
|
||||
{
|
||||
// alternate suppression variants, ignore for now
|
||||
self.error(ParseErrorKind::NotASuppression)
|
||||
} else {
|
||||
self.error(ParseErrorKind::UnknownAction)
|
||||
|
||||
@@ -3,13 +3,14 @@ use std::path::Path;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_python_trivia::{CommentRanges, SimpleTokenKind, SimpleTokenizer, indentation_at_offset};
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer, indentation_at_offset};
|
||||
use ruff_source_file::LineRanges;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::name::{Name, QualifiedName, QualifiedNameBuilder};
|
||||
use crate::parenthesize::parenthesized_range;
|
||||
use crate::statement_visitor::StatementVisitor;
|
||||
use crate::token::Tokens;
|
||||
use crate::token::parenthesized_range;
|
||||
use crate::visitor::Visitor;
|
||||
use crate::{
|
||||
self as ast, Arguments, AtomicNodeIndex, CmpOp, DictItem, ExceptHandler, Expr, ExprNoneLiteral,
|
||||
@@ -1474,7 +1475,7 @@ pub fn generate_comparison(
|
||||
ops: &[CmpOp],
|
||||
comparators: &[Expr],
|
||||
parent: AnyNodeRef,
|
||||
comment_ranges: &CommentRanges,
|
||||
tokens: &Tokens,
|
||||
source: &str,
|
||||
) -> String {
|
||||
let start = left.start();
|
||||
@@ -1483,8 +1484,7 @@ pub fn generate_comparison(
|
||||
|
||||
// Add the left side of the comparison.
|
||||
contents.push_str(
|
||||
&source[parenthesized_range(left.into(), parent, comment_ranges, source)
|
||||
.unwrap_or(left.range())],
|
||||
&source[parenthesized_range(left.into(), parent, tokens).unwrap_or(left.range())],
|
||||
);
|
||||
|
||||
for (op, comparator) in ops.iter().zip(comparators) {
|
||||
@@ -1504,7 +1504,7 @@ pub fn generate_comparison(
|
||||
|
||||
// Add the right side of the comparison.
|
||||
contents.push_str(
|
||||
&source[parenthesized_range(comparator.into(), parent, comment_ranges, source)
|
||||
&source[parenthesized_range(comparator.into(), parent, tokens)
|
||||
.unwrap_or(comparator.range())],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -154,9 +154,7 @@ impl Tokens {
|
||||
// the tokens which is valid as well.
|
||||
assert!(
|
||||
offset >= last.end(),
|
||||
"Offset {:?} is inside a token range {:?}",
|
||||
offset,
|
||||
last.range()
|
||||
"Offset {offset:?} is inside token `{last:?}`",
|
||||
);
|
||||
}
|
||||
before
|
||||
@@ -181,9 +179,7 @@ impl Tokens {
|
||||
// the tokens which is valid as well.
|
||||
assert!(
|
||||
offset <= first.start(),
|
||||
"Offset {:?} is inside a token range {:?}",
|
||||
offset,
|
||||
first.range()
|
||||
"Offset {offset:?} is inside token `{first:?}`",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -391,7 +387,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Offset 5 is inside a token range 4..7")]
|
||||
#[should_panic(expected = "Offset 5 is inside token `Name 4..7`")]
|
||||
fn tokens_after_offset_inside_token() {
|
||||
let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
|
||||
tokens.after(TextSize::new(5));
|
||||
@@ -453,7 +449,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Offset 5 is inside a token range 4..7")]
|
||||
#[should_panic(expected = "Offset 5 is inside token `Name 4..7`")]
|
||||
fn tokens_before_offset_inside_token() {
|
||||
let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
|
||||
tokens.before(TextSize::new(5));
|
||||
@@ -505,14 +501,14 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Offset 5 is inside a token range 4..7")]
|
||||
#[should_panic(expected = "Offset 5 is inside token `Name 4..7`")]
|
||||
fn tokens_in_range_start_offset_inside_token() {
|
||||
let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
|
||||
tokens.in_range(TextRange::new(5.into(), 10.into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Offset 6 is inside a token range 4..7")]
|
||||
#[should_panic(expected = "Offset 6 is inside token `Name 4..7`")]
|
||||
fn tokens_in_range_end_offset_inside_token() {
|
||||
let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
|
||||
tokens.in_range(TextRange::new(0.into(), 6.into()));
|
||||
|
||||
@@ -43,7 +43,8 @@ tracing = { workspace = true }
|
||||
[dev-dependencies]
|
||||
ruff_formatter = { workspace = true }
|
||||
|
||||
insta = { workspace = true, features = ["glob"] }
|
||||
datatest-stable = { workspace = true }
|
||||
insta = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
@@ -54,8 +55,8 @@ similar = { workspace = true }
|
||||
ignored = ["ruff_cache"]
|
||||
|
||||
[[test]]
|
||||
name = "ruff_python_formatter_fixtures"
|
||||
path = "tests/fixtures.rs"
|
||||
name = "fixtures"
|
||||
harness = false
|
||||
test = true
|
||||
required-features = ["serde"]
|
||||
|
||||
|
||||
@@ -125,6 +125,13 @@ lambda a, /, c: a
|
||||
*x: x
|
||||
)
|
||||
|
||||
(
|
||||
lambda
|
||||
# comment
|
||||
*x,
|
||||
**y: x
|
||||
)
|
||||
|
||||
(
|
||||
lambda
|
||||
# comment 1
|
||||
@@ -196,6 +203,17 @@ lambda: ( # comment
|
||||
x
|
||||
)
|
||||
|
||||
(
|
||||
lambda # 1
|
||||
# 2
|
||||
x, # 3
|
||||
# 4
|
||||
y
|
||||
: # 5
|
||||
# 6
|
||||
x
|
||||
)
|
||||
|
||||
(
|
||||
lambda
|
||||
x,
|
||||
@@ -204,6 +222,71 @@ lambda: ( # comment
|
||||
z
|
||||
)
|
||||
|
||||
|
||||
# Leading
|
||||
lambda x: (
|
||||
lambda y: lambda z: x
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ y
|
||||
+ z # Trailing
|
||||
) # Trailing
|
||||
|
||||
|
||||
# Leading
|
||||
lambda x: lambda y: lambda z: [
|
||||
x,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
y,
|
||||
z
|
||||
] # Trailing
|
||||
# Trailing
|
||||
|
||||
lambda self, araa, kkkwargs=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs), e=1, f=2, g=2: d
|
||||
|
||||
# Regression tests for https://github.com/astral-sh/ruff/issues/8179
|
||||
@@ -228,3 +311,496 @@ def a():
|
||||
g = 10
|
||||
)
|
||||
|
||||
def a():
|
||||
return b(
|
||||
c,
|
||||
d,
|
||||
e,
|
||||
f=lambda self, *args, **kwargs: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(
|
||||
*args, **kwargs
|
||||
) + 1,
|
||||
)
|
||||
|
||||
# Additional ecosystem cases from https://github.com/astral-sh/ruff/pull/21385
|
||||
class C:
|
||||
def foo():
|
||||
mock_service.return_value.bucket.side_effect = lambda name: (
|
||||
source_bucket
|
||||
if name == source_bucket_name
|
||||
else storage.Bucket(mock_service, destination_bucket_name)
|
||||
)
|
||||
|
||||
class C:
|
||||
function_dict: Dict[Text, Callable[[CRFToken], Any]] = {
|
||||
CRFEntityExtractorOptions.POS2: lambda crf_token: crf_token.pos_tag[:2]
|
||||
if crf_token.pos_tag is not None
|
||||
else None,
|
||||
}
|
||||
|
||||
name = re.sub(r"[^\x21\x23-\x5b\x5d-\x7e]...............", lambda m: f"\\{m.group(0)}", p["name"])
|
||||
|
||||
def foo():
|
||||
if True:
|
||||
if True:
|
||||
return (
|
||||
lambda x: np.exp(cs(np.log(x.to(u.MeV).value))) * u.MeV * u.cm**2 / u.g
|
||||
)
|
||||
|
||||
class C:
|
||||
_is_recognized_dtype: Callable[[DtypeObj], bool] = lambda x: lib.is_np_dtype(
|
||||
x, "M"
|
||||
) or isinstance(x, DatetimeTZDtype)
|
||||
|
||||
class C:
|
||||
def foo():
|
||||
if True:
|
||||
transaction_count = self._query_txs_for_range(
|
||||
get_count_fn=lambda from_ts, to_ts, _chain_id=chain_id: db_evmtx.count_transactions_in_range(
|
||||
chain_id=_chain_id,
|
||||
from_ts=from_ts,
|
||||
to_ts=to_ts,
|
||||
),
|
||||
)
|
||||
|
||||
transaction_count = self._query_txs_for_range(
|
||||
get_count_fn=lambda from_ts, to_ts, _chain_id=chain_id: db_evmtx.count_transactions_in_range[_chain_id, from_ts, to_ts],
|
||||
)
|
||||
|
||||
def ddb():
|
||||
sql = (
|
||||
lambda var, table, n=N: f"""
|
||||
CREATE TABLE {table} AS
|
||||
SELECT ROW_NUMBER() OVER () AS id, {var}
|
||||
FROM (
|
||||
SELECT {var}
|
||||
FROM RANGE({n}) _ ({var})
|
||||
ORDER BY RANDOM()
|
||||
)
|
||||
"""
|
||||
)
|
||||
|
||||
long_assignment_target.with_attribute.and_a_slice[with_an_index] = ( # 1
|
||||
# 2
|
||||
lambda x, y, z: # 3
|
||||
# 4
|
||||
x + y + z # 5
|
||||
# 6
|
||||
)
|
||||
|
||||
long_assignment_target.with_attribute.and_a_slice[with_an_index] = (
|
||||
lambda x, y, z: x + y + z
|
||||
)
|
||||
|
||||
long_assignment_target.with_attribute.and_a_slice[with_an_index] = lambda x, y, z: x + y + z
|
||||
|
||||
very_long_variable_name_x, very_long_variable_name_y = lambda a: a + some_very_long_expression, lambda b: b * another_very_long_expression_here
|
||||
|
||||
very_long_variable_name_for_result += lambda x: very_long_function_call_that_should_definitely_be_parenthesized_now(x, more_args, additional_parameters)
|
||||
|
||||
|
||||
if 1:
|
||||
if 2:
|
||||
if 3:
|
||||
if self.location in EVM_EVMLIKE_LOCATIONS and database is not None:
|
||||
exported_dict["notes"] = EVM_ADDRESS_REGEX.sub(
|
||||
repl=lambda matched_address: self._maybe_add_label_with_address(
|
||||
database=database,
|
||||
matched_address=matched_address,
|
||||
),
|
||||
string=exported_dict["notes"],
|
||||
)
|
||||
|
||||
class C:
|
||||
def f():
|
||||
return dict(
|
||||
filter(
|
||||
lambda intent_response: self.is_retrieval_intent_response(
|
||||
intent_response
|
||||
),
|
||||
self.responses.items(),
|
||||
)
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"op",
|
||||
[
|
||||
# Not fluent
|
||||
param(
|
||||
lambda left, right: (
|
||||
ibis.timestamp("2017-04-01")
|
||||
),
|
||||
),
|
||||
# These four are fluent and fit on one line inside the parenthesized
|
||||
# lambda body
|
||||
param(
|
||||
lambda left, right: (
|
||||
ibis.timestamp("2017-04-01").cast(dt.date)
|
||||
),
|
||||
),
|
||||
param(
|
||||
lambda left, right: (
|
||||
ibis.timestamp("2017-04-01").cast(dt.date).between(left, right)
|
||||
),
|
||||
),
|
||||
param(lambda left, right: ibis.timestamp("2017-04-01").cast(dt.date)),
|
||||
param(lambda left, right: ibis.timestamp("2017-04-01").cast(dt.date).between(left, right)),
|
||||
# This is too long on one line in the lambda body and gets wrapped
|
||||
# inside the body.
|
||||
param(
|
||||
lambda left, right: (
|
||||
ibis.timestamp("2017-04-01").cast(dt.date).between(left, right).between(left, right)
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_string_temporal_compare_between(con, op, left, right): ...
|
||||
|
||||
[
|
||||
(
|
||||
lambda eval_df, _: MetricValue(
|
||||
scores=eval_df["prediction"].tolist(),
|
||||
aggregate_results={"prediction_sum": sum(eval_df["prediction"])},
|
||||
)
|
||||
),
|
||||
]
|
||||
|
||||
# reuses the list parentheses
|
||||
lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: [xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz]
|
||||
|
||||
# adds parentheses around the body
|
||||
lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: xxxxxxxxxxxxxxxxxxxx + yyyyyyyyyyyyyyyyyyyy + zzzzzzzzzzzzzzzzzzzz
|
||||
|
||||
# removes parentheses around the body
|
||||
lambda xxxxxxxxxxxxxxxxxxxx: (xxxxxxxxxxxxxxxxxxxx + 1)
|
||||
|
||||
mapper = lambda x: dict_with_default[np.nan if isinstance(x, float) and np.isnan(x) else x]
|
||||
|
||||
lambda x, y, z: (
|
||||
x + y + z
|
||||
)
|
||||
|
||||
lambda x, y, z: (
|
||||
x + y + z
|
||||
# trailing body
|
||||
)
|
||||
|
||||
lambda x, y, z: (
|
||||
x + y + z # trailing eol body
|
||||
)
|
||||
|
||||
lambda x, y, z: (
|
||||
x + y + z
|
||||
) # trailing lambda
|
||||
|
||||
lambda x, y, z: (
|
||||
# leading body
|
||||
x + y + z
|
||||
)
|
||||
|
||||
lambda x, y, z: ( # leading eol body
|
||||
x + y + z
|
||||
)
|
||||
|
||||
(
|
||||
lambda name:
|
||||
source_bucket # trailing eol comment
|
||||
if name == source_bucket_name
|
||||
else storage.Bucket(mock_service, destination_bucket_name)
|
||||
)
|
||||
|
||||
(
|
||||
lambda name:
|
||||
# dangling header comment
|
||||
source_bucket
|
||||
if name == source_bucket_name
|
||||
else storage.Bucket(mock_service, destination_bucket_name)
|
||||
)
|
||||
|
||||
x = (
|
||||
lambda name:
|
||||
# dangling header comment
|
||||
source_bucket
|
||||
if name == source_bucket_name
|
||||
else storage.Bucket(mock_service, destination_bucket_name)
|
||||
)
|
||||
|
||||
(
|
||||
lambda name: # dangling header comment
|
||||
(
|
||||
source_bucket
|
||||
if name == source_bucket_name
|
||||
else storage.Bucket(mock_service, destination_bucket_name)
|
||||
)
|
||||
)
|
||||
|
||||
(
|
||||
lambda from_ts, to_ts, _chain_id=chain_id: # dangling eol header comment
|
||||
db_evmtx.count_transactions_in_range(
|
||||
chain_id=_chain_id,
|
||||
from_ts=from_ts,
|
||||
to_ts=to_ts,
|
||||
)
|
||||
)
|
||||
|
||||
(
|
||||
lambda from_ts, to_ts, _chain_id=chain_id:
|
||||
# dangling header comment before call
|
||||
db_evmtx.count_transactions_in_range(
|
||||
chain_id=_chain_id,
|
||||
from_ts=from_ts,
|
||||
to_ts=to_ts,
|
||||
)
|
||||
)
|
||||
|
||||
(
|
||||
lambda left, right:
|
||||
# comment
|
||||
ibis.timestamp("2017-04-01").cast(dt.date).between(left, right)
|
||||
)
|
||||
|
||||
(
|
||||
lambda left, right:
|
||||
ibis.timestamp("2017-04-01") # comment
|
||||
.cast(dt.date)
|
||||
.between(left, right)
|
||||
)
|
||||
|
||||
(
|
||||
lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy:
|
||||
# comment
|
||||
[xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz]
|
||||
)
|
||||
|
||||
(
|
||||
lambda x, y:
|
||||
# comment
|
||||
{
|
||||
"key": x,
|
||||
"another": y,
|
||||
}
|
||||
)
|
||||
|
||||
(
|
||||
lambda x, y:
|
||||
# comment
|
||||
(
|
||||
x,
|
||||
y,
|
||||
z
|
||||
)
|
||||
)
|
||||
|
||||
(
|
||||
lambda x:
|
||||
# comment
|
||||
dict_with_default[np.nan if isinstance(x, float) and np.isnan(x) else x]
|
||||
)
|
||||
|
||||
(
|
||||
lambda from_ts, to_ts, _chain_id=chain_id:
|
||||
db_evmtx.count_transactions_in_range[
|
||||
# comment
|
||||
_chain_id, from_ts, to_ts
|
||||
]
|
||||
)
|
||||
|
||||
(
|
||||
lambda
|
||||
# comment
|
||||
*args, **kwargs:
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs) + 1
|
||||
)
|
||||
|
||||
(
|
||||
lambda # comment
|
||||
*args, **kwargs:
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs) + 1
|
||||
)
|
||||
|
||||
(
|
||||
lambda # comment 1
|
||||
# comment 2
|
||||
*args, **kwargs: # comment 3
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs) + 1
|
||||
)
|
||||
|
||||
(
|
||||
lambda # comment 1
|
||||
*args, **kwargs: # comment 3
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs) + 1
|
||||
)
|
||||
|
||||
(
|
||||
lambda *args, **kwargs:
|
||||
# comment 1
|
||||
( # comment 2
|
||||
# comment 3
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*args, **kwargs) + 1 # comment 4
|
||||
# comment 5
|
||||
) # comment 6
|
||||
)
|
||||
|
||||
(
|
||||
lambda *brgs, **kwargs:
|
||||
# comment 1
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa( # comment 2
|
||||
# comment 3
|
||||
*brgs, **kwargs) + 1 # comment 4
|
||||
# comment 5
|
||||
)
|
||||
|
||||
(
|
||||
lambda *crgs, **kwargs: # comment 1
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*crgs, **kwargs) + 1
|
||||
)
|
||||
|
||||
(
|
||||
lambda *drgs, **kwargs: # comment 1
|
||||
(
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*drgs, **kwargs) + 1
|
||||
)
|
||||
)
|
||||
|
||||
(
|
||||
lambda * # comment 1
|
||||
ergs, **
|
||||
# comment 2
|
||||
kwargs # comment 3
|
||||
: # comment 4
|
||||
(
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(*ergs, **kwargs) + 1
|
||||
)
|
||||
)
|
||||
|
||||
(
|
||||
lambda # 1
|
||||
# 2
|
||||
left, # 3
|
||||
# 4
|
||||
right: # 5
|
||||
# 6
|
||||
ibis.timestamp("2017-04-01").cast(dt.date).between(left, right)
|
||||
)
|
||||
|
||||
(
|
||||
lambda x: # outer comment 1
|
||||
(
|
||||
lambda y: # inner comment 1
|
||||
# inner comment 2
|
||||
lambda z: (
|
||||
# innermost comment
|
||||
x + y + z
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
foo(
|
||||
lambda from_ts, # comment prevents collapsing the parameters to one line
|
||||
to_ts, _chain_id=chain_id: db_evmtx.count_transactions_in_range(
|
||||
chain_id=_chain_id,
|
||||
from_ts=from_ts,
|
||||
to_ts=to_ts,
|
||||
)
|
||||
)
|
||||
|
||||
foo(
|
||||
lambda from_ts, # but still wrap the body if it gets too long
|
||||
to_ts,
|
||||
_chain_id=chain_id: db_evmtx.count_transactions_in_rangeeeeeeeeeeeeeeeeeeeeeeeeeeeee(
|
||||
chain_id=_chain_id,
|
||||
from_ts=from_ts,
|
||||
to_ts=to_ts,
|
||||
)
|
||||
)
|
||||
|
||||
transform = lambda left, right: ibis.timestamp("2017-04-01").cast(dt.date).between(left, right).between(left, right) # trailing comment
|
||||
|
||||
(
|
||||
lambda: # comment
|
||||
1
|
||||
)
|
||||
|
||||
(
|
||||
lambda # comment
|
||||
:
|
||||
1
|
||||
)
|
||||
|
||||
(
|
||||
lambda:
|
||||
# comment
|
||||
1
|
||||
)
|
||||
|
||||
(
|
||||
lambda: # comment 1
|
||||
# comment 2
|
||||
1
|
||||
)
|
||||
|
||||
(
|
||||
lambda # comment 1
|
||||
# comment 2
|
||||
: # comment 3
|
||||
# comment 4
|
||||
1
|
||||
)
|
||||
|
||||
(
|
||||
lambda
|
||||
* # comment 2
|
||||
x:
|
||||
x
|
||||
)
|
||||
|
||||
(
|
||||
lambda # comment 1
|
||||
* # comment 2
|
||||
x:
|
||||
x
|
||||
)
|
||||
|
||||
(
|
||||
lambda # comment 1
|
||||
y,
|
||||
* # comment 2
|
||||
x:
|
||||
x
|
||||
)
|
||||
|
||||
(
|
||||
lambda
|
||||
# comment
|
||||
*x,
|
||||
**y: x
|
||||
)
|
||||
|
||||
(
|
||||
lambda
|
||||
* # comment 2
|
||||
x,
|
||||
**y:
|
||||
x
|
||||
)
|
||||
|
||||
(
|
||||
lambda
|
||||
** # comment 1
|
||||
x:
|
||||
x
|
||||
)
|
||||
|
||||
(
|
||||
lambda: # dangling-end-of-line
|
||||
# dangling-own-line
|
||||
( # leading-body-end-of-line
|
||||
x
|
||||
)
|
||||
)
|
||||
|
||||
(
|
||||
lambda: # dangling-end-of-line
|
||||
( # leading-body-end-of-line
|
||||
x
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_formatter::{Argument, Arguments, write};
|
||||
use ruff_formatter::{Argument, Arguments, format_args, write};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::context::{NodeLevel, WithNodeLevel};
|
||||
@@ -33,20 +33,27 @@ impl<'ast> Format<PyFormatContext<'ast>> for ParenthesizeIfExpands<'_, 'ast> {
|
||||
{
|
||||
let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f);
|
||||
|
||||
write!(
|
||||
f,
|
||||
[group(&format_with(|f| {
|
||||
if_group_breaks(&token("(")).fmt(f)?;
|
||||
|
||||
if self.indent {
|
||||
soft_block_indent(&Arguments::from(&self.inner)).fmt(f)?;
|
||||
} else {
|
||||
Arguments::from(&self.inner).fmt(f)?;
|
||||
}
|
||||
|
||||
if_group_breaks(&token(")")).fmt(f)
|
||||
}))]
|
||||
)
|
||||
if self.indent {
|
||||
let parens_id = f.group_id("indented_parenthesize_if_expands");
|
||||
group(&format_args![
|
||||
if_group_breaks(&token("(")),
|
||||
indent_if_group_breaks(
|
||||
&format_args![soft_line_break(), &Arguments::from(&self.inner)],
|
||||
parens_id
|
||||
),
|
||||
soft_line_break(),
|
||||
if_group_breaks(&token(")"))
|
||||
])
|
||||
.with_id(Some(parens_id))
|
||||
.fmt(&mut f)
|
||||
} else {
|
||||
group(&format_args![
|
||||
if_group_breaks(&token("(")),
|
||||
Arguments::from(&self.inner),
|
||||
if_group_breaks(&token(")")),
|
||||
])
|
||||
.fmt(&mut f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ impl Debug for DebugComment<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pretty-printed debug representation of [`Comments`].
|
||||
/// Pretty-printed debug representation of [`Comments`](super::Comments).
|
||||
pub(crate) struct DebugComments<'a> {
|
||||
comments: &'a CommentsMap<'a>,
|
||||
source_code: SourceCode<'a>,
|
||||
|
||||
@@ -504,7 +504,7 @@ impl InOrderEntry {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct OutOfOrderEntry {
|
||||
/// Index into the [`MultiMap::out_of_order`] vector at which offset the leading vec is stored.
|
||||
/// Index into the [`MultiMap::out_of_order_parts`] vector at which offset the leading vec is stored.
|
||||
leading_index: usize,
|
||||
_count: Count<OutOfOrderEntry>,
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ use ruff_python_ast::AnyNodeRef;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
/// Used as key into the [`MultiMap`] storing the comments per node by [`Comments`].
|
||||
/// Used as key into the [`MultiMap`](super::MultiMap) storing the comments per node by
|
||||
/// [`Comments`](super::Comments).
|
||||
///
|
||||
/// Implements equality and hashing based on the address of the [`AnyNodeRef`] to get fast and cheap
|
||||
/// hashing/equality comparison.
|
||||
|
||||
@@ -871,7 +871,20 @@ fn handle_parameter_comment<'a>(
|
||||
CommentPlacement::Default(comment)
|
||||
}
|
||||
} else if comment.start() < parameter.name.start() {
|
||||
CommentPlacement::leading(parameter, comment)
|
||||
// For lambdas, where the parameters cannot be parenthesized and the first parameter thus
|
||||
// starts at the same position as the parent parameters, mark a comment before the first
|
||||
// parameter as leading on the parameters rather than the individual parameter to prevent
|
||||
// the whole parameter list from breaking.
|
||||
//
|
||||
// Note that this check is not needed above because lambda parameters cannot have
|
||||
// annotations.
|
||||
if let Some(AnyNodeRef::Parameters(parameters)) = comment.enclosing_parent()
|
||||
&& parameters.start() == parameter.start()
|
||||
{
|
||||
CommentPlacement::leading(parameters, comment)
|
||||
} else {
|
||||
CommentPlacement::leading(parameter, comment)
|
||||
}
|
||||
} else {
|
||||
CommentPlacement::Default(comment)
|
||||
}
|
||||
@@ -1816,7 +1829,7 @@ fn handle_lambda_comment<'a>(
|
||||
source: &str,
|
||||
) -> CommentPlacement<'a> {
|
||||
if let Some(parameters) = lambda.parameters.as_deref() {
|
||||
// Comments between the `lambda` and the parameters are dangling on the lambda:
|
||||
// End-of-line comments between the `lambda` and the parameters are dangling on the lambda:
|
||||
// ```python
|
||||
// (
|
||||
// lambda # comment
|
||||
@@ -1824,8 +1837,22 @@ fn handle_lambda_comment<'a>(
|
||||
// y
|
||||
// )
|
||||
// ```
|
||||
//
|
||||
// But own-line comments are leading on the first parameter, if it exists:
|
||||
// ```python
|
||||
// (
|
||||
// lambda
|
||||
// # comment
|
||||
// x:
|
||||
// y
|
||||
// )
|
||||
// ```
|
||||
if comment.start() < parameters.start() {
|
||||
return CommentPlacement::dangling(comment.enclosing_node(), comment);
|
||||
return if comment.line_position().is_own_line() {
|
||||
CommentPlacement::leading(parameters, comment)
|
||||
} else {
|
||||
CommentPlacement::dangling(comment.enclosing_node(), comment)
|
||||
};
|
||||
}
|
||||
|
||||
// Comments between the parameters and the body are dangling on the lambda:
|
||||
@@ -1947,8 +1974,8 @@ fn handle_unary_op_comment<'a>(
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// The comment will be attached to the [`Arguments`] node as a dangling comment, to ensure
|
||||
/// that it remains on the same line as open parenthesis.
|
||||
/// The comment will be attached to the [`Arguments`](ast::Arguments) node as a dangling comment, to
|
||||
/// ensure that it remains on the same line as open parenthesis.
|
||||
///
|
||||
/// Similarly, given:
|
||||
/// ```python
|
||||
@@ -1957,8 +1984,8 @@ fn handle_unary_op_comment<'a>(
|
||||
/// ] = ...
|
||||
/// ```
|
||||
///
|
||||
/// The comment will be attached to the [`TypeParams`] node as a dangling comment, to ensure
|
||||
/// that it remains on the same line as open bracket.
|
||||
/// The comment will be attached to the [`TypeParams`](ast::TypeParams) node as a dangling comment,
|
||||
/// to ensure that it remains on the same line as open bracket.
|
||||
fn handle_bracketed_end_of_line_comment<'a>(
|
||||
comment: DecoratedComment<'a>,
|
||||
source: &str,
|
||||
|
||||
@@ -174,7 +174,8 @@ impl<'ast> SourceOrderVisitor<'ast> for CommentsVisitor<'ast, '_> {
|
||||
|
||||
/// A comment decorated with additional information about its surrounding context in the source document.
|
||||
///
|
||||
/// Used by [`CommentStyle::place_comment`] to determine if this should become a [leading](self#leading-comments), [dangling](self#dangling-comments), or [trailing](self#trailing-comments) comment.
|
||||
/// Used by [`place_comment`] to determine if this should become a [leading](self#leading-comments),
|
||||
/// [dangling](self#dangling-comments), or [trailing](self#trailing-comments) comment.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct DecoratedComment<'a> {
|
||||
enclosing: AnyNodeRef<'a>,
|
||||
@@ -465,7 +466,7 @@ pub(super) enum CommentPlacement<'a> {
|
||||
///
|
||||
/// [`preceding_node`]: DecoratedComment::preceding_node
|
||||
/// [`following_node`]: DecoratedComment::following_node
|
||||
/// [`enclosing_node`]: DecoratedComment::enclosing_node_id
|
||||
/// [`enclosing_node`]: DecoratedComment::enclosing_node
|
||||
/// [trailing comment]: self#trailing-comments
|
||||
/// [leading comment]: self#leading-comments
|
||||
/// [dangling comment]: self#dangling-comments
|
||||
|
||||
@@ -166,7 +166,7 @@ impl InterpolatedStringState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the interpolated string state is [`NestedInterpolatedElement`].
|
||||
/// Returns `true` if the interpolated string state is [`Self::NestedInterpolatedElement`].
|
||||
pub(crate) fn is_nested(self) -> bool {
|
||||
matches!(self, Self::NestedInterpolatedElement(..))
|
||||
}
|
||||
|
||||
@@ -1095,9 +1095,9 @@ impl OperandIndex {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the index of the operand's right operator. The method always returns an index
|
||||
/// even if the operand has no right operator. Use [`BinaryCallChain::get_operator`] to test if
|
||||
/// the operand has a right operator.
|
||||
/// Returns the index of the operand's right operator. The method always returns an index even
|
||||
/// if the operand has no right operator. Use [`FlatBinaryExpressionSlice::get_operator`] to
|
||||
/// test if the operand has a right operator.
|
||||
fn right_operator(self) -> OperatorIndex {
|
||||
OperatorIndex::new(self.0 + 1)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::ExprLambda;
|
||||
use ruff_formatter::{FormatRuleWithOptions, RemoveSoftLinesBuffer, format_args, write};
|
||||
use ruff_python_ast::{AnyNodeRef, Expr, ExprLambda};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::comments::dangling_comments;
|
||||
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
|
||||
use crate::builders::parenthesize_if_expands;
|
||||
use crate::comments::{SourceComment, dangling_comments, leading_comments, trailing_comments};
|
||||
use crate::expression::parentheses::{
|
||||
NeedsParentheses, OptionalParentheses, Parentheses, is_expression_parenthesized,
|
||||
};
|
||||
use crate::expression::{CallChainLayout, has_own_parentheses};
|
||||
use crate::other::parameters::ParametersParentheses;
|
||||
use crate::prelude::*;
|
||||
use crate::preview::is_parenthesize_lambda_bodies_enabled;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatExprLambda;
|
||||
pub struct FormatExprLambda {
|
||||
layout: ExprLambdaLayout,
|
||||
}
|
||||
|
||||
impl FormatNodeRule<ExprLambda> for FormatExprLambda {
|
||||
fn fmt_fields(&self, item: &ExprLambda, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
@@ -20,49 +26,171 @@ impl FormatNodeRule<ExprLambda> for FormatExprLambda {
|
||||
body,
|
||||
} = item;
|
||||
|
||||
let body = &**body;
|
||||
let parameters = parameters.as_deref();
|
||||
|
||||
let comments = f.context().comments().clone();
|
||||
let dangling = comments.dangling(item);
|
||||
let preview = is_parenthesize_lambda_bodies_enabled(f.context());
|
||||
|
||||
write!(f, [token("lambda")])?;
|
||||
|
||||
if let Some(parameters) = parameters {
|
||||
// In this context, a dangling comment can either be a comment between the `lambda` the
|
||||
// Format any dangling comments before the parameters, but save any dangling comments after
|
||||
// the parameters/after the header to be formatted with the body below.
|
||||
let dangling_header_comments = if let Some(parameters) = parameters {
|
||||
// In this context, a dangling comment can either be a comment between the `lambda` and the
|
||||
// parameters, or a comment between the parameters and the body.
|
||||
let (dangling_before_parameters, dangling_after_parameters) = dangling
|
||||
.split_at(dangling.partition_point(|comment| comment.end() < parameters.start()));
|
||||
|
||||
if dangling_before_parameters.is_empty() {
|
||||
write!(f, [space()])?;
|
||||
// If the parameters have a leading comment, insert a hard line break. This
|
||||
// comment is associated as a leading comment on the parameters:
|
||||
//
|
||||
// ```py
|
||||
// (
|
||||
// lambda
|
||||
// * # comment
|
||||
// x:
|
||||
// x
|
||||
// )
|
||||
// ```
|
||||
//
|
||||
// so a hard line break is needed to avoid formatting it like:
|
||||
//
|
||||
// ```py
|
||||
// (
|
||||
// lambda # comment
|
||||
// *x: x
|
||||
// )
|
||||
// ```
|
||||
//
|
||||
// which is unstable because it's missing the second space before the comment.
|
||||
//
|
||||
// Inserting the line break causes it to format like:
|
||||
//
|
||||
// ```py
|
||||
// (
|
||||
// lambda
|
||||
// # comment
|
||||
// *x :x
|
||||
// )
|
||||
// ```
|
||||
//
|
||||
// which is also consistent with the formatting in the presence of an actual
|
||||
// dangling comment on the lambda:
|
||||
//
|
||||
// ```py
|
||||
// (
|
||||
// lambda # comment 1
|
||||
// * # comment 2
|
||||
// x:
|
||||
// x
|
||||
// )
|
||||
// ```
|
||||
//
|
||||
// formats to:
|
||||
//
|
||||
// ```py
|
||||
// (
|
||||
// lambda # comment 1
|
||||
// # comment 2
|
||||
// *x: x
|
||||
// )
|
||||
// ```
|
||||
if comments.has_leading(parameters) {
|
||||
hard_line_break().fmt(f)?;
|
||||
} else {
|
||||
write!(f, [space()])?;
|
||||
}
|
||||
} else {
|
||||
write!(f, [dangling_comments(dangling_before_parameters)])?;
|
||||
}
|
||||
|
||||
write!(
|
||||
f,
|
||||
[parameters
|
||||
.format()
|
||||
.with_options(ParametersParentheses::Never)]
|
||||
)?;
|
||||
|
||||
write!(f, [token(":")])?;
|
||||
|
||||
if dangling_after_parameters.is_empty() {
|
||||
write!(f, [space()])?;
|
||||
// Try to keep the parameters on a single line, unless there are intervening comments.
|
||||
if preview && !comments.contains_comments(parameters.into()) {
|
||||
let mut buffer = RemoveSoftLinesBuffer::new(f);
|
||||
write!(
|
||||
buffer,
|
||||
[parameters
|
||||
.format()
|
||||
.with_options(ParametersParentheses::Never)]
|
||||
)?;
|
||||
} else {
|
||||
write!(f, [dangling_comments(dangling_after_parameters)])?;
|
||||
write!(
|
||||
f,
|
||||
[parameters
|
||||
.format()
|
||||
.with_options(ParametersParentheses::Never)]
|
||||
)?;
|
||||
}
|
||||
|
||||
dangling_after_parameters
|
||||
} else {
|
||||
write!(f, [token(":")])?;
|
||||
dangling
|
||||
};
|
||||
|
||||
// In this context, a dangling comment is a comment between the `lambda` and the body.
|
||||
if dangling.is_empty() {
|
||||
write!(f, [space()])?;
|
||||
} else {
|
||||
write!(f, [dangling_comments(dangling)])?;
|
||||
}
|
||||
write!(f, [token(":")])?;
|
||||
|
||||
if dangling_header_comments.is_empty() {
|
||||
write!(f, [space()])?;
|
||||
} else if !preview {
|
||||
write!(f, [dangling_comments(dangling_header_comments)])?;
|
||||
}
|
||||
|
||||
write!(f, [body.format()])
|
||||
if !preview {
|
||||
return body.format().fmt(f);
|
||||
}
|
||||
|
||||
let fmt_body = FormatBody {
|
||||
body,
|
||||
dangling_header_comments,
|
||||
};
|
||||
|
||||
match self.layout {
|
||||
ExprLambdaLayout::Assignment => fits_expanded(&fmt_body).fmt(f),
|
||||
ExprLambdaLayout::Default => fmt_body.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub enum ExprLambdaLayout {
|
||||
#[default]
|
||||
Default,
|
||||
|
||||
/// The [`ExprLambda`] is the direct child of an assignment expression, so it needs to use
|
||||
/// `fits_expanded` to prefer parenthesizing its own body before the assignment tries to
|
||||
/// parenthesize the whole lambda. For example, we want this formatting:
|
||||
///
|
||||
/// ```py
|
||||
/// long_assignment_target = lambda x, y, z: (
|
||||
/// x + y + z
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// instead of either of these:
|
||||
///
|
||||
/// ```py
|
||||
/// long_assignment_target = (
|
||||
/// lambda x, y, z: (
|
||||
/// x + y + z
|
||||
/// )
|
||||
/// )
|
||||
///
|
||||
/// long_assignment_target = (
|
||||
/// lambda x, y, z: x + y + z
|
||||
/// )
|
||||
/// ```
|
||||
Assignment,
|
||||
}
|
||||
|
||||
impl FormatRuleWithOptions<ExprLambda, PyFormatContext<'_>> for FormatExprLambda {
|
||||
type Options = ExprLambdaLayout;
|
||||
|
||||
fn with_options(mut self, options: Self::Options) -> Self {
|
||||
self.layout = options;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,3 +207,266 @@ impl NeedsParentheses for ExprLambda {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FormatBody<'a> {
|
||||
body: &'a Expr,
|
||||
|
||||
/// Dangling comments attached to the lambda header that should be formatted with the body.
|
||||
///
|
||||
/// These can include both own-line and end-of-line comments. For lambdas with parameters, this
|
||||
/// means comments after the parameters:
|
||||
///
|
||||
/// ```py
|
||||
/// (
|
||||
/// lambda x, y # 1
|
||||
/// # 2
|
||||
/// : # 3
|
||||
/// # 4
|
||||
/// x + y
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// Or all dangling comments for lambdas without parameters:
|
||||
///
|
||||
/// ```py
|
||||
/// (
|
||||
/// lambda # 1
|
||||
/// # 2
|
||||
/// : # 3
|
||||
/// # 4
|
||||
/// 1
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// In most cases these should formatted within the parenthesized body, as in:
|
||||
///
|
||||
/// ```py
|
||||
/// (
|
||||
/// lambda: ( # 1
|
||||
/// # 2
|
||||
/// # 3
|
||||
/// # 4
|
||||
/// 1
|
||||
/// )
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// or without `# 2`:
|
||||
///
|
||||
/// ```py
|
||||
/// (
|
||||
/// lambda: ( # 1 # 3
|
||||
/// # 4
|
||||
/// 1
|
||||
/// )
|
||||
/// )
|
||||
/// ```
|
||||
dangling_header_comments: &'a [SourceComment],
|
||||
}
|
||||
|
||||
impl Format<PyFormatContext<'_>> for FormatBody<'_> {
|
||||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let FormatBody {
|
||||
dangling_header_comments,
|
||||
body,
|
||||
} = self;
|
||||
|
||||
let body = *body;
|
||||
let comments = f.context().comments().clone();
|
||||
let body_comments = comments.leading_dangling_trailing(body);
|
||||
|
||||
if !dangling_header_comments.is_empty() {
|
||||
// Split the dangling header comments into trailing comments formatted with the lambda
|
||||
// header (1) and leading comments formatted with the body (2, 3, 4).
|
||||
//
|
||||
// ```python
|
||||
// (
|
||||
// lambda # 1
|
||||
// # 2
|
||||
// : # 3
|
||||
// # 4
|
||||
// y
|
||||
// )
|
||||
// ```
|
||||
//
|
||||
// Note that these are split based on their line position rather than using
|
||||
// `partition_point` based on a range, for example.
|
||||
let (trailing_header_comments, leading_body_comments) = dangling_header_comments
|
||||
.split_at(
|
||||
dangling_header_comments
|
||||
.iter()
|
||||
.position(|comment| comment.line_position().is_own_line())
|
||||
.unwrap_or(dangling_header_comments.len()),
|
||||
);
|
||||
|
||||
// If the body is parenthesized and has its own leading comments, preserve the
|
||||
// separation between the dangling lambda comments and the body comments. For
|
||||
// example, preserve this comment positioning:
|
||||
//
|
||||
// ```python
|
||||
// (
|
||||
// lambda: # 1
|
||||
// # 2
|
||||
// ( # 3
|
||||
// x
|
||||
// )
|
||||
// )
|
||||
// ```
|
||||
//
|
||||
// 1 and 2 are dangling on the lambda and emitted first, followed by a hard line
|
||||
// break and the parenthesized body with its leading comments.
|
||||
//
|
||||
// However, when removing 2, 1 and 3 can instead be formatted on the same line:
|
||||
//
|
||||
// ```python
|
||||
// (
|
||||
// lambda: ( # 1 # 3
|
||||
// x
|
||||
// )
|
||||
// )
|
||||
// ```
|
||||
let comments = f.context().comments();
|
||||
if is_expression_parenthesized(body.into(), comments.ranges(), f.context().source())
|
||||
&& comments.has_leading(body)
|
||||
{
|
||||
trailing_comments(dangling_header_comments).fmt(f)?;
|
||||
|
||||
// Note that `leading_body_comments` have already been formatted as part of
|
||||
// `dangling_header_comments` above, but their presence still determines the spacing
|
||||
// here.
|
||||
if leading_body_comments.is_empty() {
|
||||
space().fmt(f)?;
|
||||
} else {
|
||||
hard_line_break().fmt(f)?;
|
||||
}
|
||||
|
||||
body.format().with_options(Parentheses::Always).fmt(f)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
space(),
|
||||
token("("),
|
||||
trailing_comments(trailing_header_comments),
|
||||
block_indent(&format_args!(
|
||||
leading_comments(leading_body_comments),
|
||||
body.format().with_options(Parentheses::Never)
|
||||
)),
|
||||
token(")")
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
// If the body has comments, we always want to preserve the parentheses. This also
|
||||
// ensures that we correctly handle parenthesized comments, and don't need to worry
|
||||
// about them in the implementation below.
|
||||
else if body_comments.has_leading() || body_comments.has_trailing_own_line() {
|
||||
body.format().with_options(Parentheses::Always).fmt(f)
|
||||
}
|
||||
// Calls and subscripts require special formatting because they have their own
|
||||
// parentheses, but they can also have an arbitrary amount of text before the
|
||||
// opening parenthesis. We want to avoid cases where we keep a long callable on the
|
||||
// same line as the lambda parameters. For example, `db_evmtx...` in:
|
||||
//
|
||||
// ```py
|
||||
// transaction_count = self._query_txs_for_range(
|
||||
// get_count_fn=lambda from_ts, to_ts, _chain_id=chain_id: db_evmtx.count_transactions_in_range(
|
||||
// chain_id=_chain_id,
|
||||
// from_ts=from_ts,
|
||||
// to_ts=to_ts,
|
||||
// ),
|
||||
// )
|
||||
// ```
|
||||
//
|
||||
// should cause the whole lambda body to be parenthesized instead:
|
||||
//
|
||||
// ```py
|
||||
// transaction_count = self._query_txs_for_range(
|
||||
// get_count_fn=lambda from_ts, to_ts, _chain_id=chain_id: (
|
||||
// db_evmtx.count_transactions_in_range(
|
||||
// chain_id=_chain_id,
|
||||
// from_ts=from_ts,
|
||||
// to_ts=to_ts,
|
||||
// )
|
||||
// ),
|
||||
// )
|
||||
// ```
|
||||
else if matches!(body, Expr::Call(_) | Expr::Subscript(_)) {
|
||||
let unparenthesized = body.format().with_options(Parentheses::Never);
|
||||
if CallChainLayout::from_expression(
|
||||
body.into(),
|
||||
comments.ranges(),
|
||||
f.context().source(),
|
||||
) == CallChainLayout::Fluent
|
||||
{
|
||||
parenthesize_if_expands(&unparenthesized).fmt(f)
|
||||
} else {
|
||||
let unparenthesized = unparenthesized.memoized();
|
||||
if unparenthesized.inspect(f)?.will_break() {
|
||||
expand_parent().fmt(f)?;
|
||||
}
|
||||
|
||||
best_fitting![
|
||||
// body all flat
|
||||
unparenthesized,
|
||||
// body expanded
|
||||
group(&unparenthesized).should_expand(true),
|
||||
// parenthesized
|
||||
format_args![token("("), block_indent(&unparenthesized), token(")")]
|
||||
]
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
// For other cases with their own parentheses, such as lists, sets, dicts, tuples,
|
||||
// etc., we can just format the body directly. Their own formatting results in the
|
||||
// lambda being formatted well too. For example:
|
||||
//
|
||||
// ```py
|
||||
// lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: [xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz]
|
||||
// ```
|
||||
//
|
||||
// gets formatted as:
|
||||
//
|
||||
// ```py
|
||||
// lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: [
|
||||
// xxxxxxxxxxxxxxxxxxxx,
|
||||
// yyyyyyyyyyyyyyyyyyyy,
|
||||
// zzzzzzzzzzzzzzzzzzzz
|
||||
// ]
|
||||
// ```
|
||||
else if has_own_parentheses(body, f.context()).is_some() {
|
||||
body.format().fmt(f)
|
||||
}
|
||||
// Finally, for expressions without their own parentheses, use
|
||||
// `parenthesize_if_expands` to add parentheses around the body, only if it expands
|
||||
// across multiple lines. The `Parentheses::Never` here also removes unnecessary
|
||||
// parentheses around lambda bodies that fit on one line. For example:
|
||||
//
|
||||
// ```py
|
||||
// lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: xxxxxxxxxxxxxxxxxxxx + yyyyyyyyyyyyyyyyyyyy + zzzzzzzzzzzzzzzzzzzz
|
||||
// ```
|
||||
//
|
||||
// is formatted as:
|
||||
//
|
||||
// ```py
|
||||
// lambda xxxxxxxxxxxxxxxxxxxx, yyyyyyyyyyyyyyyyyyyy, zzzzzzzzzzzzzzzzzzzz: (
|
||||
// xxxxxxxxxxxxxxxxxxxx + yyyyyyyyyyyyyyyyyyyy + zzzzzzzzzzzzzzzzzzzz
|
||||
// )
|
||||
// ```
|
||||
//
|
||||
// while
|
||||
//
|
||||
// ```py
|
||||
// lambda xxxxxxxxxxxxxxxxxxxx: (xxxxxxxxxxxxxxxxxxxx + 1)
|
||||
// ```
|
||||
//
|
||||
// is formatted as:
|
||||
//
|
||||
// ```py
|
||||
// lambda xxxxxxxxxxxxxxxxxxxx: xxxxxxxxxxxxxxxxxxxx + 1
|
||||
// ```
|
||||
else {
|
||||
parenthesize_if_expands(&body.format().with_options(Parentheses::Never)).fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,18 +56,20 @@ pub(crate) enum Parenthesize {
|
||||
/// Adding parentheses is desired to prevent the comments from wandering.
|
||||
IfRequired,
|
||||
|
||||
/// Same as [`Self::IfBreaks`] except that it uses [`parenthesize_if_expands`] for expressions
|
||||
/// with the layout [`NeedsParentheses::BestFit`] which is used by non-splittable
|
||||
/// expressions like literals, name, and strings.
|
||||
/// Same as [`Self::IfBreaks`] except that it uses
|
||||
/// [`parenthesize_if_expands`](crate::builders::parenthesize_if_expands) for expressions with
|
||||
/// the layout [`OptionalParentheses::BestFit`] which is used by non-splittable expressions like
|
||||
/// literals, name, and strings.
|
||||
///
|
||||
/// Use this layout over `IfBreaks` when there's a sequence of `maybe_parenthesize_expression`
|
||||
/// in a single logical-line and you want to break from right-to-left. Use `IfBreaks` for the
|
||||
/// first expression and `IfBreaksParenthesized` for the rest.
|
||||
IfBreaksParenthesized,
|
||||
|
||||
/// Same as [`Self::IfBreaksParenthesized`] but uses [`parenthesize_if_expands`] for nested
|
||||
/// [`maybe_parenthesized_expression`] calls unlike other layouts that always omit parentheses
|
||||
/// when outer parentheses are present.
|
||||
/// Same as [`Self::IfBreaksParenthesized`] but uses
|
||||
/// [`parenthesize_if_expands`](crate::builders::parenthesize_if_expands) for nested
|
||||
/// [`maybe_parenthesized_expression`](crate::expression::maybe_parenthesize_expression) calls
|
||||
/// unlike other layouts that always omit parentheses when outer parentheses are present.
|
||||
IfBreaksParenthesizedNested,
|
||||
}
|
||||
|
||||
|
||||
@@ -214,8 +214,9 @@ impl Format<PyFormatContext<'_>> for MaybeParenthesizePattern<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// This function is very similar to [`can_omit_optional_parentheses`] with the only difference that it is for patterns
|
||||
/// and not expressions.
|
||||
/// This function is very similar to
|
||||
/// [`can_omit_optional_parentheses`](crate::expression::can_omit_optional_parentheses)
|
||||
/// with the only difference that it is for patterns and not expressions.
|
||||
///
|
||||
/// The base idea of the omit optional parentheses layout is to prefer using parentheses of sub-patterns
|
||||
/// when splitting the pattern over introducing new patterns. For example, prefer splitting the sequence pattern in
|
||||
|
||||
@@ -72,8 +72,9 @@ impl FormatNodeRule<PatternArguments> for FormatPatternArguments {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the pattern (which is the only argument to a [`PatternMatchClass`]) is
|
||||
/// parenthesized. Used to avoid falsely assuming that `x` is parenthesized in cases like:
|
||||
/// Returns `true` if the pattern (which is the only argument to a
|
||||
/// [`PatternMatchClass`](ruff_python_ast::PatternMatchClass)) is parenthesized.
|
||||
/// Used to avoid falsely assuming that `x` is parenthesized in cases like:
|
||||
/// ```python
|
||||
/// case Point2D(x): ...
|
||||
/// ```
|
||||
|
||||
@@ -52,3 +52,10 @@ pub(crate) const fn is_avoid_parens_for_long_as_captures_enabled(
|
||||
) -> bool {
|
||||
context.is_preview()
|
||||
}
|
||||
|
||||
/// Returns `true` if the
|
||||
/// [`parenthesize_lambda_bodies`](https://github.com/astral-sh/ruff/pull/21385) preview style is
|
||||
/// enabled.
|
||||
pub(crate) const fn is_parenthesize_lambda_bodies_enabled(context: &PyFormatContext) -> bool {
|
||||
context.is_preview()
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user