Compare commits

..

No commits in common. "main" and "0.14.8" have entirely different histories.
main ... 0.14.8

515 changed files with 17119 additions and 41880 deletions

View File

@ -75,6 +75,14 @@
matchManagers: ["cargo"],
enabled: false,
},
{
// `mkdocs-material` requires a manual update to keep the version in sync
// with `mkdocs-material-insider`.
// See: https://squidfunk.github.io/mkdocs-material/insiders/upgrade/
matchManagers: ["pip_requirements"],
matchPackageNames: ["mkdocs-material"],
enabled: false,
},
{
groupName: "pre-commit dependencies",
matchManagers: ["pre-commit"],

View File

@ -24,8 +24,6 @@ env:
PACKAGE_NAME: ruff
PYTHON_VERSION: "3.14"
NEXTEST_PROFILE: ci
# Enable mdtests that require external dependencies
MDTEST_EXTERNAL: "1"
jobs:
determine_changes:
@ -298,7 +296,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 -p ruff_python_formatter --document-private-items
- run: cargo doc --no-deps -p ty_python_semantic -p ty -p ty_test -p ruff_db --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"
@ -781,6 +779,8 @@ jobs:
name: "mkdocs"
runs-on: ubuntu-latest
timeout-minutes: 10
env:
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
@ -788,6 +788,11 @@ jobs:
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: "Add SSH key"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
with:
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
- name: "Install Rust toolchain"
run: rustup show
- name: Install uv
@ -795,7 +800,11 @@ jobs:
with:
python-version: 3.13
activate-environment: true
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: uv pip install -r docs/requirements-insiders.txt
- name: "Install dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
run: uv pip install -r docs/requirements.txt
- name: "Update README File"
run: python scripts/transform_readme.py --target mkdocs
@ -803,8 +812,12 @@ jobs:
run: python scripts/generate_mkdocs.py
- name: "Check docs formatting"
run: python scripts/check_docs_formatted.py
- name: "Build Insiders docs"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: mkdocs build --strict -f mkdocs.insiders.yml
- name: "Build docs"
run: mkdocs build --strict -f mkdocs.yml
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
run: mkdocs build --strict -f mkdocs.public.yml
check-formatter-instability-and-black-similarity:
name: "formatter instabilities and black similarity"

View File

@ -47,7 +47,6 @@ jobs:
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
shared-key: "mypy-primer"
workspaces: "ruff"
- name: Install Rust toolchain
@ -87,7 +86,6 @@ jobs:
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
with:
workspaces: "ruff"
shared-key: "mypy-primer"
- name: Install Rust toolchain
run: rustup show
@ -107,54 +105,3 @@ 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

View File

@ -20,6 +20,8 @@ on:
jobs:
mkdocs:
runs-on: ubuntu-latest
env:
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
@ -57,12 +59,23 @@ jobs:
echo "branch_name=update-docs-$branch_display_name-$timestamp" >> "$GITHUB_ENV"
echo "timestamp=$timestamp" >> "$GITHUB_ENV"
- name: "Add SSH key"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
uses: webfactory/ssh-agent@a6f90b1f127823b31d4d4a8d96047790581349bd # v0.9.1
with:
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: pip install -r docs/requirements-insiders.txt
- name: "Install dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
run: pip install -r docs/requirements.txt
- name: "Copy README File"
@ -70,8 +83,13 @@ jobs:
python scripts/transform_readme.py --target mkdocs
python scripts/generate_mkdocs.py
- name: "Build Insiders docs"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: mkdocs build --strict -f mkdocs.insiders.yml
- name: "Build docs"
run: mkdocs build --strict -f mkdocs.yml
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
run: mkdocs build --strict -f mkdocs.public.yml
- name: "Clone docs repo"
run: git clone https://${{ secrets.ASTRAL_DOCS_PAT }}@github.com/astral-sh/docs.git astral-docs

View File

@ -60,7 +60,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
with:
persist-credentials: false
submodules: recursive
@ -123,7 +123,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
with:
persist-credentials: false
submodules: recursive
@ -174,7 +174,7 @@ jobs:
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
with:
persist-credentials: false
submodules: recursive
@ -250,7 +250,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
with:
persist-credentials: false
submodules: recursive

View File

@ -67,7 +67,7 @@ jobs:
cd ..
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@2e1816eac09c90140b1ba51d19afc5f59da460f5"
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@55df3c868f3fa9ab34cff0498dd6106722aac205"
ecosystem-analyzer \
--repository ruff \

View File

@ -52,7 +52,7 @@ jobs:
cd ..
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@2e1816eac09c90140b1ba51d19afc5f59da460f5"
uv tool install "git+https://github.com/astral-sh/ecosystem-analyzer@55df3c868f3fa9ab34cff0498dd6106722aac205"
ecosystem-analyzer \
--verbose \

View File

@ -1,47 +1,5 @@
# 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.

View File

@ -331,6 +331,13 @@ you addressed them.
## MkDocs
> [!NOTE]
>
> The documentation uses Material for MkDocs Insiders, which is closed-source software.
> This means only members of the Astral organization can preview the documentation exactly as it
> will appear in production.
> Outside contributors can still preview the documentation, but there will be some differences. Consult [the Material for MkDocs documentation](https://squidfunk.github.io/mkdocs-material/insiders/benefits/#features) for which features are exclusively available in the insiders version.
To preview any changes to the documentation locally:
1. Install the [Rust toolchain](https://www.rust-lang.org/tools/install).
@ -344,7 +351,11 @@ To preview any changes to the documentation locally:
1. Run the development server with:
```shell
uvx --with-requirements docs/requirements.txt -- mkdocs serve -f mkdocs.yml
# For contributors.
uvx --with-requirements docs/requirements.txt -- mkdocs serve -f mkdocs.public.yml
# For members of the Astral org, which has access to MkDocs Insiders via sponsorship.
uvx --with-requirements docs/requirements-insiders.txt -- mkdocs serve -f mkdocs.insiders.yml
```
The documentation should then be available locally at

97
Cargo.lock generated
View File

@ -254,21 +254,6 @@ 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"
@ -959,18 +944,6 @@ 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"
@ -1043,7 +1016,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.61.0",
"windows-sys 0.59.0",
]
[[package]]
@ -1135,7 +1108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.0",
"windows-sys 0.59.0",
]
[[package]]
@ -1165,17 +1138,6 @@ 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"
@ -1276,9 +1238,9 @@ dependencies = [
[[package]]
name = "get-size-derive2"
version = "0.7.3"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab21d7bd2c625f2064f04ce54bcb88bc57c45724cde45cba326d784e22d3f71a"
checksum = "ff47daa61505c85af126e9dd64af6a342a33dc0cccfe1be74ceadc7d352e6efd"
dependencies = [
"attribute-derive",
"quote",
@ -1287,15 +1249,14 @@ dependencies = [
[[package]]
name = "get-size2"
version = "0.7.3"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879272b0de109e2b67b39fcfe3d25fdbba96ac07e44a254f5a0b4d7ff55340cb"
checksum = "ac7bb8710e1f09672102be7ddf39f764d8440ae74a9f4e30aaa4820dcdffa4af"
dependencies = [
"compact_str",
"get-size-derive2",
"hashbrown 0.16.1",
"indexmap",
"ordermap",
"smallvec",
]
@ -1663,6 +1624,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46fdb647ebde000f43b5b53f773c30cf9b0cb4300453208713fa38b2c70935a0"
dependencies = [
"console 0.15.11",
"globset",
"once_cell",
"pest",
"pest_derive",
@ -1670,6 +1632,7 @@ dependencies = [
"ron",
"serde",
"similar",
"walkdir",
]
[[package]]
@ -1800,7 +1763,7 @@ dependencies = [
"portable-atomic",
"portable-atomic-util",
"serde_core",
"windows-sys 0.61.0",
"windows-sys 0.59.0",
]
[[package]]
@ -1955,18 +1918,6 @@ 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"
@ -2282,9 +2233,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "ordermap"
version = "1.0.0"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed637741ced8fb240855d22a2b4f208dab7a06bcce73380162e5253000c16758"
checksum = "b100f7dd605611822d30e182214d3c02fdefce2d801d23993f6b6ba6ca1392af"
dependencies = [
"indexmap",
"serde",
@ -2908,7 +2859,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.14.9"
version = "0.14.8"
dependencies = [
"anyhow",
"argfile",
@ -3166,7 +3117,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.14.9"
version = "0.14.8"
dependencies = [
"aho-corasick",
"anyhow",
@ -3326,7 +3277,6 @@ dependencies = [
"anyhow",
"clap",
"countme",
"datatest-stable",
"insta",
"itertools 0.14.0",
"memchr",
@ -3396,10 +3346,8 @@ dependencies = [
"bitflags 2.10.0",
"bstr",
"compact_str",
"datatest-stable",
"get-size2",
"insta",
"itertools 0.14.0",
"memchr",
"ruff_annotate_snippets",
"ruff_python_ast",
@ -3525,7 +3473,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.14.9"
version = "0.14.8"
dependencies = [
"console_error_panic_hook",
"console_log",
@ -3623,7 +3571,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.0",
"windows-sys 0.59.0",
]
[[package]]
@ -3641,7 +3589,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "salsa"
version = "0.24.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
source = "git+https://github.com/salsa-rs/salsa.git?rev=59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0#59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0"
dependencies = [
"boxcar",
"compact_str",
@ -3652,7 +3600,6 @@ dependencies = [
"indexmap",
"intrusive-collections",
"inventory",
"ordermap",
"parking_lot",
"portable-atomic",
"rustc-hash",
@ -3666,12 +3613,12 @@ dependencies = [
[[package]]
name = "salsa-macro-rules"
version = "0.24.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
source = "git+https://github.com/salsa-rs/salsa.git?rev=59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0#59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0"
[[package]]
name = "salsa-macros"
version = "0.24.0"
source = "git+https://github.com/salsa-rs/salsa.git?rev=55e5e7d32fa3fc189276f35bb04c9438f9aedbd1#55e5e7d32fa3fc189276f35bb04c9438f9aedbd1"
source = "git+https://github.com/salsa-rs/salsa.git?rev=59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0#59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0"
dependencies = [
"proc-macro2",
"quote",
@ -4025,7 +3972,7 @@ dependencies = [
"getrandom 0.3.4",
"once_cell",
"rustix",
"windows-sys 0.61.0",
"windows-sys 0.59.0",
]
[[package]]
@ -4361,7 +4308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fe242ee9e646acec9ab73a5c540e8543ed1b107f0ce42be831e0775d423c396"
dependencies = [
"ignore",
"libtest-mimic 0.7.3",
"libtest-mimic",
"snapbox",
]
@ -4390,7 +4337,6 @@ dependencies = [
"ruff_python_trivia",
"salsa",
"tempfile",
"tikv-jemallocator",
"toml",
"tracing",
"tracing-flame",
@ -4611,7 +4557,6 @@ dependencies = [
"anyhow",
"camino",
"colored 3.0.0",
"dunce",
"insta",
"memchr",
"path-slash",
@ -5080,7 +5025,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.0",
"windows-sys 0.59.0",
]
[[package]]

View File

@ -5,7 +5,7 @@ resolver = "2"
[workspace.package]
# Please update rustfmt.toml when bumping the Rust edition
edition = "2024"
rust-version = "1.90"
rust-version = "1.89"
homepage = "https://docs.astral.sh/ruff"
documentation = "https://docs.astral.sh/ruff"
repository = "https://github.com/astral-sh/ruff"
@ -81,7 +81,6 @@ 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" }
@ -89,7 +88,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.3", features = [
get-size2 = { version = "0.7.0", features = [
"derive",
"smallvec",
"hashbrown",
@ -130,7 +129,7 @@ memchr = { version = "2.7.1" }
mimalloc = { version = "0.1.39" }
natord = { version = "1.0.9" }
notify = { version = "8.0.0" }
ordermap = { version = "1.0.0" }
ordermap = { version = "0.5.0" }
path-absolutize = { version = "3.1.1" }
path-slash = { version = "0.2.1" }
pathdiff = { version = "0.2.1" }
@ -147,7 +146,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 = "55e5e7d32fa3fc189276f35bb04c9438f9aedbd1", default-features = false, features = [
salsa = { git = "https://github.com/salsa-rs/salsa.git", rev = "59aa1075e837f5deb0d6ffb24b68fedc0f4bc5e0", default-features = false, features = [
"compact_str",
"macros",
"salsa_unstable",
@ -273,12 +272,6 @@ large_stack_arrays = "allow"
lto = "fat"
codegen-units = 16
# Profile to build a minimally sized binary for ruff/ty
[profile.minimal-size]
inherits = "release"
opt-level = "z"
codegen-units = 1
# Some crates don't change as much but benefit more from
# more expensive optimization passes, so we selectively
# decrease codegen-units in some cases.

View File

@ -57,11 +57,8 @@ Ruff is extremely actively developed and used in major open-source projects like
...and [many more](#whos-using-ruff).
Ruff is backed by [Astral](https://astral.sh), the creators of
[uv](https://github.com/astral-sh/uv) and [ty](https://github.com/astral-sh/ty).
Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff), or the
original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
Ruff is backed by [Astral](https://astral.sh). Read the [launch post](https://astral.sh/blog/announcing-astral-the-company-behind-ruff),
or the original [project announcement](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
## Testimonials
@ -150,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.9/install.sh | sh
powershell -c "irm https://astral.sh/ruff/0.14.9/install.ps1 | iex"
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"
```
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
@ -184,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.9
rev: v0.14.8
hooks:
# Run the linter.
- id: ruff-check

View File

@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.14.9"
version = "0.14.8"
publish = true
authors = { workspace = true }
edition = { workspace = true }

View File

@ -10,7 +10,7 @@ use anyhow::bail;
use clap::builder::Styles;
use clap::builder::styling::{AnsiColor, Effects};
use clap::builder::{TypedValueParser, ValueParserFactory};
use clap::{Parser, Subcommand};
use clap::{Parser, Subcommand, command};
use colored::Colorize;
use itertools::Itertools;
use path_absolutize::path_dedot;

View File

@ -9,7 +9,7 @@ use std::sync::mpsc::channel;
use anyhow::Result;
use clap::CommandFactory;
use colored::Colorize;
use log::error;
use log::{error, warn};
use notify::{RecursiveMode, Watcher, recommended_watcher};
use args::{GlobalConfigArgs, ServerCommand};

View File

@ -1440,78 +1440,6 @@ def function():
Ok(())
}
#[test]
fn ignore_noqa() -> Result<()> {
let fixture = CliTest::new()?;
fixture.write_file(
"ruff.toml",
r#"
[lint]
select = ["F401"]
"#,
)?;
fixture.write_file(
"noqa.py",
r#"
import os # noqa: F401
# ruff: disable[F401]
import sys
"#,
)?;
// without --ignore-noqa
assert_cmd_snapshot!(fixture
.check_command()
.args(["--config", "ruff.toml"])
.arg("noqa.py"),
@r"
success: false
exit_code: 1
----- stdout -----
noqa.py:5:8: F401 [*] `sys` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.
----- stderr -----
");
assert_cmd_snapshot!(fixture
.check_command()
.args(["--config", "ruff.toml"])
.arg("noqa.py")
.args(["--preview"]),
@r"
success: true
exit_code: 0
----- stdout -----
All checks passed!
----- stderr -----
");
// with --ignore-noqa --preview
assert_cmd_snapshot!(fixture
.check_command()
.args(["--config", "ruff.toml"])
.arg("noqa.py")
.args(["--ignore-noqa", "--preview"]),
@r"
success: false
exit_code: 1
----- stdout -----
noqa.py:2:8: F401 [*] `os` imported but unused
noqa.py:5:8: F401 [*] `sys` imported but unused
Found 2 errors.
[*] 2 fixable with the `--fix` option.
----- stderr -----
");
Ok(())
}
#[test]
fn add_noqa() -> Result<()> {
let fixture = CliTest::new()?;
@ -1704,100 +1632,6 @@ def unused(x): # noqa: ANN001, ARG001, D103
Ok(())
}
#[test]
fn add_noqa_existing_file_level_noqa() -> Result<()> {
let fixture = CliTest::new()?;
fixture.write_file(
"ruff.toml",
r#"
[lint]
select = ["F401"]
"#,
)?;
fixture.write_file(
"noqa.py",
r#"
# ruff: noqa F401
import os
"#,
)?;
assert_cmd_snapshot!(fixture
.check_command()
.args(["--config", "ruff.toml"])
.arg("noqa.py")
.arg("--preview")
.args(["--add-noqa"])
.arg("-")
.pass_stdin(r#"
"#), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
");
let test_code =
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
insta::assert_snapshot!(test_code, @r"
# ruff: noqa F401
import os
");
Ok(())
}
#[test]
fn add_noqa_existing_range_suppression() -> Result<()> {
let fixture = CliTest::new()?;
fixture.write_file(
"ruff.toml",
r#"
[lint]
select = ["F401"]
"#,
)?;
fixture.write_file(
"noqa.py",
r#"
# ruff: disable[F401]
import os
"#,
)?;
assert_cmd_snapshot!(fixture
.check_command()
.args(["--config", "ruff.toml"])
.arg("noqa.py")
.arg("--preview")
.args(["--add-noqa"])
.arg("-")
.pass_stdin(r#"
"#), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
");
let test_code =
fs::read_to_string(fixture.root().join("noqa.py")).expect("should read test file");
insta::assert_snapshot!(test_code, @r"
# ruff: disable[F401]
import os
");
Ok(())
}
#[test]
fn add_noqa_multiline_comment() -> Result<()> {
let fixture = CliTest::new()?;

View File

@ -194,7 +194,7 @@ static SYMPY: Benchmark = Benchmark::new(
max_dep_date: "2025-06-17",
python_version: PythonVersion::PY312,
},
13100,
13000,
);
static TANJUN: Benchmark = Benchmark::new(
@ -223,7 +223,7 @@ static STATIC_FRAME: Benchmark = Benchmark::new(
max_dep_date: "2025-08-09",
python_version: PythonVersion::PY311,
},
1100,
950,
);
#[track_caller]

View File

@ -166,8 +166,28 @@ impl Diagnostic {
/// Returns the primary message for this diagnostic.
///
/// A diagnostic always has a message, but it may be empty.
///
/// NOTE: At present, this routine will return the first primary
/// annotation's message as the primary message when the main diagnostic
/// message is empty. This is meant to facilitate an incremental migration
/// in ty over to the new diagnostic data model. (The old data model
/// didn't distinguish between messages on the entire diagnostic and
/// messages attached to a particular span.)
pub fn primary_message(&self) -> &str {
self.inner.message.as_str()
if !self.inner.message.as_str().is_empty() {
return self.inner.message.as_str();
}
// FIXME: As a special case, while we're migrating ty
// to the new diagnostic data model, we'll look for a primary
// message from the primary annotation. This is because most
// ty diagnostics are created with an empty diagnostic
// message and instead attach the message to the annotation.
// Fixing this will require touching basically every diagnostic
// in ty, so we do it this way for now to match the old
// semantics. ---AG
self.primary_annotation()
.and_then(|ann| ann.get_message())
.unwrap_or_default()
}
/// Introspects this diagnostic and returns what kind of "primary" message
@ -179,6 +199,18 @@ impl Diagnostic {
/// contains *essential* information or context for understanding the
/// diagnostic.
///
/// The reason why we don't just always return both the main diagnostic
/// message and the primary annotation message is because this was written
/// in the midst of an incremental migration of ty over to the new
/// diagnostic data model. At time of writing, diagnostics were still
/// constructed in the old model where the main diagnostic message and the
/// primary annotation message were not distinguished from each other. So
/// for now, we carefully return what kind of messages this diagnostic
/// contains. In effect, if this diagnostic has a non-empty main message
/// *and* a non-empty primary annotation message, then the diagnostic is
/// 100% using the new diagnostic data model and we can format things
/// appropriately.
///
/// The type returned implements the `std::fmt::Display` trait. In most
/// cases, just converting it to a string (or printing it) will do what
/// you want.
@ -192,10 +224,11 @@ impl Diagnostic {
.primary_annotation()
.and_then(|ann| ann.get_message())
.unwrap_or_default();
if annotation.is_empty() {
ConciseMessage::MainDiagnostic(main)
} else {
ConciseMessage::Both { main, annotation }
match (main.is_empty(), annotation.is_empty()) {
(false, true) => ConciseMessage::MainDiagnostic(main),
(true, false) => ConciseMessage::PrimaryAnnotation(annotation),
(false, false) => ConciseMessage::Both { main, annotation },
(true, true) => ConciseMessage::Empty,
}
}
@ -660,6 +693,18 @@ impl SubDiagnostic {
/// contains *essential* information or context for understanding the
/// diagnostic.
///
/// The reason why we don't just always return both the main diagnostic
/// message and the primary annotation message is because this was written
/// in the midst of an incremental migration of ty over to the new
/// diagnostic data model. At time of writing, diagnostics were still
/// constructed in the old model where the main diagnostic message and the
/// primary annotation message were not distinguished from each other. So
/// for now, we carefully return what kind of messages this diagnostic
/// contains. In effect, if this diagnostic has a non-empty main message
/// *and* a non-empty primary annotation message, then the diagnostic is
/// 100% using the new diagnostic data model and we can format things
/// appropriately.
///
/// The type returned implements the `std::fmt::Display` trait. In most
/// cases, just converting it to a string (or printing it) will do what
/// you want.
@ -669,10 +714,11 @@ impl SubDiagnostic {
.primary_annotation()
.and_then(|ann| ann.get_message())
.unwrap_or_default();
if annotation.is_empty() {
ConciseMessage::MainDiagnostic(main)
} else {
ConciseMessage::Both { main, annotation }
match (main.is_empty(), annotation.is_empty()) {
(false, true) => ConciseMessage::MainDiagnostic(main),
(true, false) => ConciseMessage::PrimaryAnnotation(annotation),
(false, false) => ConciseMessage::Both { main, annotation },
(true, true) => ConciseMessage::Empty,
}
}
}
@ -842,10 +888,6 @@ impl Annotation {
pub fn hide_snippet(&mut self, yes: bool) {
self.hide_snippet = yes;
}
pub fn is_primary(&self) -> bool {
self.is_primary
}
}
/// Tags that can be associated with an annotation.
@ -1466,10 +1508,28 @@ pub enum DiagnosticFormat {
pub enum ConciseMessage<'a> {
/// A diagnostic contains a non-empty main message and an empty
/// primary annotation message.
///
/// This strongly suggests that the diagnostic is using the
/// "new" data model.
MainDiagnostic(&'a str),
/// A diagnostic contains an empty main message and a non-empty
/// primary annotation message.
///
/// This strongly suggests that the diagnostic is using the
/// "old" data model.
PrimaryAnnotation(&'a str),
/// A diagnostic contains a non-empty main message and a non-empty
/// primary annotation message.
///
/// This strongly suggests that the diagnostic is using the
/// "new" data model.
Both { main: &'a str, annotation: &'a str },
/// A diagnostic contains an empty main message and an empty
/// primary annotation message.
///
/// This indicates that the diagnostic is probably using the old
/// model.
Empty,
/// A custom concise message has been provided.
Custom(&'a str),
}
@ -1480,9 +1540,13 @@ impl std::fmt::Display for ConciseMessage<'_> {
ConciseMessage::MainDiagnostic(main) => {
write!(f, "{main}")
}
ConciseMessage::PrimaryAnnotation(annotation) => {
write!(f, "{annotation}")
}
ConciseMessage::Both { main, annotation } => {
write!(f, "{main}: {annotation}")
}
ConciseMessage::Empty => Ok(()),
ConciseMessage::Custom(message) => {
write!(f, "{message}")
}

View File

@ -144,8 +144,8 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
output.push('\n');
if let Some(deprecated) = &field.deprecated {
output.push_str("!!! warning \"Deprecated\"\n");
output.push_str(" This option has been deprecated");
output.push_str("> [!WARN] \"Deprecated\"\n");
output.push_str("> This option has been deprecated");
if let Some(since) = deprecated.since {
write!(output, " in {since}").unwrap();
@ -166,9 +166,8 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
output.push('\n');
let _ = writeln!(output, "**Type**: `{}`", field.value_type);
output.push('\n');
output.push_str("**Example usage**:\n\n");
output.push_str("**Example usage** (`pyproject.toml`):\n\n");
output.push_str(&format_example(
"pyproject.toml",
&format_header(
field.scope,
field.example,
@ -180,11 +179,11 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parents: &[S
output.push('\n');
}
fn format_example(title: &str, header: &str, content: &str) -> String {
fn format_example(header: &str, content: &str) -> String {
if header.is_empty() {
format!("```toml title=\"{title}\"\n{content}\n```\n",)
format!("```toml\n{content}\n```\n",)
} else {
format!("```toml title=\"{title}\"\n{header}\n{content}\n```\n",)
format!("```toml\n{header}\n{content}\n```\n",)
}
}

View File

@ -39,7 +39,7 @@ impl Edit {
/// Creates an edit that replaces the content in `range` with `content`.
pub fn range_replacement(content: String, range: TextRange) -> Self {
debug_assert!(!content.is_empty(), "Prefer `Edit::deletion`");
debug_assert!(!content.is_empty(), "Prefer `Fix::deletion`");
Self {
content: Some(Box::from(content)),

View File

@ -337,7 +337,7 @@ macro_rules! best_fitting {
#[cfg(test)]
mod tests {
use crate::prelude::*;
use crate::{FormatState, SimpleFormatOptions, VecBuffer};
use crate::{FormatState, SimpleFormatOptions, VecBuffer, write};
struct TestFormat;
@ -385,8 +385,8 @@ mod tests {
#[test]
fn best_fitting_variants_print_as_lists() {
use crate::Formatted;
use crate::prelude::*;
use crate::{Formatted, format, format_args};
// The second variant below should be selected when printing at a width of 30
let formatted_best_fitting = format!(

View File

@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.14.9"
version = "0.14.8"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@ -28,11 +28,9 @@ yaml.load("{}", SafeLoader)
yaml.load("{}", yaml.SafeLoader)
yaml.load("{}", CSafeLoader)
yaml.load("{}", yaml.CSafeLoader)
yaml.load("{}", yaml.cyaml.CSafeLoader)
yaml.load("{}", NewSafeLoader)
yaml.load("{}", Loader=SafeLoader)
yaml.load("{}", Loader=yaml.SafeLoader)
yaml.load("{}", Loader=CSafeLoader)
yaml.load("{}", Loader=yaml.CSafeLoader)
yaml.load("{}", Loader=yaml.cyaml.CSafeLoader)
yaml.load("{}", Loader=NewSafeLoader)

View File

@ -199,9 +199,6 @@ def bytes_okay(value=bytes(1)):
def int_okay(value=int("12")):
pass
# Allow immutable slice()
def slice_okay(value=slice(1,2)):
pass
# Allow immutable complex() value
def complex_okay(value=complex(1,2)):

View File

@ -218,26 +218,3 @@ def should_not_fail(payload, Args):
Args:
The other arguments.
"""
# Test cases for Unpack[TypedDict] kwargs
from typing import TypedDict
from typing_extensions import Unpack
class User(TypedDict):
id: int
name: str
def function_with_unpack_args_should_not_fail(query: str, **kwargs: Unpack[User]):
"""Function with Unpack kwargs.
Args:
query: some arg
"""
def function_with_unpack_and_missing_arg_doc_should_fail(query: str, **kwargs: Unpack[User]):
"""Function with Unpack kwargs but missing query arg documentation.
Args:
**kwargs: keyword arguments
"""

View File

@ -2,40 +2,15 @@ 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
@ -44,9 +19,6 @@ 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
@ -54,18 +26,12 @@ 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()
@ -74,9 +40,6 @@ 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
@ -85,58 +48,27 @@ 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(Exception):
with suppress(AttributeError):
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 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
UserWarning("This is an assertion error") # PLW0133
# Non-violation test cases: PLW0133
@ -187,3 +119,10 @@ 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")

View File

@ -132,6 +132,7 @@ async def c():
# Non-errors
###
# False-negative: RustPython doesn't parse the `\N{snowman}`.
"\N{snowman} {}".format(a)
"{".format(a)
@ -275,6 +276,3 @@ if __name__ == "__main__":
number = 0
string = "{}".format(number := number + 1)
print(string)
# Unicode escape
"\N{angle}AOB = {angle}°".format(angle=180)

View File

@ -138,6 +138,5 @@ with open("file.txt", encoding="utf-8") as f:
with open("file.txt", encoding="utf-8") as f:
contents = process_contents(f.read())
with open("file1.txt", encoding="utf-8") as f:
with open("file.txt", encoding="utf-8") as f:
contents: str = process_contents(f.read())

View File

@ -1,8 +0,0 @@
from pathlib import Path
with Path("file.txt").open() as f:
contents = f.read()
with Path("file.txt").open("r") as f:
contents = f.read()

View File

@ -1,26 +0,0 @@
from pathlib import Path
with Path("file.txt").open("w") as f:
f.write("test")
with Path("file.txt").open("wb") as f:
f.write(b"test")
with Path("file.txt").open(mode="w") as f:
f.write("test")
with Path("file.txt").open("w", encoding="utf8") as f:
f.write("test")
with Path("file.txt").open("w", errors="ignore") as f:
f.write("test")
with Path(foo()).open("w") as f:
f.write("test")
p = Path("file.txt")
with p.open("w") as f:
f.write("test")
with Path("foo", "bar", "baz").open("w") as f:
f.write("test")

View File

@ -1,88 +0,0 @@
def f():
# These should both be ignored by the range suppression.
# ruff: disable[E741, F841]
I = 1
# ruff: enable[E741, F841]
def f():
# These should both be ignored by the implicit range suppression.
# Should also generate an "unmatched suppression" warning.
# ruff:disable[E741,F841]
I = 1
def f():
# Neither warning is ignored, and an "unmatched suppression"
# should be generated.
I = 1
# ruff: enable[E741, F841]
def f():
# One should be ignored by the range suppression, and
# the other logged to the user.
# ruff: disable[E741]
I = 1
# ruff: enable[E741]
def f():
# Test interleaved range suppressions. The first and last
# lines should each log a different warning, while the
# middle line should be completely silenced.
# ruff: disable[E741]
l = 0
# ruff: disable[F841]
O = 1
# ruff: enable[E741]
I = 2
# ruff: enable[F841]
def f():
# Neither of these are ignored and warnings are
# logged to user
# ruff: disable[E501]
I = 1
# ruff: enable[E501]
def f():
# These should both be ignored by the range suppression,
# and an unusued noqa diagnostic should be logged.
# 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")

View File

@ -1,38 +0,0 @@
a: int = 1
def f1():
global a
a: str = "foo" # error
b: int = 1
def outer():
def inner():
global b
b: str = "nested" # error
c: int = 1
def f2():
global c
c: list[str] = [] # error
d: int = 1
def f3():
global d
d: str # error
e: int = 1
def f4():
e: str = "happy" # okay
global f
f: int = 1 # okay
g: int = 1
global g # error
class C:
x: str
global x # error
class D:
global x # error
x: str

View File

@ -437,15 +437,6 @@ 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> {

View File

@ -12,20 +12,17 @@ use crate::fix::edits::delete_comment;
use crate::noqa::{
Code, Directive, FileExemption, FileNoqaDirectives, NoqaDirectives, NoqaMapping,
};
use crate::preview::is_range_suppressions_enabled;
use crate::registry::Rule;
use crate::rule_redirects::get_redirect_target;
use crate::rules::pygrep_hooks;
use crate::rules::ruff;
use crate::rules::ruff::rules::{UnusedCodes, UnusedNOQA};
use crate::settings::LinterSettings;
use crate::suppression::Suppressions;
use crate::{Edit, Fix, Locator};
use super::ast::LintContext;
/// RUF100
#[expect(clippy::too_many_arguments)]
pub(crate) fn check_noqa(
context: &mut LintContext,
path: &Path,
@ -34,7 +31,6 @@ pub(crate) fn check_noqa(
noqa_line_for: &NoqaMapping,
analyze_directives: bool,
settings: &LinterSettings,
suppressions: &Suppressions,
) -> Vec<usize> {
// Identify any codes that are globally exempted (within the current file).
let file_noqa_directives =
@ -44,7 +40,7 @@ pub(crate) fn check_noqa(
let mut noqa_directives =
NoqaDirectives::from_commented_ranges(comment_ranges, &settings.external, path, locator);
if file_noqa_directives.is_empty() && noqa_directives.is_empty() && suppressions.is_empty() {
if file_noqa_directives.is_empty() && noqa_directives.is_empty() {
return Vec::new();
}
@ -64,19 +60,11 @@ pub(crate) fn check_noqa(
continue;
}
// Apply file-level suppressions first
if exemption.contains_secondary_code(code) {
ignored_diagnostics.push(index);
continue;
}
// Apply ranged suppressions next
if is_range_suppressions_enabled(settings) && suppressions.check_diagnostic(diagnostic) {
ignored_diagnostics.push(index);
continue;
}
// Apply end-of-line noqa suppressions last
let noqa_offsets = diagnostic
.parent()
.into_iter()
@ -119,9 +107,6 @@ 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)
@ -143,13 +128,8 @@ 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,
kind: ruff::rules::UnusedNOQAKind::Noqa,
},
directive.range(),
);
let mut diagnostic = context
.report_diagnostic(UnusedNOQA { codes: None }, directive.range());
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Unnecessary);
diagnostic.set_fix(Fix::safe_edit(edit));
}
@ -244,7 +224,6 @@ pub(crate) fn check_noqa(
.map(|code| (*code).to_string())
.collect(),
}),
kind: ruff::rules::UnusedNOQAKind::Noqa,
},
directive.range(),
);

View File

@ -3,13 +3,14 @@
use anyhow::{Context, Result};
use ruff_python_ast::AnyNodeRef;
use ruff_python_ast::token::{self, Tokens, parenthesized_range};
use ruff_python_ast::parenthesize::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::{
PythonWhitespace, SimpleTokenKind, SimpleTokenizer, has_leading_content, is_python_whitespace,
CommentRanges, PythonWhitespace, SimpleTokenKind, SimpleTokenizer, has_leading_content,
is_python_whitespace,
};
use ruff_source_file::{LineRanges, NewlineWithTrailingNewline, UniversalNewlines};
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
@ -208,7 +209,7 @@ pub(crate) fn remove_argument<T: Ranged>(
arguments: &Arguments,
parentheses: Parentheses,
source: &str,
tokens: &Tokens,
comment_ranges: &CommentRanges,
) -> Result<Edit> {
// Partition into arguments before and after the argument to remove.
let (before, after): (Vec<_>, Vec<_>) = arguments
@ -223,7 +224,7 @@ pub(crate) fn remove_argument<T: Ranged>(
.context("Unable to find argument")?;
let parenthesized_range =
token::parenthesized_range(arg.value().into(), arguments.into(), tokens)
parenthesized_range(arg.value().into(), arguments.into(), comment_ranges, source)
.unwrap_or(arg.range());
if !after.is_empty() {
@ -269,13 +270,24 @@ 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, tokens: &Tokens) -> Edit {
pub(crate) fn add_argument(
argument: &str,
arguments: &Arguments,
comment_ranges: &CommentRanges,
source: &str,
) -> Edit {
if let Some(ast::Keyword { range, value, .. }) = arguments.keywords.first() {
let keyword = parenthesized_range(value.into(), arguments.into(), tokens).unwrap_or(*range);
let keyword = parenthesized_range(value.into(), arguments.into(), comment_ranges, source)
.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(), tokens)
let last = parenthesized_range(
last.value().into(),
arguments.into(),
comment_ranges,
source,
)
.unwrap_or(last.range());
Edit::insertion(format!(", {argument}"), last.end())
} else {
@ -286,7 +298,12 @@ pub(crate) fn add_argument(argument: &str, arguments: &Arguments, tokens: &Token
/// Generic function to add a (regular) parameter to a function definition.
pub(crate) fn add_parameter(parameter: &str, parameters: &Parameters, source: &str) -> Edit {
if let Some(last) = parameters.args.iter().rfind(|arg| arg.default.is_none()) {
if let Some(last) = parameters
.args
.iter()
.filter(|arg| arg.default.is_none())
.next_back()
{
// Case 1: at least one regular parameter, so append after the last one.
Edit::insertion(format!(", {parameter}"), last.end())
} else if !parameters.args.is_empty() {

View File

@ -32,7 +32,6 @@ use crate::rules::ruff::rules::test_rules::{self, TEST_RULES, TestRule};
use crate::settings::types::UnsafeFixes;
use crate::settings::{LinterSettings, TargetVersion, flags};
use crate::source_kind::SourceKind;
use crate::suppression::Suppressions;
use crate::{Locator, directives, fs};
pub(crate) mod float;
@ -129,7 +128,6 @@ pub fn check_path(
source_type: PySourceType,
parsed: &Parsed<ModModule>,
target_version: TargetVersion,
suppressions: &Suppressions,
) -> Vec<Diagnostic> {
// Aggregate all diagnostics.
let mut context = LintContext::new(path, locator.contents(), settings);
@ -341,7 +339,6 @@ pub fn check_path(
&directives.noqa_line_for,
parsed.has_valid_syntax(),
settings,
suppressions,
);
if noqa.is_enabled() {
for index in ignored.iter().rev() {
@ -403,9 +400,6 @@ pub fn add_noqa_to_path(
&indexer,
);
// Parse range suppression comments
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
// Generate diagnostics, ignoring any existing `noqa` directives.
let diagnostics = check_path(
path,
@ -420,7 +414,6 @@ pub fn add_noqa_to_path(
source_type,
&parsed,
target_version,
&suppressions,
);
// Add any missing `# noqa` pragmas.
@ -434,7 +427,6 @@ pub fn add_noqa_to_path(
&directives.noqa_line_for,
stylist.line_ending(),
reason,
&suppressions,
)
}
@ -469,9 +461,6 @@ pub fn lint_only(
&indexer,
);
// Parse range suppression comments
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
// Generate diagnostics.
let diagnostics = check_path(
path,
@ -486,7 +475,6 @@ pub fn lint_only(
source_type,
&parsed,
target_version,
&suppressions,
);
LinterResult {
@ -578,9 +566,6 @@ pub fn lint_fix<'a>(
&indexer,
);
// Parse range suppression comments
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
// Generate diagnostics.
let diagnostics = check_path(
path,
@ -595,7 +580,6 @@ pub fn lint_fix<'a>(
source_type,
&parsed,
target_version,
&suppressions,
);
if iterations == 0 {
@ -785,7 +769,6 @@ mod tests {
use crate::registry::Rule;
use crate::settings::LinterSettings;
use crate::source_kind::SourceKind;
use crate::suppression::Suppressions;
use crate::test::{TestedNotebook, assert_notebook_path, test_contents, test_snippet};
use crate::{Locator, assert_diagnostics, directives, settings};
@ -961,7 +944,6 @@ mod tests {
&locator,
&indexer,
);
let suppressions = Suppressions::from_tokens(settings, locator.contents(), parsed.tokens());
let mut diagnostics = check_path(
path,
None,
@ -975,7 +957,6 @@ mod tests {
source_type,
&parsed,
target_version,
&suppressions,
);
diagnostics.sort_by(Diagnostic::ruff_start_ordering);
diagnostics
@ -1001,7 +982,6 @@ mod tests {
#[test_case(Path::new("write_to_debug.py"), PythonVersion::PY310)]
#[test_case(Path::new("invalid_expression.py"), PythonVersion::PY312)]
#[test_case(Path::new("global_parameter.py"), PythonVersion::PY310)]
#[test_case(Path::new("annotated_global.py"), PythonVersion::PY314)]
fn test_semantic_errors(path: &Path, python_version: PythonVersion) -> Result<()> {
let snapshot = format!(
"semantic_syntax_error_{}_{}",

View File

@ -20,14 +20,12 @@ use crate::Locator;
use crate::fs::relativize_path;
use crate::registry::Rule;
use crate::rule_redirects::get_redirect_target;
use crate::suppression::Suppressions;
/// Generates an array of edits that matches the length of `messages`.
/// Each potential edit in the array is paired, in order, with the associated diagnostic.
/// Each edit will add a `noqa` comment to the appropriate line in the source to hide
/// the diagnostic. These edits may conflict with each other and should not be applied
/// simultaneously.
#[expect(clippy::too_many_arguments)]
pub fn generate_noqa_edits(
path: &Path,
diagnostics: &[Diagnostic],
@ -36,19 +34,11 @@ pub fn generate_noqa_edits(
external: &[String],
noqa_line_for: &NoqaMapping,
line_ending: LineEnding,
suppressions: &Suppressions,
) -> Vec<Option<Edit>> {
let file_directives = FileNoqaDirectives::extract(locator, comment_ranges, external, path);
let exemption = FileExemption::from(&file_directives);
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator);
let comments = find_noqa_comments(
diagnostics,
locator,
&exemption,
&directives,
noqa_line_for,
suppressions,
);
let comments = find_noqa_comments(diagnostics, locator, &exemption, &directives, noqa_line_for);
build_noqa_edits_by_diagnostic(comments, locator, line_ending, None)
}
@ -735,7 +725,6 @@ pub(crate) fn add_noqa(
noqa_line_for: &NoqaMapping,
line_ending: LineEnding,
reason: Option<&str>,
suppressions: &Suppressions,
) -> Result<usize> {
let (count, output) = add_noqa_inner(
path,
@ -746,7 +735,6 @@ pub(crate) fn add_noqa(
noqa_line_for,
line_ending,
reason,
suppressions,
);
fs::write(path, output)?;
@ -763,7 +751,6 @@ fn add_noqa_inner(
noqa_line_for: &NoqaMapping,
line_ending: LineEnding,
reason: Option<&str>,
suppressions: &Suppressions,
) -> (usize, String) {
let mut count = 0;
@ -773,14 +760,7 @@ fn add_noqa_inner(
let directives = NoqaDirectives::from_commented_ranges(comment_ranges, external, path, locator);
let comments = find_noqa_comments(
diagnostics,
locator,
&exemption,
&directives,
noqa_line_for,
suppressions,
);
let comments = find_noqa_comments(diagnostics, locator, &exemption, &directives, noqa_line_for);
let edits = build_noqa_edits_by_line(comments, locator, line_ending, reason);
@ -879,7 +859,6 @@ fn find_noqa_comments<'a>(
exemption: &'a FileExemption,
directives: &'a NoqaDirectives,
noqa_line_for: &NoqaMapping,
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![];
@ -896,12 +875,6 @@ fn find_noqa_comments<'a>(
continue;
}
// Apply ranged suppressions next
if suppressions.check_diagnostic(message) {
comments_by_line.push(None);
continue;
}
// Is the violation ignored by a `noqa` directive on the parent line?
if let Some(parent) = message.parent() {
if let Some(directive_line) =
@ -1280,7 +1253,6 @@ mod tests {
use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon};
use crate::rules::pyflakes::rules::UnusedVariable;
use crate::rules::pyupgrade::rules::PrintfStringFormatting;
use crate::suppression::Suppressions;
use crate::{Edit, Violation};
use crate::{Locator, generate_noqa_edits};
@ -2876,7 +2848,6 @@ mod tests {
&noqa_line_for,
LineEnding::Lf,
None,
&Suppressions::default(),
);
assert_eq!(count, 0);
assert_eq!(output, format!("{contents}"));
@ -2901,7 +2872,6 @@ mod tests {
&noqa_line_for,
LineEnding::Lf,
None,
&Suppressions::default(),
);
assert_eq!(count, 1);
assert_eq!(output, "x = 1 # noqa: F841\n");
@ -2933,7 +2903,6 @@ mod tests {
&noqa_line_for,
LineEnding::Lf,
None,
&Suppressions::default(),
);
assert_eq!(count, 1);
assert_eq!(output, "x = 1 # noqa: E741, F841\n");
@ -2965,7 +2934,6 @@ mod tests {
&noqa_line_for,
LineEnding::Lf,
None,
&Suppressions::default(),
);
assert_eq!(count, 0);
assert_eq!(output, "x = 1 # noqa");
@ -2988,7 +2956,6 @@ print(
let messages = [PrintfStringFormatting
.into_diagnostic(TextRange::new(12.into(), 79.into()), &source_file)];
let comment_ranges = CommentRanges::default();
let suppressions = Suppressions::default();
let edits = generate_noqa_edits(
path,
&messages,
@ -2997,7 +2964,6 @@ print(
&[],
&noqa_line_for,
LineEnding::Lf,
&suppressions,
);
assert_eq!(
edits,
@ -3021,7 +2987,6 @@ bar =
[UselessSemicolon.into_diagnostic(TextRange::new(4.into(), 5.into()), &source_file)];
let noqa_line_for = NoqaMapping::default();
let comment_ranges = CommentRanges::default();
let suppressions = Suppressions::default();
let edits = generate_noqa_edits(
path,
&messages,
@ -3030,7 +2995,6 @@ bar =
&[],
&noqa_line_for,
LineEnding::Lf,
&suppressions,
);
assert_eq!(
edits,

View File

@ -9,11 +9,6 @@ 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()
@ -291,8 +286,3 @@ pub(crate) const fn is_s310_resolve_string_literal_bindings_enabled(
) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/21623
pub(crate) const fn is_range_suppressions_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}

View File

@ -91,8 +91,8 @@ pub(crate) fn fastapi_redundant_response_model(checker: &Checker, function_def:
response_model_arg,
&call.arguments,
Parentheses::Preserve,
checker.source(),
checker.tokens(),
checker.locator().contents(),
checker.comment_ranges(),
)
.map(Fix::unsafe_edit)
});

View File

@ -70,7 +70,7 @@ fn is_open_call(func: &Expr, semantic: &SemanticModel) -> bool {
}
/// Returns `true` if an expression resolves to a call to `pathlib.Path.open`.
pub(crate) fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
fn is_open_call_from_pathlib(func: &Expr, semantic: &SemanticModel) -> bool {
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func else {
return false;
};

View File

@ -18,7 +18,7 @@ mod async_zero_sleep;
mod blocking_http_call;
mod blocking_http_call_httpx;
mod blocking_input;
pub(crate) mod blocking_open_call;
mod blocking_open_call;
mod blocking_path_methods;
mod blocking_process_invocation;
mod blocking_sleep;

View File

@ -75,7 +75,6 @@ pub(crate) fn unsafe_yaml_load(checker: &Checker, call: &ast::ExprCall) {
qualified_name.segments(),
["yaml", "SafeLoader" | "CSafeLoader"]
| ["yaml", "loader", "SafeLoader" | "CSafeLoader"]
| ["yaml", "cyaml", "CSafeLoader"]
)
})
{

View File

@ -74,7 +74,12 @@ 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.tokens()),
add_argument(
"strict=False",
&call.arguments,
checker.comment_ranges(),
checker.locator().contents(),
),
Applicability::Unsafe,
));
}

View File

@ -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::token::parenthesized_range;
use ruff_python_ast::parenthesize::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,7 +166,12 @@ fn move_initialization(
return None;
}
let range = match parenthesized_range(default.into(), parameter.into(), checker.tokens()) {
let range = match parenthesized_range(
default.into(),
parameter.into(),
checker.comment_ranges(),
checker.source(),
) {
Some(range) => range,
None => default.range(),
};
@ -189,7 +194,12 @@ fn move_initialization(
"{} = {}",
parameter.parameter.name(),
locator.slice(
parenthesized_range(default.into(), parameter.into(), checker.tokens())
parenthesized_range(
default.into(),
parameter.into(),
checker.comment_ranges(),
checker.source()
)
.unwrap_or(default.range())
)
);

View File

@ -92,7 +92,12 @@ 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.tokens());
let edit = add_argument(
"stacklevel=2",
&call.arguments,
checker.comment_ranges(),
checker.locator().contents(),
);
diagnostic.set_fix(Fix::unsafe_edit(edit));
}

View File

@ -70,7 +70,12 @@ 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.tokens()),
add_argument(
"strict=False",
&call.arguments,
checker.comment_ranges(),
checker.locator().contents(),
),
Applicability::Unsafe,
));
}

View File

@ -236,227 +236,227 @@ help: Replace with `None`; initialize within function
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:242:20
--> B006_B008.py:239:20
|
240 | # B006 and B008
241 | # We should handle arbitrary nesting of these B008.
242 | def nested_combo(a=[float(3), dt.datetime.now()]):
237 | # B006 and B008
238 | # We should handle arbitrary nesting of these B008.
239 | def nested_combo(a=[float(3), dt.datetime.now()]):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
243 | pass
240 | pass
|
help: Replace with `None`; initialize within function
239 |
240 | # B006 and B008
241 | # We should handle arbitrary nesting of these B008.
236 |
237 | # B006 and B008
238 | # We should handle arbitrary nesting of these B008.
- def nested_combo(a=[float(3), dt.datetime.now()]):
242 + def nested_combo(a=None):
243 | pass
244 |
245 |
239 + def nested_combo(a=None):
240 | pass
241 |
242 |
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:279:27
--> B006_B008.py:276:27
|
278 | def mutable_annotations(
279 | a: list[int] | None = [],
275 | def mutable_annotations(
276 | a: list[int] | None = [],
| ^^
280 | b: Optional[Dict[int, int]] = {},
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
277 | b: Optional[Dict[int, int]] = {},
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
help: Replace with `None`; initialize within function
276 |
277 |
278 | def mutable_annotations(
273 |
274 |
275 | def mutable_annotations(
- a: list[int] | None = [],
279 + a: list[int] | None = None,
280 | b: Optional[Dict[int, int]] = {},
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
276 + a: list[int] | None = None,
277 | b: Optional[Dict[int, int]] = {},
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:280:35
--> B006_B008.py:277:35
|
278 | def mutable_annotations(
279 | a: list[int] | None = [],
280 | b: Optional[Dict[int, int]] = {},
275 | def mutable_annotations(
276 | a: list[int] | None = [],
277 | b: Optional[Dict[int, int]] = {},
| ^^
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
help: Replace with `None`; initialize within function
277 |
278 | def mutable_annotations(
279 | a: list[int] | None = [],
274 |
275 | def mutable_annotations(
276 | a: list[int] | None = [],
- b: Optional[Dict[int, int]] = {},
280 + b: Optional[Dict[int, int]] = None,
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
283 | ):
277 + b: Optional[Dict[int, int]] = None,
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
280 | ):
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:281:62
--> B006_B008.py:278:62
|
279 | a: list[int] | None = [],
280 | b: Optional[Dict[int, int]] = {},
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
276 | a: list[int] | None = [],
277 | b: Optional[Dict[int, int]] = {},
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
| ^^^^^
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
283 | ):
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
280 | ):
|
help: Replace with `None`; initialize within function
278 | def mutable_annotations(
279 | a: list[int] | None = [],
280 | b: Optional[Dict[int, int]] = {},
275 | def mutable_annotations(
276 | a: list[int] | None = [],
277 | b: Optional[Dict[int, int]] = {},
- c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
281 + c: Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
283 | ):
284 | pass
278 + c: Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
280 | ):
281 | pass
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:282:80
--> B006_B008.py:279:80
|
280 | b: Optional[Dict[int, int]] = {},
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
277 | b: Optional[Dict[int, int]] = {},
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
| ^^^^^
283 | ):
284 | pass
280 | ):
281 | pass
|
help: Replace with `None`; initialize within function
279 | a: list[int] | None = [],
280 | b: Optional[Dict[int, int]] = {},
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
276 | a: list[int] | None = [],
277 | b: Optional[Dict[int, int]] = {},
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
- d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
282 + d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
283 | ):
284 | pass
285 |
279 + d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
280 | ):
281 | pass
282 |
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:287:52
--> B006_B008.py:284:52
|
287 | def single_line_func_wrong(value: dict[str, str] = {}):
284 | def single_line_func_wrong(value: dict[str, str] = {}):
| ^^
288 | """Docstring"""
285 | """Docstring"""
|
help: Replace with `None`; initialize within function
284 | pass
285 |
281 | pass
282 |
283 |
- def single_line_func_wrong(value: dict[str, str] = {}):
284 + def single_line_func_wrong(value: dict[str, str] = None):
285 | """Docstring"""
286 |
- def single_line_func_wrong(value: dict[str, str] = {}):
287 + def single_line_func_wrong(value: dict[str, str] = None):
288 | """Docstring"""
289 |
290 |
287 |
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:291:52
--> B006_B008.py:288:52
|
291 | def single_line_func_wrong(value: dict[str, str] = {}):
288 | def single_line_func_wrong(value: dict[str, str] = {}):
| ^^
292 | """Docstring"""
293 | ...
289 | """Docstring"""
290 | ...
|
help: Replace with `None`; initialize within function
288 | """Docstring"""
289 |
290 |
285 | """Docstring"""
286 |
287 |
- def single_line_func_wrong(value: dict[str, str] = {}):
291 + def single_line_func_wrong(value: dict[str, str] = None):
292 | """Docstring"""
293 | ...
294 |
288 + def single_line_func_wrong(value: dict[str, str] = None):
289 | """Docstring"""
290 | ...
291 |
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:296:52
--> B006_B008.py:293:52
|
296 | def single_line_func_wrong(value: dict[str, str] = {}):
293 | def single_line_func_wrong(value: dict[str, str] = {}):
| ^^
297 | """Docstring"""; ...
294 | """Docstring"""; ...
|
help: Replace with `None`; initialize within function
293 | ...
294 |
290 | ...
291 |
292 |
- def single_line_func_wrong(value: dict[str, str] = {}):
293 + def single_line_func_wrong(value: dict[str, str] = None):
294 | """Docstring"""; ...
295 |
- def single_line_func_wrong(value: dict[str, str] = {}):
296 + def single_line_func_wrong(value: dict[str, str] = None):
297 | """Docstring"""; ...
298 |
299 |
296 |
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:300:52
--> B006_B008.py:297:52
|
300 | def single_line_func_wrong(value: dict[str, str] = {}):
297 | def single_line_func_wrong(value: dict[str, str] = {}):
| ^^
301 | """Docstring"""; \
302 | ...
298 | """Docstring"""; \
299 | ...
|
help: Replace with `None`; initialize within function
297 | """Docstring"""; ...
298 |
299 |
294 | """Docstring"""; ...
295 |
296 |
- def single_line_func_wrong(value: dict[str, str] = {}):
300 + def single_line_func_wrong(value: dict[str, str] = None):
301 | """Docstring"""; \
302 | ...
303 |
297 + def single_line_func_wrong(value: dict[str, str] = None):
298 | """Docstring"""; \
299 | ...
300 |
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:305:52
--> B006_B008.py:302:52
|
305 | def single_line_func_wrong(value: dict[str, str] = {
302 | def single_line_func_wrong(value: dict[str, str] = {
| ____________________________________________________^
306 | | # This is a comment
307 | | }):
303 | | # This is a comment
304 | | }):
| |_^
308 | """Docstring"""
305 | """Docstring"""
|
help: Replace with `None`; initialize within function
302 | ...
303 |
304 |
299 | ...
300 |
301 |
- def single_line_func_wrong(value: dict[str, str] = {
- # This is a comment
- }):
305 + def single_line_func_wrong(value: dict[str, str] = None):
306 | """Docstring"""
307 |
308 |
302 + def single_line_func_wrong(value: dict[str, str] = None):
303 | """Docstring"""
304 |
305 |
note: This is an unsafe fix and may change runtime behavior
B006 Do not use mutable data structures for argument defaults
--> B006_B008.py:311:52
--> B006_B008.py:308:52
|
311 | def single_line_func_wrong(value: dict[str, str] = {}) \
308 | def single_line_func_wrong(value: dict[str, str] = {}) \
| ^^
312 | : \
313 | """Docstring"""
309 | : \
310 | """Docstring"""
|
help: Replace with `None`; initialize within function
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:316:52
--> B006_B008.py:313:52
|
316 | def single_line_func_wrong(value: dict[str, str] = {}):
313 | def single_line_func_wrong(value: dict[str, str] = {}):
| ^^
317 | """Docstring without newline"""
314 | """Docstring without newline"""
|
help: Replace with `None`; initialize within function
313 | """Docstring"""
314 |
315 |
310 | """Docstring"""
311 |
312 |
- def single_line_func_wrong(value: dict[str, str] = {}):
316 + def single_line_func_wrong(value: dict[str, str] = None):
317 | """Docstring without newline"""
313 + def single_line_func_wrong(value: dict[str, str] = None):
314 | """Docstring without newline"""
note: This is an unsafe fix and may change runtime behavior

View File

@ -53,39 +53,39 @@ B008 Do not perform function call in argument defaults; instead, perform the cal
|
B008 Do not perform function call `dt.datetime.now` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
--> B006_B008.py:242:31
--> B006_B008.py:239:31
|
240 | # B006 and B008
241 | # We should handle arbitrary nesting of these B008.
242 | def nested_combo(a=[float(3), dt.datetime.now()]):
237 | # B006 and B008
238 | # We should handle arbitrary nesting of these B008.
239 | def nested_combo(a=[float(3), dt.datetime.now()]):
| ^^^^^^^^^^^^^^^^^
243 | pass
240 | pass
|
B008 Do not perform function call `map` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
--> B006_B008.py:248:22
--> B006_B008.py:245:22
|
246 | # Don't flag nested B006 since we can't guarantee that
247 | # it isn't made mutable by the outer operation.
248 | def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
243 | # Don't flag nested B006 since we can't guarantee that
244 | # it isn't made mutable by the outer operation.
245 | def no_nested_b006(a=map(lambda s: s.upper(), ["a", "b", "c"])):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
249 | pass
246 | pass
|
B008 Do not perform function call `random.randint` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
--> B006_B008.py:253:19
--> B006_B008.py:250:19
|
252 | # B008-ception.
253 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
249 | # B008-ception.
250 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
254 | pass
251 | pass
|
B008 Do not perform function call `dt.datetime.now` in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable
--> B006_B008.py:253:37
--> B006_B008.py:250:37
|
252 | # B008-ception.
253 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
249 | # B008-ception.
250 | def nested_b008(a=random.randint(0, dt.datetime.now().year)):
| ^^^^^^^^^^^^^^^^^
254 | pass
251 | pass
|

View File

@ -236,227 +236,227 @@ help: Replace with `None`; initialize within function
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:242:20
--> B006_B008.py:239:20
|
240 | # B006 and B008
241 | # We should handle arbitrary nesting of these B008.
242 | def nested_combo(a=[float(3), dt.datetime.now()]):
237 | # B006 and B008
238 | # We should handle arbitrary nesting of these B008.
239 | def nested_combo(a=[float(3), dt.datetime.now()]):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
243 | pass
240 | pass
|
help: Replace with `None`; initialize within function
239 |
240 | # B006 and B008
241 | # We should handle arbitrary nesting of these B008.
236 |
237 | # B006 and B008
238 | # We should handle arbitrary nesting of these B008.
- def nested_combo(a=[float(3), dt.datetime.now()]):
242 + def nested_combo(a=None):
243 | pass
244 |
245 |
239 + def nested_combo(a=None):
240 | pass
241 |
242 |
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:279:27
--> B006_B008.py:276:27
|
278 | def mutable_annotations(
279 | a: list[int] | None = [],
275 | def mutable_annotations(
276 | a: list[int] | None = [],
| ^^
280 | b: Optional[Dict[int, int]] = {},
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
277 | b: Optional[Dict[int, int]] = {},
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
help: Replace with `None`; initialize within function
276 |
277 |
278 | def mutable_annotations(
273 |
274 |
275 | def mutable_annotations(
- a: list[int] | None = [],
279 + a: list[int] | None = None,
280 | b: Optional[Dict[int, int]] = {},
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
276 + a: list[int] | None = None,
277 | b: Optional[Dict[int, int]] = {},
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:280:35
--> B006_B008.py:277:35
|
278 | def mutable_annotations(
279 | a: list[int] | None = [],
280 | b: Optional[Dict[int, int]] = {},
275 | def mutable_annotations(
276 | a: list[int] | None = [],
277 | b: Optional[Dict[int, int]] = {},
| ^^
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
|
help: Replace with `None`; initialize within function
277 |
278 | def mutable_annotations(
279 | a: list[int] | None = [],
274 |
275 | def mutable_annotations(
276 | a: list[int] | None = [],
- b: Optional[Dict[int, int]] = {},
280 + b: Optional[Dict[int, int]] = None,
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
283 | ):
277 + b: Optional[Dict[int, int]] = None,
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
280 | ):
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:281:62
--> B006_B008.py:278:62
|
279 | a: list[int] | None = [],
280 | b: Optional[Dict[int, int]] = {},
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
276 | a: list[int] | None = [],
277 | b: Optional[Dict[int, int]] = {},
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
| ^^^^^
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
283 | ):
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
280 | ):
|
help: Replace with `None`; initialize within function
278 | def mutable_annotations(
279 | a: list[int] | None = [],
280 | b: Optional[Dict[int, int]] = {},
275 | def mutable_annotations(
276 | a: list[int] | None = [],
277 | b: Optional[Dict[int, int]] = {},
- c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
281 + c: Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
283 | ):
284 | pass
278 + c: Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
280 | ):
281 | pass
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:282:80
--> B006_B008.py:279:80
|
280 | b: Optional[Dict[int, int]] = {},
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
282 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
277 | b: Optional[Dict[int, int]] = {},
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
279 | d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
| ^^^^^
283 | ):
284 | pass
280 | ):
281 | pass
|
help: Replace with `None`; initialize within function
279 | a: list[int] | None = [],
280 | b: Optional[Dict[int, int]] = {},
281 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
276 | a: list[int] | None = [],
277 | b: Optional[Dict[int, int]] = {},
278 | c: Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
- d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = set(),
282 + d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
283 | ):
284 | pass
285 |
279 + d: typing_extensions.Annotated[Union[Set[str], abc.Sized], "annotation"] = None,
280 | ):
281 | pass
282 |
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:287:52
--> B006_B008.py:284:52
|
287 | def single_line_func_wrong(value: dict[str, str] = {}):
284 | def single_line_func_wrong(value: dict[str, str] = {}):
| ^^
288 | """Docstring"""
285 | """Docstring"""
|
help: Replace with `None`; initialize within function
284 | pass
285 |
281 | pass
282 |
283 |
- def single_line_func_wrong(value: dict[str, str] = {}):
284 + def single_line_func_wrong(value: dict[str, str] = None):
285 | """Docstring"""
286 |
- def single_line_func_wrong(value: dict[str, str] = {}):
287 + def single_line_func_wrong(value: dict[str, str] = None):
288 | """Docstring"""
289 |
290 |
287 |
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:291:52
--> B006_B008.py:288:52
|
291 | def single_line_func_wrong(value: dict[str, str] = {}):
288 | def single_line_func_wrong(value: dict[str, str] = {}):
| ^^
292 | """Docstring"""
293 | ...
289 | """Docstring"""
290 | ...
|
help: Replace with `None`; initialize within function
288 | """Docstring"""
289 |
290 |
285 | """Docstring"""
286 |
287 |
- def single_line_func_wrong(value: dict[str, str] = {}):
291 + def single_line_func_wrong(value: dict[str, str] = None):
292 | """Docstring"""
293 | ...
294 |
288 + def single_line_func_wrong(value: dict[str, str] = None):
289 | """Docstring"""
290 | ...
291 |
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:296:52
--> B006_B008.py:293:52
|
296 | def single_line_func_wrong(value: dict[str, str] = {}):
293 | def single_line_func_wrong(value: dict[str, str] = {}):
| ^^
297 | """Docstring"""; ...
294 | """Docstring"""; ...
|
help: Replace with `None`; initialize within function
293 | ...
294 |
290 | ...
291 |
292 |
- def single_line_func_wrong(value: dict[str, str] = {}):
293 + def single_line_func_wrong(value: dict[str, str] = None):
294 | """Docstring"""; ...
295 |
- def single_line_func_wrong(value: dict[str, str] = {}):
296 + def single_line_func_wrong(value: dict[str, str] = None):
297 | """Docstring"""; ...
298 |
299 |
296 |
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:300:52
--> B006_B008.py:297:52
|
300 | def single_line_func_wrong(value: dict[str, str] = {}):
297 | def single_line_func_wrong(value: dict[str, str] = {}):
| ^^
301 | """Docstring"""; \
302 | ...
298 | """Docstring"""; \
299 | ...
|
help: Replace with `None`; initialize within function
297 | """Docstring"""; ...
298 |
299 |
294 | """Docstring"""; ...
295 |
296 |
- def single_line_func_wrong(value: dict[str, str] = {}):
300 + def single_line_func_wrong(value: dict[str, str] = None):
301 | """Docstring"""; \
302 | ...
303 |
297 + def single_line_func_wrong(value: dict[str, str] = None):
298 | """Docstring"""; \
299 | ...
300 |
note: This is an unsafe fix and may change runtime behavior
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:305:52
--> B006_B008.py:302:52
|
305 | def single_line_func_wrong(value: dict[str, str] = {
302 | def single_line_func_wrong(value: dict[str, str] = {
| ____________________________________________________^
306 | | # This is a comment
307 | | }):
303 | | # This is a comment
304 | | }):
| |_^
308 | """Docstring"""
305 | """Docstring"""
|
help: Replace with `None`; initialize within function
302 | ...
303 |
304 |
299 | ...
300 |
301 |
- def single_line_func_wrong(value: dict[str, str] = {
- # This is a comment
- }):
305 + def single_line_func_wrong(value: dict[str, str] = None):
306 | """Docstring"""
307 |
308 |
302 + def single_line_func_wrong(value: dict[str, str] = None):
303 | """Docstring"""
304 |
305 |
note: This is an unsafe fix and may change runtime behavior
B006 Do not use mutable data structures for argument defaults
--> B006_B008.py:311:52
--> B006_B008.py:308:52
|
311 | def single_line_func_wrong(value: dict[str, str] = {}) \
308 | def single_line_func_wrong(value: dict[str, str] = {}) \
| ^^
312 | : \
313 | """Docstring"""
309 | : \
310 | """Docstring"""
|
help: Replace with `None`; initialize within function
B006 [*] Do not use mutable data structures for argument defaults
--> B006_B008.py:316:52
--> B006_B008.py:313:52
|
316 | def single_line_func_wrong(value: dict[str, str] = {}):
313 | def single_line_func_wrong(value: dict[str, str] = {}):
| ^^
317 | """Docstring without newline"""
314 | """Docstring without newline"""
|
help: Replace with `None`; initialize within function
313 | """Docstring"""
314 |
315 |
310 | """Docstring"""
311 |
312 |
- def single_line_func_wrong(value: dict[str, str] = {}):
316 + def single_line_func_wrong(value: dict[str, str] = None):
317 | """Docstring without newline"""
313 + def single_line_func_wrong(value: dict[str, str] = None):
314 | """Docstring without newline"""
note: This is an unsafe fix and may change runtime behavior

View File

@ -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,8 +142,12 @@ 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.tokens())
let range = parenthesized_range(
argument.into(),
(&call.arguments).into(),
checker.comment_ranges(),
checker.locator().contents(),
)
.unwrap_or(argument.range());
// The generator always parenthesizes the expression; trim the parentheses.

View File

@ -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,8 +147,12 @@ 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.tokens())
let range = parenthesized_range(
argument.into(),
(&call.arguments).into(),
checker.comment_ranges(),
checker.locator().contents(),
)
.unwrap_or(argument.range());
// The generator always parenthesizes the expression; trim the parentheses.

View File

@ -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,8 +89,12 @@ 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.tokens())
let replacement_range = parenthesized_range(
argument.into(),
(&call.arguments).into(),
checker.comment_ranges(),
checker.locator().contents(),
)
.unwrap_or_else(|| argument.range());
let span = argument.range().add_start(one).sub_end(one);

View File

@ -1,5 +1,5 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::token::parenthesized_range;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::{self as ast, Expr, Operator};
use ruff_python_trivia::is_python_whitespace;
use ruff_source_file::LineRanges;
@ -88,7 +88,13 @@ 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.tokens()).is_some()
parenthesized_range(
expr.into(),
bin_op.into(),
checker.comment_ranges(),
checker.source(),
)
.is_some()
};
// If either `left` or `right` is parenthesized, generating
// a fix would be too involved. Just report the diagnostic.

View File

@ -111,6 +111,7 @@ 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);
@ -119,8 +120,8 @@ pub(crate) fn exc_info_outside_except_handler(checker: &Checker, call: &ExprCall
exc_info,
arguments,
Parentheses::Preserve,
checker.source(),
checker.tokens(),
source,
checker.comment_ranges(),
)?;
Ok(Fix::unsafe_edit(edit))
});

View File

@ -2,7 +2,7 @@ use itertools::Itertools;
use rustc_hash::{FxBuildHasher, FxHashSet};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::token::parenthesized_range;
use ruff_python_ast::parenthesize::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.source(),
checker.tokens(),
checker.locator().contents(),
checker.comment_ranges(),
)
.map(Fix::safe_edit)
});
@ -158,7 +158,8 @@ pub(crate) fn unnecessary_dict_kwargs(checker: &Checker, call: &ast::ExprCall) {
parenthesized_range(
value.into(),
dict.into(),
checker.tokens()
checker.comment_ranges(),
checker.locator().contents(),
)
.unwrap_or(value.range())
)

View File

@ -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.source(),
checker.tokens(),
checker.locator().contents(),
checker.comment_ranges(),
)
.map(Fix::safe_edit)
});

View File

@ -160,16 +160,20 @@ 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,
tokens,
checker.comment_ranges(),
)?;
let insertion = add_argument(locator.slice(generic_base), arguments, tokens);
let insertion = add_argument(
locator.slice(generic_base),
arguments,
checker.comment_ranges(),
source,
);
Ok(Fix::unsafe_edits(deletion, [insertion]))
}

View File

@ -5,7 +5,7 @@ use ruff_python_ast::{
helpers::{pep_604_union, typing_optional},
name::Name,
operator_precedence::OperatorPrecedence,
token::{Tokens, parenthesized_range},
parenthesize::parenthesized_range,
};
use ruff_python_semantic::analyze::typing::{traverse_literal, traverse_union};
use ruff_text_size::{Ranged, TextRange};
@ -243,8 +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.tokens()) {
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)
@ -274,7 +278,8 @@ enum UnionKind {
fn needs_parentheses_for_precedence(
semantic: &ruff_python_semantic::SemanticModel,
literal_expr: &Expr,
tokens: &Tokens,
comment_ranges: &ruff_python_trivia::CommentRanges,
source: &str,
) -> 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 {
@ -282,7 +287,14 @@ fn needs_parentheses_for_precedence(
};
// Check if the literal expression is already parenthesized
if parenthesized_range(literal_expr.into(), parent_expr.into(), tokens).is_some() {
if parenthesized_range(
literal_expr.into(),
parent_expr.into(),
comment_ranges,
source,
)
.is_some()
{
return false; // Already parenthesized, don't add more
}

View File

@ -10,7 +10,7 @@ use libcst_native::{
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::helpers::Truthiness;
use ruff_python_ast::token::parenthesized_range;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::visitor::Visitor;
use ruff_python_ast::{
self as ast, AnyNodeRef, Arguments, BoolOp, ExceptHandler, Expr, Keyword, Stmt, UnaryOp,
@ -303,7 +303,8 @@ pub(crate) fn unittest_assertion(
parenthesized_range(
expr.into(),
checker.semantic().current_statement().into(),
checker.tokens(),
checker.comment_ranges(),
checker.locator().contents(),
)
.unwrap_or(expr.range()),
)));

View File

@ -768,8 +768,8 @@ fn check_fixture_decorator(checker: &Checker, func_name: &str, decorator: &Decor
keyword,
arguments,
edits::Parentheses::Preserve,
checker.source(),
checker.tokens(),
checker.locator().contents(),
checker.comment_ranges(),
)
.map(Fix::unsafe_edit)
});

View File

@ -2,9 +2,10 @@ use rustc_hash::{FxBuildHasher, FxHashMap};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::token::{Tokens, parenthesized_range};
use ruff_python_ast::parenthesize::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};
@ -321,8 +322,18 @@ 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, tokens: &Tokens) -> Option<TextRange> {
parenthesized_range(expr.into(), (&call.arguments).into(), tokens)
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,
)
}
/// PT006
@ -338,7 +349,12 @@ 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.tokens())
let name_range = get_parametrize_name_range(
call,
expr,
checker.comment_ranges(),
checker.locator().contents(),
)
.unwrap_or(expr.range());
let mut diagnostic = checker.report_diagnostic(
PytestParametrizeNamesWrongType {
@ -370,7 +386,12 @@ fn check_names(checker: &Checker, call: &ExprCall, expr: &Expr, argvalues: &Expr
)));
}
types::ParametrizeNameType::List => {
let name_range = get_parametrize_name_range(call, expr, checker.tokens())
let name_range = get_parametrize_name_range(
call,
expr,
checker.comment_ranges(),
checker.locator().contents(),
)
.unwrap_or(expr.range());
let mut diagnostic = checker.report_diagnostic(
PytestParametrizeNamesWrongType {

View File

@ -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::token::parenthesized_range;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_codegen::Generator;
use ruff_python_semantic::SemanticModel;
@ -800,7 +800,12 @@ fn is_short_circuit(
edit = Some(get_short_circuit_edit(
value,
TextRange::new(
parenthesized_range(furthest.into(), expr.into(), checker.tokens())
parenthesized_range(
furthest.into(),
expr.into(),
checker.comment_ranges(),
checker.locator().contents(),
)
.unwrap_or(furthest.range())
.start(),
expr.end(),
@ -823,7 +828,12 @@ fn is_short_circuit(
edit = Some(get_short_circuit_edit(
next_value,
TextRange::new(
parenthesized_range(furthest.into(), expr.into(), checker.tokens())
parenthesized_range(
furthest.into(),
expr.into(),
checker.comment_ranges(),
checker.locator().contents(),
)
.unwrap_or(furthest.range())
.start(),
expr.end(),

View File

@ -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::token::parenthesized_range;
use ruff_python_ast::parenthesize::parenthesized_range;
use crate::checkers::ast::Checker;
use crate::{AlwaysFixableViolation, Edit, Fix, FixAvailability, Violation};
@ -171,7 +171,12 @@ pub(crate) fn if_expr_with_true_false(
checker
.locator()
.slice(
parenthesized_range(test.into(), expr.into(), checker.tokens())
parenthesized_range(
test.into(),
expr.into(),
checker.comment_ranges(),
checker.locator().contents(),
)
.unwrap_or(test.range()),
)
.to_string(),

View File

@ -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::{SimpleTokenKind, SimpleTokenizer};
use ruff_python_trivia::{CommentRanges, 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) {
&current_branch,
following_branch,
checker.locator(),
checker.tokens(),
checker.comment_ranges(),
)
});
}
@ -111,7 +111,7 @@ fn merge_branches(
current_branch: &IfElifBranch,
following_branch: &IfElifBranch,
locator: &Locator,
tokens: &ruff_python_ast::token::Tokens,
comment_ranges: &CommentRanges,
) -> Result<Fix> {
// Identify the colon (`:`) at the end of the current branch's test.
let Some(current_branch_colon) =
@ -127,9 +127,12 @@ 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(), tokens)
{
let following_branch_test = if let Some(range) = parenthesized_range(
following_branch.test.into(),
stmt_if.into(),
comment_ranges,
locator.contents(),
) {
Cow::Borrowed(locator.slice(range))
} else if matches!(
following_branch.test,
@ -150,11 +153,16 @@ 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!(
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()
) && 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)),

View File

@ -1,6 +1,6 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::AnyNodeRef;
use ruff_python_ast::token::parenthesized_range;
use ruff_python_ast::parenthesize::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,10 +90,20 @@ 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.tokens()).unwrap_or(left.range());
let right_range =
parenthesized_range(right.into(), parent, checker.tokens()).unwrap_or(right.range());
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 mut diagnostic = checker.report_diagnostic(
InDictKeys {

View File

@ -146,7 +146,7 @@ fn reverse_comparison(expr: &Expr, locator: &Locator, stylist: &Stylist) -> Resu
let left = (*comparison.left).clone();
// Copy the right side to the left side.
*comparison.left = comparison.comparisons[0].comparator.clone();
comparison.left = Box::new(comparison.comparisons[0].comparator.clone());
// Copy the left side to the right side.
comparison.comparisons[0].comparator = left;

View File

@ -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::token::parenthesized_range;
use ruff_python_ast::parenthesize::parenthesized_range;
/// ## What it does
/// Checks if [PEP 613] explicit type aliases contain references to
@ -295,20 +295,21 @@ 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 parentheses outside the string ("""...""")
// Check for parenthesis outside string ("""...""")
annotation_expr.into(),
checker.semantic().current_statement().into(),
checker.source_tokens(),
checker.comment_ranges(),
checker.locator().contents(),
)
.is_none()
&& parenthesized_range(
// Check for parentheses inside the string """(...)"""
// Check for parenthesis inside string """(...)"""
expr.into(),
annotation_expr.into(),
checker.tokens(),
checker.comment_ranges(),
checker.locator().contents(),
)
.is_none()
{

View File

@ -1,9 +1,10 @@
use std::ops::Range;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::token::parenthesized_range;
use ruff_python_ast::parenthesize::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;
@ -88,7 +89,11 @@ 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.tokens()) {
match parent_and_next_path_fragment_range(
checker.semantic(),
checker.comment_ranges(),
checker.source(),
) {
Some((parent_range, next_fragment_range)) => {
let next_fragment_expr = checker.locator().slice(next_fragment_range);
let call_expr = checker.locator().slice(call.range());
@ -111,7 +116,7 @@ pub(crate) fn path_constructor_current_directory(
arguments,
Parentheses::Preserve,
checker.source(),
checker.tokens(),
checker.comment_ranges(),
)?;
Ok(Fix::applicable_edit(edit, applicability(call.range())))
}),
@ -120,7 +125,8 @@ pub(crate) fn path_constructor_current_directory(
fn parent_and_next_path_fragment_range(
semantic: &SemanticModel,
tokens: &ruff_python_ast::token::Tokens,
comment_ranges: &CommentRanges,
source: &str,
) -> Option<(TextRange, TextRange)> {
let parent = semantic.current_expression_parent()?;
@ -136,6 +142,6 @@ fn parent_and_next_path_fragment_range(
Some((
parent.range(),
parenthesized_range(right.into(), parent.into(), tokens).unwrap_or(range),
parenthesized_range(right.into(), parent.into(), comment_ranges, source).unwrap_or(range),
))
}

View File

@ -1,7 +1,8 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::helpers::is_const_true;
use ruff_python_ast::token::{Tokens, parenthesized_range};
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::{self as ast, Keyword, Stmt};
use ruff_python_trivia::CommentRanges;
use ruff_text_size::Ranged;
use crate::Locator;
@ -90,7 +91,7 @@ pub(crate) fn inplace_argument(checker: &Checker, call: &ast::ExprCall) {
call,
keyword,
statement,
checker.tokens(),
checker.comment_ranges(),
checker.locator(),
) {
diagnostic.set_fix(fix);
@ -110,14 +111,19 @@ fn convert_inplace_argument_to_assignment(
call: &ast::ExprCall,
keyword: &Keyword,
statement: &Stmt,
tokens: &Tokens,
comment_ranges: &CommentRanges,
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(), tokens)
parenthesized_range(
call.into(),
statement.into(),
comment_ranges,
locator.contents(),
)
.unwrap_or(call.range())
.start(),
);
@ -128,7 +134,7 @@ fn convert_inplace_argument_to_assignment(
&call.arguments,
Parentheses::Preserve,
locator.contents(),
tokens,
comment_ranges,
)
.ok()?;

View File

@ -1,5 +1,5 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::token::parenthesized_range;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::{
self as ast, Expr, ExprEllipsisLiteral, ExprLambda, Identifier, Parameter,
ParameterWithDefault, Parameters, Stmt,
@ -265,15 +265,25 @@ fn replace_trailing_ellipsis_with_original_expr(
stmt: &Stmt,
checker: &Checker,
) -> String {
let original_expr_range =
parenthesized_range((&lambda.body).into(), lambda.into(), checker.tokens())
let original_expr_range = parenthesized_range(
(&lambda.body).into(),
lambda.into(),
checker.comment_ranges(),
checker.source(),
)
.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.tokens()).is_some() {
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()

View File

@ -1,4 +1,4 @@
use ruff_python_ast::token::{Tokens, parenthesized_range};
use ruff_python_ast::parenthesize::parenthesized_range;
use rustc_hash::FxHashMap;
use ruff_macros::{ViolationMetadata, derive_message_formats};
@ -179,13 +179,14 @@ fn is_redundant_boolean_comparison(op: CmpOp, comparator: &Expr) -> Option<bool>
fn generate_redundant_comparison(
compare: &ast::ExprCompare,
tokens: &Tokens,
comment_ranges: &ruff_python_trivia::CommentRanges,
source: &str,
comparator: &Expr,
kind: bool,
needs_wrap: bool,
) -> String {
let comparator_range = parenthesized_range(comparator.into(), compare.into(), tokens)
let comparator_range =
parenthesized_range(comparator.into(), compare.into(), comment_ranges, source)
.unwrap_or(comparator.range());
let comparator_str = &source[comparator_range];
@ -378,7 +379,7 @@ pub(crate) fn literal_comparisons(checker: &Checker, compare: &ast::ExprCompare)
.copied()
.collect::<Vec<_>>();
let tokens = checker.tokens();
let comment_ranges = checker.comment_ranges();
let source = checker.source();
let content = match (&*compare.ops, &*compare.comparators) {
@ -386,13 +387,18 @@ 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, tokens, source, comparator, kind, needs_wrap,
compare,
comment_ranges,
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,
tokens,
comment_ranges,
source,
&compare.left,
kind,
@ -404,7 +410,7 @@ pub(crate) fn literal_comparisons(checker: &Checker, compare: &ast::ExprCompare)
&ops,
&compare.comparators,
compare.into(),
tokens,
comment_ranges,
source,
)
}
@ -414,7 +420,7 @@ pub(crate) fn literal_comparisons(checker: &Checker, compare: &ast::ExprCompare)
&ops,
&compare.comparators,
compare.into(),
tokens,
comment_ranges,
source,
),
};

View File

@ -107,7 +107,7 @@ pub(crate) fn not_tests(checker: &Checker, unary_op: &ast::ExprUnaryOp) {
&[CmpOp::NotIn],
comparators,
unary_op.into(),
checker.tokens(),
checker.comment_ranges(),
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.tokens(),
checker.comment_ranges(),
checker.source(),
),
unary_op.range(),

View File

@ -4,9 +4,7 @@ use rustc_hash::FxHashSet;
use std::sync::LazyLock;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::Parameter;
use ruff_python_ast::docstrings::{clean_space, leading_space};
use ruff_python_ast::helpers::map_subscript;
use ruff_python_ast::identifier::Identifier;
use ruff_python_semantic::analyze::visibility::is_staticmethod;
use ruff_python_trivia::textwrap::dedent;
@ -1186,9 +1184,6 @@ impl AlwaysFixableViolation for MissingSectionNameColon {
/// This rule is enabled when using the `google` convention, and disabled when
/// using the `pep257` and `numpy` conventions.
///
/// Parameters annotated with `typing.Unpack` are exempt from this rule.
/// This follows the Python typing specification for unpacking keyword arguments.
///
/// ## Example
/// ```python
/// def calculate_speed(distance: float, time: float) -> float:
@ -1238,7 +1233,6 @@ impl AlwaysFixableViolation for MissingSectionNameColon {
/// - [PEP 257 Docstring Conventions](https://peps.python.org/pep-0257/)
/// - [PEP 287 reStructuredText Docstring Format](https://peps.python.org/pep-0287/)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
/// - [Python - Unpack for keyword arguments](https://typing.python.org/en/latest/spec/callables.html#unpack-kwargs)
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "v0.0.73")]
pub(crate) struct UndocumentedParam {
@ -1814,9 +1808,7 @@ fn missing_args(checker: &Checker, docstring: &Docstring, docstrings_args: &FxHa
missing_arg_names.insert(starred_arg_name);
}
}
if let Some(arg) = function.parameters.kwarg.as_ref()
&& !has_unpack_annotation(checker, arg)
{
if let Some(arg) = function.parameters.kwarg.as_ref() {
let arg_name = arg.name.as_str();
let starred_arg_name = format!("**{arg_name}");
if !arg_name.starts_with('_')
@ -1842,15 +1834,6 @@ fn missing_args(checker: &Checker, docstring: &Docstring, docstrings_args: &FxHa
}
}
/// Returns `true` if the parameter is annotated with `typing.Unpack`
fn has_unpack_annotation(checker: &Checker, parameter: &Parameter) -> bool {
parameter.annotation.as_ref().is_some_and(|annotation| {
checker
.semantic()
.match_typing_expr(map_subscript(annotation), "Unpack")
})
}
// See: `GOOGLE_ARGS_REGEX` in `pydocstyle/checker.py`.
static GOOGLE_ARGS_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"^\s*(\*?\*?\w+)\s*(\(.*?\))?\s*:(\r\n|\n)?\s*.+").unwrap());

View File

@ -101,13 +101,3 @@ D417 Missing argument description in the docstring for `should_fail`: `Args`
200 | """
201 | Send a message.
|
D417 Missing argument description in the docstring for `function_with_unpack_and_missing_arg_doc_should_fail`: `query`
--> D417.py:238:5
|
236 | """
237 |
238 | def function_with_unpack_and_missing_arg_doc_should_fail(query: str, **kwargs: Unpack[User]):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
239 | """Function with Unpack kwargs but missing query arg documentation.
|

View File

@ -83,13 +83,3 @@ D417 Missing argument description in the docstring for `should_fail`: `Args`
200 | """
201 | Send a message.
|
D417 Missing argument description in the docstring for `function_with_unpack_and_missing_arg_doc_should_fail`: `query`
--> D417.py:238:5
|
236 | """
237 |
238 | def function_with_unpack_and_missing_arg_doc_should_fail(query: str, **kwargs: Unpack[User]):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
239 | """Function with Unpack kwargs but missing query arg documentation.
|

View File

@ -101,13 +101,3 @@ D417 Missing argument description in the docstring for `should_fail`: `Args`
200 | """
201 | Send a message.
|
D417 Missing argument description in the docstring for `function_with_unpack_and_missing_arg_doc_should_fail`: `query`
--> D417.py:238:5
|
236 | """
237 |
238 | def function_with_unpack_and_missing_arg_doc_should_fail(query: str, **kwargs: Unpack[User]):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
239 | """Function with Unpack kwargs but missing query arg documentation.
|

View File

@ -101,13 +101,3 @@ D417 Missing argument description in the docstring for `should_fail`: `Args`
200 | """
201 | Send a message.
|
D417 Missing argument description in the docstring for `function_with_unpack_and_missing_arg_doc_should_fail`: `query`
--> D417.py:238:5
|
236 | """
237 |
238 | def function_with_unpack_and_missing_arg_doc_should_fail(query: str, **kwargs: Unpack[User]):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
239 | """Function with Unpack kwargs but missing query arg documentation.
|

View File

@ -28,7 +28,6 @@ mod tests {
use crate::settings::types::PreviewMode;
use crate::settings::{LinterSettings, flags};
use crate::source_kind::SourceKind;
use crate::suppression::Suppressions;
use crate::test::{test_contents, test_path, test_snippet};
use crate::{Locator, assert_diagnostics, assert_diagnostics_diff, directives};
@ -956,8 +955,6 @@ mod tests {
&locator,
&indexer,
);
let suppressions =
Suppressions::from_tokens(&settings, locator.contents(), parsed.tokens());
let mut messages = check_path(
Path::new("<filename>"),
None,
@ -971,7 +968,6 @@ mod tests {
source_type,
&parsed,
target_version,
&suppressions,
);
messages.sort_by(Diagnostic::ruff_start_ordering);
let actual = messages

View File

@ -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::token::parenthesized_range;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::{self as ast, Expr};
use ruff_text_size::Ranged;
@ -193,14 +193,16 @@ pub(crate) fn repeated_keys(checker: &Checker, dict: &ast::ExprDict) {
parenthesized_range(
dict.value(i - 1).into(),
dict.into(),
checker.tokens(),
checker.comment_ranges(),
checker.locator().contents(),
)
.unwrap_or_else(|| dict.value(i - 1).range())
.end(),
parenthesized_range(
dict.value(i).into(),
dict.into(),
checker.tokens(),
checker.comment_ranges(),
checker.locator().contents(),
)
.unwrap_or_else(|| dict.value(i).range())
.end(),
@ -222,14 +224,16 @@ pub(crate) fn repeated_keys(checker: &Checker, dict: &ast::ExprDict) {
parenthesized_range(
dict.value(i - 1).into(),
dict.into(),
checker.tokens(),
checker.comment_ranges(),
checker.locator().contents(),
)
.unwrap_or_else(|| dict.value(i - 1).range())
.end(),
parenthesized_range(
dict.value(i).into(),
dict.into(),
checker.tokens(),
checker.comment_ranges(),
checker.locator().contents(),
)
.unwrap_or_else(|| dict.value(i).range())
.end(),

View File

@ -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::token::parenthesized_range;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::token::{TokenKind, Tokens};
use ruff_python_ast::{self as ast, Stmt};
use ruff_python_semantic::Binding;
@ -172,8 +172,12 @@ 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.tokens())
let start = parenthesized_range(
target.into(),
statement.into(),
checker.comment_ranges(),
checker.locator().contents(),
)
.unwrap_or(target.range())
.start();
let end = match_token_after(checker.tokens(), target.end(), |token| {

View File

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

View File

@ -2,7 +2,7 @@ use itertools::Itertools;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{
BoolOp, CmpOp, Expr, ExprBoolOp, ExprCompare,
token::{parentheses_iterator, parenthesized_range},
parenthesize::{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 tokens = checker.tokens();
let comment_ranges = checker.comment_ranges();
// retrieve all compare expressions from boolean expression
let compare_expressions = expr_bool_op
@ -89,21 +89,39 @@ 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()), tokens).count();
let left_paren_count = parentheses_iterator(
left_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();
let right_paren_count = parentheses_iterator(
right_compare.into(),
Some(expr_bool_op.into()),
comment_ranges,
locator.contents(),
)
.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(), tokens)
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(), tokens)
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 edit = Edit::range_replacement(
locator.slice(left_compare_right_range).to_string(),

View File

@ -99,7 +99,7 @@ pub(crate) fn duplicate_bases(checker: &Checker, name: &str, arguments: Option<&
arguments,
Parentheses::Remove,
checker.locator().contents(),
checker.tokens(),
checker.comment_ranges(),
)
.map(|edit| {
Fix::applicable_edit(

View File

@ -1,6 +1,6 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::token::parenthesized_range;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::{self as ast, CmpOp, Stmt};
use ruff_text_size::Ranged;
@ -166,7 +166,12 @@ 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.tokens())
parenthesized_range(
body_target.into(),
body.into(),
checker.comment_ranges(),
checker.locator().contents()
)
.unwrap_or(body_target.range())
),
checker.locator().slice(arg1),

View File

@ -174,8 +174,12 @@ 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.tokens());
let maxsplit_argument_edit = fix::edits::add_argument(
"maxsplit=1",
arguments,
checker.comment_ranges(),
checker.locator().contents(),
);
// 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 {

View File

@ -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::token::parenthesized_range;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::{ExprBinOp, ExprRef, Operator};
use ruff_text_size::{Ranged, TextRange};
@ -150,10 +150,12 @@ fn augmented_assignment(
let right_operand_ref = ExprRef::from(right_operand);
let parent = original_expr.into();
let tokens = checker.tokens();
let comment_ranges = checker.comment_ranges();
let source = checker.source();
let right_operand_range =
parenthesized_range(right_operand_ref, parent, tokens).unwrap_or(right_operand.range());
parenthesized_range(right_operand_ref, parent, comment_ranges, source)
.unwrap_or(right_operand.range());
let right_operand_expr = locator.slice(right_operand_range);
let target_expr = locator.slice(target);

View File

@ -75,7 +75,12 @@ 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.tokens()),
add_argument(
"check=False",
&call.arguments,
checker.comment_ranges(),
checker.locator().contents(),
),
// If the function call contains `**kwargs`, mark the fix as unsafe.
if call
.arguments

View File

@ -1,7 +1,8 @@
use std::fmt::{Display, Formatter};
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{self as ast, Expr, name::QualifiedName};
use ruff_python_ast::name::QualifiedName;
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::SemanticModel;
use ruff_python_semantic::analyze::typing;
use ruff_text_size::{Ranged, TextRange};
@ -192,7 +193,8 @@ fn generate_keyword_fix(checker: &Checker, call: &ast::ExprCall) -> Fix {
}))
),
&call.arguments,
checker.tokens(),
checker.comment_ranges(),
checker.locator().contents(),
))
}

View File

@ -1,11 +1,10 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::{SemanticModel, analyze};
use ruff_python_semantic::SemanticModel;
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;
@ -21,9 +20,6 @@ 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("...")
@ -36,8 +32,7 @@ use ruff_python_ast::PythonVersion;
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as converting a useless exception
///
/// [preview]: https://docs.astral.sh/ruff/preview/
/// statement to a `raise` statement will change the program's behavior.
#[derive(ViolationMetadata)]
#[violation_metadata(stable_since = "0.5.0")]
pub(crate) struct UselessExceptionStatement;
@ -61,10 +56,7 @@ pub(crate) fn useless_exception_statement(checker: &Checker, expr: &ast::StmtExp
return;
};
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()))
{
if is_builtin_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(),
@ -86,34 +78,3 @@ 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
}

View File

@ -2,294 +2,250 @@
source: crates/ruff_linter/src/rules/pylint/mod.rs
---
PLW0133 [*] Missing `raise` statement on exception
--> useless_exception_statement.py:26:5
--> useless_exception_statement.py:7:5
|
24 | # Test case 1: Useless exception statement
25 | def func():
26 | AssertionError("This is an assertion error") # PLW0133
5 | # Test case 1: Useless exception statement
6 | def func():
7 | 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():
4 |
5 | # Test case 1: Useless exception statement
6 | 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
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:35:9
--> useless_exception_statement.py:13:9
|
33 | def func():
34 | try:
35 | Exception("This is an exception") # PLW0133
11 | def func():
12 | try:
13 | Exception("This is an exception") # PLW0133
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
36 | MyError("This is an exception") # PLW0133
37 | MySubError("This is an exception") # PLW0133
14 | except Exception as err:
15 | pass
|
help: Add `raise` keyword
32 | # Test case 2: Useless exception statement in try-except block
33 | def func():
34 | try:
10 | # Test case 2: Useless exception statement in try-except block
11 | def func():
12 | 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
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:46:9
--> useless_exception_statement.py:21:9
|
44 | def func():
45 | if True:
46 | RuntimeError("This is an exception") # PLW0133
19 | def func():
20 | if True:
21 | 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:
18 | # Test case 3: Useless exception statement in if statement
19 | def func():
20 | 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
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:56:13
--> useless_exception_statement.py:28:13
|
54 | class Class:
55 | def __init__(self):
56 | TypeError("This is an exception") # PLW0133
26 | class Class:
27 | def __init__(self):
28 | 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):
25 | def func():
26 | class Class:
27 | def __init__(self):
- TypeError("This is an exception") # PLW0133
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
28 + raise TypeError("This is an exception") # PLW0133
29 |
30 |
31 | # 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:65:9
--> useless_exception_statement.py:34:9
|
63 | def func():
64 | def inner():
65 | IndexError("This is an exception") # PLW0133
32 | def func():
33 | def inner():
34 | IndexError("This is an exception") # PLW0133
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
66 | MyError("This is an exception") # PLW0133
67 | MySubError("This is an exception") # PLW0133
35 |
36 | inner()
|
help: Add `raise` keyword
62 | # Test case 5: Useless exception statement in function
63 | def func():
64 | def inner():
31 | # Test case 5: Useless exception statement in function
32 | def func():
33 | def inner():
- IndexError("This is an exception") # PLW0133
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
34 + raise IndexError("This is an exception") # PLW0133
35 |
36 | inner()
37 |
note: This is an unsafe fix and may change runtime behavior
PLW0133 [*] Missing `raise` statement on exception
--> useless_exception_statement.py:76:9
--> useless_exception_statement.py:42:9
|
74 | def func():
75 | while True:
76 | KeyError("This is an exception") # PLW0133
40 | def func():
41 | while True:
42 | KeyError("This is an exception") # PLW0133
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
77 | MyError("This is an exception") # PLW0133
78 | MySubError("This is an exception") # PLW0133
|
help: Add `raise` keyword
73 | # Test case 6: Useless exception statement in while loop
74 | def func():
75 | while True:
39 | # Test case 6: Useless exception statement in while loop
40 | def func():
41 | while True:
- KeyError("This is an exception") # PLW0133
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
42 + raise KeyError("This is an exception") # PLW0133
43 |
44 |
45 | # 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:87:13
--> useless_exception_statement.py:50:13
|
85 | @abstractmethod
86 | def method(self):
87 | NotImplementedError("This is an exception") # PLW0133
48 | @abstractmethod
49 | def method(self):
50 | NotImplementedError("This is an exception") # PLW0133
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
88 | MyError("This is an exception") # PLW0133
89 | MySubError("This is an exception") # PLW0133
|
help: Add `raise` keyword
84 | class Class(ABC):
85 | @abstractmethod
86 | def method(self):
47 | class Class(ABC):
48 | @abstractmethod
49 | def method(self):
- NotImplementedError("This is an exception") # PLW0133
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
50 + raise NotImplementedError("This is an exception") # PLW0133
51 |
52 |
53 | # 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:96:9
--> useless_exception_statement.py:56:9
|
94 | def func():
95 | with suppress(Exception):
96 | AttributeError("This is an exception") # PLW0133
54 | def func():
55 | with suppress(AttributeError):
56 | AttributeError("This is an exception") # PLW0133
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
97 | MyError("This is an exception") # PLW0133
98 | MySubError("This is an exception") # PLW0133
|
help: Add `raise` keyword
93 | # Test case 8: Useless exception statement inside context manager
94 | def func():
95 | with suppress(Exception):
53 | # Test case 8: Useless exception statement inside context manager
54 | def func():
55 | with suppress(AttributeError):
- AttributeError("This is an exception") # PLW0133
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
56 + raise AttributeError("This is an exception") # PLW0133
57 |
58 |
59 | # 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:104:5
--> useless_exception_statement.py:61:5
|
102 | # Test case 9: Useless exception statement in parentheses
103 | def func():
104 | (RuntimeError("This is an exception")) # PLW0133
59 | # Test case 9: Useless exception statement in parentheses
60 | def func():
61 | (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():
58 |
59 | # Test case 9: Useless exception statement in parentheses
60 | 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
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:112:12
--> useless_exception_statement.py:66:12
|
110 | # Test case 10: Useless exception statement in continuation
111 | def func():
112 | x = 1; (RuntimeError("This is an exception")); y = 2 # PLW0133
64 | # Test case 10: Useless exception statement in continuation
65 | def func():
66 | 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():
63 |
64 | # Test case 10: Useless exception statement in continuation
65 | 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
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:120:5
--> useless_exception_statement.py:71: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
69 | # Test case 11: Useless warning statement
70 | def func():
71 | UserWarning("This is an assertion error") # 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 |
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:127:1
--> useless_exception_statement.py:126:1
|
125 | import builtins
126 |
127 | builtins.TypeError("still an exception even though it's an Attribute") # PLW0133
124 | import builtins
125 |
126 | builtins.TypeError("still an exception even though it's an Attribute")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
128 |
129 | PythonFinalizationError("Added in Python 3.13") # PLW0133
127 |
128 | PythonFinalizationError("Added in Python 3.13")
|
help: Add `raise` keyword
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 |
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:129:1
--> useless_exception_statement.py:128:1
|
127 | builtins.TypeError("still an exception even though it's an Attribute") # PLW0133
128 |
129 | PythonFinalizationError("Added in Python 3.13") # PLW0133
126 | builtins.TypeError("still an exception even though it's an Attribute")
127 |
128 | PythonFinalizationError("Added in Python 3.13")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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 |
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")
note: This is an unsafe fix and may change runtime behavior

View File

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

View File

@ -204,7 +204,7 @@ pub(crate) fn non_pep695_generic_class(checker: &Checker, class_def: &StmtClassD
arguments,
Parentheses::Remove,
checker.source(),
checker.tokens(),
checker.comment_ranges(),
)?;
Ok(Fix::unsafe_edits(
Edit::insertion(type_params.to_string(), name.end()),

View File

@ -2,7 +2,7 @@ use itertools::Itertools;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::name::Name;
use ruff_python_ast::token::parenthesized_range;
use ruff_python_ast::parenthesize::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(), tokens).unwrap_or(value.range());
parenthesized_range(value.into(), stmt.into(), comment_ranges, source)
.unwrap_or(value.range());
let content = format!(
"type {name}{type_params} = {value}",

View File

@ -1,8 +1,9 @@
use anyhow::Result;
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::{self as ast, Keyword, token::Tokens};
use ruff_python_ast::{self as ast, Keyword};
use ruff_python_semantic::Modules;
use ruff_python_trivia::CommentRanges;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@ -103,7 +104,7 @@ pub(crate) fn replace_stdout_stderr(checker: &Checker, call: &ast::ExprCall) {
stderr,
call,
checker.locator().contents(),
checker.tokens(),
checker.comment_ranges(),
)
});
}
@ -116,7 +117,7 @@ fn generate_fix(
stderr: &Keyword,
call: &ast::ExprCall,
source: &str,
tokens: &Tokens,
comment_ranges: &CommentRanges,
) -> Result<Fix> {
let (first, second) = if stdout.start() < stderr.start() {
(stdout, stderr)
@ -131,7 +132,7 @@ fn generate_fix(
&call.arguments,
Parentheses::Preserve,
source,
tokens,
comment_ranges,
)?],
))
}

View File

@ -78,7 +78,7 @@ pub(crate) fn replace_universal_newlines(checker: &Checker, call: &ast::ExprCall
&call.arguments,
Parentheses::Preserve,
checker.locator().contents(),
checker.tokens(),
checker.comment_ranges(),
)
.map(Fix::safe_edit)
});

View File

@ -188,7 +188,7 @@ pub(crate) fn unnecessary_encode_utf8(checker: &Checker, call: &ast::ExprCall) {
&call.arguments,
Parentheses::Preserve,
checker.locator().contents(),
checker.tokens(),
checker.comment_ranges(),
)
.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.tokens(),
checker.comment_ranges(),
)
.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.tokens(),
checker.comment_ranges(),
)
.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.tokens(),
checker.comment_ranges(),
)
.map(Fix::safe_edit)
});

View File

@ -70,7 +70,7 @@ pub(crate) fn useless_class_metaclass_type(checker: &Checker, class_def: &StmtCl
arguments,
Parentheses::Remove,
checker.locator().contents(),
checker.tokens(),
checker.comment_ranges(),
)?;
let range = edit.range();

View File

@ -73,7 +73,7 @@ pub(crate) fn useless_object_inheritance(checker: &Checker, class_def: &ast::Stm
arguments,
Parentheses::Remove,
checker.locator().contents(),
checker.tokens(),
checker.comment_ranges(),
)?;
let range = edit.range();

View File

@ -1,5 +1,5 @@
use ruff_macros::{ViolationMetadata, derive_message_formats};
use ruff_python_ast::token::parenthesized_range;
use ruff_python_ast::parenthesize::parenthesized_range;
use ruff_python_ast::{self as ast, Expr, Stmt};
use ruff_text_size::Ranged;
@ -139,7 +139,12 @@ 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.tokens())
parenthesized_range(
iter.as_ref().into(),
stmt_for.into(),
checker.comment_ranges(),
checker.locator().contents(),
)
.unwrap_or(iter.range()),
);
let contents = if iter.as_tuple_expr().is_some_and(|it| !it.parenthesized) {

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