Compare commits

...

49 Commits

Author SHA1 Message Date
Charlie Marsh
0c4926ff7b Bump version to v0.0.257 (#3591) 2023-03-17 22:34:10 -04:00
tomecki
61653b9f27 [pylint] Implement useless-return (R1711) (#3116) 2023-03-17 18:30:32 -04:00
Charlie Marsh
8dd3959e74 Update output in resources/test/project/README.md (#3587) 2023-03-17 21:51:03 +00:00
Charlie Marsh
50f9db21da Enable ANSI colors on Windows 10 (#3583) 2023-03-17 17:34:39 -04:00
Tomer Chachamu
1dd3cbd047 [pylint] invalid-characters-* (#3552) 2023-03-17 19:30:41 +00:00
Johan
bd935cbd49 [flake8-bugbear] Add no-explicit-stacklevel (B028) (#3550) 2023-03-17 19:20:08 +00:00
Charlie Marsh
babd0a05ac Avoid adding dashed line outside of docstring (#3581) 2023-03-17 14:40:32 -04:00
Micha Reiser
87fab4a2e1 Benchmark all rules (#3570) 2023-03-17 19:29:39 +01:00
Charlie Marsh
2e21920adf Respect type overrides in E721 (#3582) 2023-03-17 14:29:05 -04:00
Micha Reiser
dedf4cbdeb refactor: Move scope and binding types to scope.rs (#3573) 2023-03-17 17:31:33 +01:00
Micha Reiser
92179e6369 Scope and Binding IDs (#3572) 2023-03-17 17:12:27 +01:00
Evan Rittenhouse
33d2457909 Prefer itertools.pairwise() over zip() for successive pairs (RUF007) (#3501) 2023-03-16 23:50:45 -04:00
Charlie Marsh
373a77e8c2 Avoid C1901 violations within subscripts (#3517) 2023-03-17 02:52:05 +00:00
Jacob Latonis
73df267635 [pylint]: Implement continue-in-finally (E0116) (#3541) 2023-03-17 02:47:49 +00:00
Jonathan Plasse
f5e5caaa25 Fix autofix conflict between D209 and D400 (#3564) 2023-03-17 02:36:25 +00:00
Henry Schreiner
d9ed0aae69 ci(check_ecosystem): add cibuildwheel (#3567) 2023-03-16 22:34:56 -04:00
Charlie Marsh
e0df62b841 Rewrite mock import with starred imports (#3566) 2023-03-16 20:54:29 -04:00
Henry Schreiner
bbc87b7177 ci(check_ecosystem): add scikit-build-core (#3563) 2023-03-16 19:46:42 -04:00
Charlie Marsh
667130a4c3 Add some additional users to the users list (#3565) 2023-03-16 23:32:17 +00:00
Nyakku Shigure
72febf98b7 add PaddlePaddle to Who's Using Ruff? (#3562) 2023-03-16 14:20:11 -04:00
Xuehai Pan
e99e1fae2b ci: add python/typeshed to ecosystem check (#3559) 2023-03-16 14:19:48 -04:00
Micha Reiser
eff84442bc refactor: Add Copy implementation to Rule (#3556) 2023-03-16 17:50:18 +01:00
Micha Reiser
aa51ecedc5 ci: Benchmark CI Step (#3480) 2023-03-16 09:05:10 +01:00
Edgar R. M
9ae9cc9d2f Use value > max style in pylint and mccabe messages (#3553) 2023-03-16 01:37:25 -04:00
Micha Reiser
de1106b95a Allow dispatching the PR comment job for testing (#3535) 2023-03-15 09:34:53 +01:00
Charlie Marsh
e636c5fcf0 Avoid unused argument violations in .pyi files (#3533) 2023-03-15 03:17:19 +00:00
Charlie Marsh
12dfd57211 Bump version to v0.0.256 (#3531) 2023-03-14 22:52:21 -04:00
Charlie Marsh
d188d242a0 Avoid tracking as-imports separately with force-single-line (#3530) 2023-03-15 02:26:01 +00:00
Charlie Marsh
57796c5e59 Add last remaining deprecated typing imports (#3529) 2023-03-15 00:08:09 +00:00
Charlie Marsh
2545869797 Avoid PEP 604 isinstance errors for starred tuples (#3527) 2023-03-14 22:08:43 +00:00
Charlie Marsh
58353a4bf4 Avoid PEP 604 panic with empty tuple (#3526) 2023-03-14 22:02:15 +00:00
Charlie Marsh
a36139ae21 Replicate inline comments when splitting single-line imports (#3521) 2023-03-14 14:48:12 -04:00
Jonathan Plasse
7e904111b1 Fix PYI011 and add auto-fix (#3492) 2023-03-14 14:43:09 -04:00
Charlie Marsh
344daebb1b Refine complexity rules for try-except-else-finally (#3519) 2023-03-14 14:40:33 -04:00
Charlie Marsh
432059de35 Allow # ruff: prefix for isort action comments (#3493) 2023-03-14 14:34:28 -04:00
Charlie Marsh
c50d6da8b4 Allow string percent formatting in os.getenv (#3518) 2023-03-14 14:27:21 -04:00
Charlie Marsh
1b738f88c4 Allow f-strings and concatenations in os.getenv (#3516) 2023-03-14 17:46:34 +00:00
Charlie Marsh
1eff3dffa5 Ensure that redirect warnings appear exactly once per code (#3500) 2023-03-14 15:22:14 +00:00
Xuehai Pan
8c7317eb8d ci: fix missing short tag for cloudflare/wrangler-action (#3513) 2023-03-14 15:16:09 +00:00
Charlie Marsh
106a93eab0 Make Clap an optional feature for ruff crate (#3498) 2023-03-14 11:02:05 -04:00
Xuehai Pan
78c2b0ac47 ci: add dependabot integration for GitHub Actions (#3504) 2023-03-14 10:31:26 -04:00
Micha Reiser
d5700d7c69 Add Micro Benchmark (#3466) 2023-03-14 08:35:07 +01:00
Samuel Cormier-Iijima
3a7bdb39c9 Fix base ref determination for artifact download in ecosystem CI check (#3499) 2023-03-13 22:37:12 -04:00
Grzegorz Bokota
a82fe4a139 Fix lack of not in PLC1901 error message (#3497) 2023-03-13 19:19:41 -04:00
Charlie Marsh
62ff3b62e3 Add requires-python inference to docs (#3495) 2023-03-13 18:14:39 -04:00
Charlie Marsh
1e5db58b7b Include individual path checks in --verbose logging (#3489) 2023-03-13 17:13:47 -04:00
Charlie Marsh
a6e998d639 Remove Wasm-specific Rayon workarounds (#3490) 2023-03-13 16:48:43 -04:00
Charlie Marsh
a8c1915e2e Remove erroneous C4-to-C40 redirect (#3488) 2023-03-13 19:52:05 +00:00
Xuehai Pan
c515a1b31a PYI011: allow math constants in defaults (#3484) 2023-03-13 14:23:00 -04:00
222 changed files with 5249 additions and 2430 deletions

View File

@@ -1,5 +1,6 @@
[alias]
dev = "run --package ruff_dev --bin ruff_dev"
benchmark = "bench -p ruff_benchmark --"
[target.'cfg(all())']
rustflags = [

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "12:00"
timezone: "America/New_York"
commit-message:
prefix: "ci(deps)"

133
.github/workflows/benchmark.yaml vendored Normal file
View File

@@ -0,0 +1,133 @@
name: Benchmark
on:
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
jobs:
run-benchmark:
if: github.event_name == 'pull_request'
name: "Run | ${{ matrix.os }}"
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: "PR - Checkout Branch"
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: "PR - Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- name: "PR - Build benchmarks"
uses: actions-rs/cargo@v1
with:
command: bench
args: -p ruff_benchmark --no-run
- name: "PR - Run benchmarks"
run: cargo benchmark --save-baseline=pr
- name: "Main - Checkout Branch"
uses: actions/checkout@v3
with:
clean: false
ref: main
- name: "Main - Install Rust toolchain"
run: rustup show
- name: "Main - Build benchmarks"
uses: actions-rs/cargo@v1
with:
command: bench
args: -p ruff_benchmark --no-run
- name: "Main - Run benchmarks"
run: cargo benchmark --save-baseline=main
- name: "Upload benchmark results"
uses: actions/upload-artifact@v3
with:
name: benchmark-results-${{ matrix.os }}
path: ./target/criterion
# Cleanup
- name: Remove Criterion Artifact
uses: JesseTG/rm@v1.0.3
with:
path: ./target/criterion
benchmark-compare:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
name: Compare
needs:
- run-benchmark
steps:
- name: "Install Rust toolchain"
run: rustup show
- name: "Install critcmp"
# Use debug build: Building takes much longer than the "slowness" of using the debug build.
run: cargo install --debug critcmp
- name: "Linux | Download PR benchmark results"
uses: actions/download-artifact@v3
with:
name: benchmark-results-ubuntu-latest
path: ./target/criterion
- name: "Linux | Compare benchmark results"
shell: bash
run: |
echo "### Benchmark" >> summary.md
echo "#### Linux" >> summary.md
echo "\`\`\`" >> summary.md
critcmp main pr >> summary.md
echo "\`\`\`" >> summary.md
echo "" >> summary.md
- name: "Linux | Cleanup benchmark results"
run: rm -rf ./target/criterion
- name: "Windows | Download PR benchmark results"
uses: actions/download-artifact@v3
with:
name: benchmark-results-windows-latest
path: ./target/criterion
- name: "Windows | Compare benchmark results"
shell: bash
run: |
echo "#### Windows" >> summary.md
echo "\`\`\`" >> summary.md
critcmp main pr >> summary.md
echo "\`\`\`" >> summary.md
echo "" >> summary.md
echo ${{ github.event.pull_request.number }} > pr-number
cat summary.md > $GITHUB_STEP_SUMMARY
- uses: actions/upload-artifact@v3
name: Upload PR Number
with:
name: pr-number
path: pr-number
- uses: actions/upload-artifact@v3
name: Upload Summary
with:
name: summary
path: summary.md

View File

@@ -34,7 +34,7 @@ jobs:
- name: "Install Rust toolchain"
run: |
rustup component add clippy
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo-clippy-wasm:
@@ -46,7 +46,7 @@ jobs:
run: |
rustup component add clippy
rustup target add wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- run: cargo clippy -p ruff_wasm --target wasm32-unknown-unknown --all-features -- -D warnings
cargo-test:
@@ -59,9 +59,9 @@ jobs:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- run: cargo install cargo-insta
- run: pip install black[d]==22.12.0
- run: pip install black[d]==23.1.0
- name: "Run tests (Ubuntu)"
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
@@ -80,6 +80,7 @@ jobs:
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
RUSTDOCFLAGS: "-D warnings"
- uses: actions/upload-artifact@v3
if: ${{ matrix.os == 'ubuntu-latest' }}
with:
name: ruff
path: target/debug/ruff
@@ -98,7 +99,7 @@ jobs:
cache: "npm"
cache-dependency-path: playground/package-lock.json
- uses: jetli/wasm-pack-action@v0.4.0
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- name: "Run wasm-pack"
run: |
cd crates/ruff_wasm
@@ -111,7 +112,7 @@ jobs:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- run: ./scripts/add_rule.py --name DoTheThing --code PLC999 --linter pylint
- run: cargo check
- run: |
@@ -139,27 +140,39 @@ jobs:
- uses: actions/setup-python@v4
with:
python-version: "3.11"
- uses: actions/download-artifact@v3
name: Download Ruff binary
id: ruff-target
with:
name: ruff
path: target/debug
- uses: dawidd6/action-download-artifact@v2
name: Download base results
with:
name: ruff
branch: ${{ github.event.pull_request.base_ref }}
branch: ${{ github.event.pull_request.base.ref }}
check_artifacts: true
- name: Run ecosystem check
run: |
# Make executable, since artifact download doesn't preserve this
chmod +x ruff ${{ steps.ruff-target.outputs.download-path }}/ruff
scripts/check_ecosystem.py ruff ${{ steps.ruff-target.outputs.download-path }}/ruff | tee ecosystem-result
cat ecosystem-result > $GITHUB_STEP_SUMMARY
echo ${{ github.event.number }} > pr-number
- uses: actions/upload-artifact@v3
name: Upload PR Number
with:
name: pr-number
path: pr-number
- uses: actions/upload-artifact@v3
name: Upload Results
with:
name: ecosystem-result
path: |
ecosystem-result
pr-number
path: ecosystem-result

View File

@@ -15,7 +15,7 @@ jobs:
- uses: actions/setup-python@v4
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v1
- uses: Swatinem/rust-cache@v2
- name: "Install dependencies"
run: |
pip install -r docs/requirements.txt

View File

@@ -1,31 +0,0 @@
on:
workflow_run:
workflows: [CI]
types: [completed]
permissions:
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dawidd6/action-download-artifact@v2
id: download-result
with:
name: ecosystem-result
workflow: ci.yaml
run_id: ${{ github.event.workflow_run.id }}
if_no_artifact_found: ignore
- if: steps.download-result.outputs.found_artifact
id: result
run: |
echo "pr-number=$(<pr-number)" >> $GITHUB_OUTPUT
- name: Create comment
if: steps.download-result.outputs.found_artifact
uses: thollander/actions-comment-pull-request@v2
with:
pr_number: ${{ steps.result.outputs.pr-number }}
filePath: ecosystem-result
comment_tag: ecosystem-results

View File

@@ -133,7 +133,7 @@ jobs:
target: ${{ matrix.target }}
manylinux: auto
args: --no-default-features --release --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
- uses: uraimo/run-on-arch-action@v2.5.0
- uses: uraimo/run-on-arch-action@v2
if: matrix.target != 'ppc64'
name: Install built wheel
with:
@@ -206,7 +206,7 @@ jobs:
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2
args: --release --out dist -m ./${{ env.CRATE_NAME }}/Cargo.toml
- uses: uraimo/run-on-arch-action@master
- uses: uraimo/run-on-arch-action@v2
name: Install built wheel
with:
arch: ${{ matrix.platform.arch }}

83
.github/workflows/pr-comment.yaml vendored Normal file
View File

@@ -0,0 +1,83 @@
name: PR Check Comment
on:
workflow_run:
workflows: [CI, Benchmark]
types: [completed]
workflow_dispatch:
inputs:
workflow_run_id:
description: The ecosystem workflow that triggers the workflow run
required: true
permissions:
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- uses: dawidd6/action-download-artifact@v2
name: Download PR Number
with:
name: pr-number
run_id: ${{ github.event.workflow_run.id || github.event.inputs.workflow_run_id }}
if_no_artifact_found: ignore
- name: Extract PR Number
id: pr-number
run: |
if [[ -f pr-number ]]
then
echo "pr-number=$(<pr-number)" >> $GITHUB_OUTPUT
fi
- uses: dawidd6/action-download-artifact@v2
name: "Download Ecosystem Result"
id: download-ecosystem-result
if: steps.pr-number.outputs.pr-number
with:
name: ecosystem-result
workflow: ci.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/ecosystem
if_no_artifact_found: ignore
- uses: dawidd6/action-download-artifact@v2
name: "Download Benchmark Result"
id: download-benchmark-result
if: steps.pr-number.outputs.pr-number
with:
name: summary
workflow: benchmark.yaml
pr: ${{ steps.pr-number.outputs.pr-number }}
path: pr/benchmark
if_no_artifact_found: ignore
- name: Generate Comment
id: generate-comment
if: steps.download-ecosystem-result.outputs.found_artifact == 'true' || steps.download-benchmark-result.outputs.found_artifact == 'true'
run: |
echo 'comment<<EOF' >> $GITHUB_OUTPUT
echo '## PR Check Results' >> $GITHUB_OUTPUT
if [[ -f pr/ecosystem/ecosystem-result ]]
then
echo "### Ecosystem" >> $GITHUB_OUTPUT
cat pr/ecosystem/ecosystem-result >> $GITHUB_OUTPUT
fi
if [[ -f pr/benchmark/summary.md ]]
then
cat pr/benchmark/summary.md >> $GITHUB_OUTPUT
fi
echo 'EOF' >> $GITHUB_OUTPUT
- name: Create or update comment
if: steps.generate-comment.outputs.comment
uses: thollander/actions-comment-pull-request@v2
with:
pr_number: ${{ steps.pr-number.outputs.pr-number }}
message: ${{ steps.generate-comment.outputs.comment }}
comment_tag: PR Check Results

View File

@@ -208,7 +208,7 @@ jobs:
target: ${{ matrix.platform.target }}
manylinux: auto
args: --release --out dist
- uses: uraimo/run-on-arch-action@v2.5.0
- uses: uraimo/run-on-arch-action@v2
if: matrix.platform.arch != 'ppc64'
name: Install built wheel
with:
@@ -309,7 +309,7 @@ jobs:
target: ${{ matrix.platform.target }}
manylinux: musllinux_1_2
args: --release --out dist
- uses: uraimo/run-on-arch-action@master
- uses: uraimo/run-on-arch-action@v2
name: Install built wheel
with:
arch: ${{ matrix.platform.arch }}

View File

@@ -1,7 +1,7 @@
fail_fast: true
repos:
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.10.1
rev: v0.12.1
hooks:
- id: validate-pyproject

36
Cargo.lock generated
View File

@@ -313,13 +313,14 @@ dependencies = [
[[package]]
name = "clap_complete_command"
version = "0.4.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4160b4a4f72ef58bd766bad27c09e6ef1cc9d82a22f6a0f55d152985a4a48e31"
checksum = "183495371ea78d4c9ff638bfc6497d46fed2396e4f9c50aebc1278a4a9919a3d"
dependencies = [
"clap 4.1.8",
"clap_complete",
"clap_complete_fig",
"clap_complete_nushell",
]
[[package]]
@@ -332,6 +333,16 @@ dependencies = [
"clap_complete",
]
[[package]]
name = "clap_complete_nushell"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fa41f5e6aa83bd151b70fd0ceaee703d68cd669522795dc812df9edad1252c"
dependencies = [
"clap 4.1.8",
"clap_complete",
]
[[package]]
name = "clap_derive"
version = "4.1.8"
@@ -769,7 +780,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.255"
version = "0.0.257"
dependencies = [
"anyhow",
"clap 4.1.8",
@@ -1971,7 +1982,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.255"
version = "0.0.257"
dependencies = [
"anyhow",
"bisection",
@@ -2024,6 +2035,21 @@ dependencies = [
"toml",
]
[[package]]
name = "ruff_benchmark"
version = "0.0.0"
dependencies = [
"criterion",
"mimalloc",
"once_cell",
"ruff",
"serde",
"serde_json",
"tikv-jemallocator",
"ureq",
"url",
]
[[package]]
name = "ruff_cache"
version = "0.0.0"
@@ -2037,7 +2063,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.255"
version = "0.0.257"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",

View File

@@ -4,6 +4,10 @@ members = ["crates/*"]
[workspace.package]
edition = "2021"
rust-version = "1.67"
homepage = "https://beta.ruff.rs/docs/"
documentation = "https://beta.ruff.rs/docs/"
repository = "https://github.com/charliermarsh/ruff"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
[workspace.dependencies]
anyhow = { version = "1.0.69" }
@@ -59,3 +63,9 @@ opt-level = 3
# https://github.com/bytecodealliance/wasm-tools/blob/b5c3d98e40590512a3b12470ef358d5c7b983b15/crates/wasmparser/src/limits.rs#L29
[profile.dev.package.rustpython-parser]
opt-level = 1
# Use the `--profile release-debug` flag to show symbols in release mode.
# e.g. `cargo build --profile release-debug`
[profile.release-debug]
inherits = "release"
debug = 1

View File

@@ -137,7 +137,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.255'
rev: 'v0.0.257'
hooks:
- id: ruff
```
@@ -309,6 +309,10 @@ Ruff is used in a number of major open-source projects, including:
- [Starlite](https://github.com/starlite-api/starlite)
- [telemetry-airflow (Mozilla)](https://github.com/mozilla/telemetry-airflow)
- [Stable Baselines3](https://github.com/DLR-RM/stable-baselines3)
- [PaddlePaddle](https://github.com/PaddlePaddle/Paddle)
- [nox](https://github.com/wntrblm/nox)
- [Neon](https://github.com/neondatabase/neon)
- [The Algorithms](https://github.com/TheAlgorithms/Python)
## License

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.255"
version = "0.0.257"
edition = { workspace = true }
rust-version = { workspace = true }

View File

@@ -1,12 +1,12 @@
[package]
name = "ruff"
version = "0.0.255"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = { workspace = true }
rust-version = { workspace = true }
documentation = "https://github.com/charliermarsh/ruff"
homepage = "https://github.com/charliermarsh/ruff"
repository = "https://github.com/charliermarsh/ruff"
version = "0.0.257"
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
readme = "README.md"
license = "MIT"
@@ -25,7 +25,7 @@ anyhow = { workspace = true }
bisection = { version = "0.1.0" }
bitflags = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true, features = ["derive", "env", "string"] }
clap = { workspace = true, features = ["derive", "string"], optional = true }
colored = { workspace = true }
dirs = { version = "4.0.0" }
fern = { version = "0.6.1" }
@@ -72,7 +72,6 @@ insta = { workspace = true, features = ["yaml", "redactions"] }
pretty_assertions = "1.3.0"
test-case = { workspace = true }
[features]
default = []
logical_lines = []

View File

@@ -0,0 +1,11 @@
import warnings
"""
Should emit:
B028 - on lines 8 and 9
"""
warnings.warn(DeprecationWarning("test"))
warnings.warn(DeprecationWarning("test"), source=None)
warnings.warn(DeprecationWarning("test"), source=None, stacklevel=2)
warnings.warn(DeprecationWarning("test"), stacklevel=1)

View File

@@ -1,3 +1,10 @@
import math
import os
import sys
from math import inf
import numpy as np
def f12(
x,
y: str = os.pathsep, # Error PYI011 Only simple default values allowed for typed arguments
@@ -61,3 +68,49 @@ def f22(
x: complex = -42.5j # Error PYI011 Only simple default values allowed for typed arguments
+ 4.3j,
) -> None: ...
def f23(
x: bool = True, # OK
) -> None: ...
def f24(
x: float = 3.14, # OK
) -> None: ...
def f25(
x: float = -3.14, # OK
) -> None: ...
def f26(
x: complex = -3.14j, # OK
) -> None: ...
def f27(
x: complex = -3 - 3.14j, # OK
) -> None: ...
def f28(
x: float = math.tau, # OK
) -> None: ...
def f29(
x: float = math.inf, # OK
) -> None: ...
def f30(
x: float = -math.inf, # OK
) -> None: ...
def f31(
x: float = inf, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f32(
x: float = np.inf, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f33(
x: float = math.nan, # OK
) -> None: ...
def f34(
x: float = -math.nan, # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...
def f35(
x: complex = math.inf # Error PYI011 Only simple default values allowed for typed arguments
+ 1j,
) -> None: ...
def f36(
*, x: str = sys.version, # OK
) -> None: ...
def f37(
*, x: str = "" + "", # Error PYI011 Only simple default values allowed for typed arguments
) -> None: ...

View File

@@ -1,5 +1,10 @@
import sys, math
from os import path, uname
from json import detect_encoding
from json import dump
from json import dumps as json_dumps
from json import load
from json import loads as json_loads
from logging.handlers import StreamHandler, FileHandler
# comment 1
@@ -10,9 +15,10 @@ from third_party import lib4
from foo import bar # comment 3
from foo2 import bar2 # comment 4
from foo3 import bar3, baz3 # comment 5
# comment 5
# comment 6
from bar import (
a, # comment 6
b, # comment 7
)
a, # comment 7
b, # comment 8
)

View File

@@ -0,0 +1,10 @@
# ruff: isort: skip_file
import e
import f
# isort: split
import a
import b
import c
import d

View File

@@ -6,6 +6,14 @@ def f():
# isort: on
def f():
# ruff: isort: off
import sys
import os
import collections
# ruff: isort: on
def f():
import sys
import os # isort: skip

View File

@@ -54,3 +54,7 @@ if type(a) != type(b) or type(a) == type(ccc):
pass
assert type(res) == type(None)
types = StrEnum
if x == types.X:
pass

View File

@@ -0,0 +1,3 @@
def lorem():
"""lorem ipsum dolor sit amet consectetur adipiscing elit
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"""

View File

@@ -58,6 +58,15 @@ def no_underline_and_no_description(): # noqa: D416
"""
@expect(_D213)
@expect("D407: Missing dashed underline after section ('Returns')")
@expect("D414: Section has no content ('Returns')")
def no_underline_and_no_newline(): # noqa: D416
"""Toggle the gizmo.
Returns"""
@expect(_D213)
@expect("D410: Missing blank line after section ('Returns')")
@expect("D414: Section has no content ('Returns')")

View File

@@ -17,3 +17,7 @@ def errors():
def ok():
if x and not y:
print("x is not an empty string, but y is an empty string")
data.loc[data["a"] != ""]
data.loc[data["a"] != "", :]

View File

@@ -0,0 +1,95 @@
while True:
try:
pass
finally:
continue # [continue-in-finally]
while True:
try:
pass
except Exception:
continue
finally:
try:
pass
finally:
continue # [continue-in-finally]
pass
while True:
try:
pass
finally:
test = "aa"
match test:
case "aa":
continue # [continue-in-finally]
while True:
try:
pass
finally:
with "aa" as f:
continue # [continue-in-finally]
while True:
try:
pass
finally:
if True:
continue # [continue-in-finally]
continue # [continue-in-finally]
def test():
while True:
continue
try:
pass
finally:
continue # [continue-in-finally]
while True:
try:
pass
finally:
continue # [continue-in-finally]
def test():
while True:
continue
while True:
try:
pass
finally:
for i in range(12):
continue
continue # [continue-in-finally]
while True:
pass
else:
continue # [continue-in-finally]
def test():
continue
while True:
continue
while True:
try:
pass
finally:
if True:
pass
elif False:
continue # [continue-in-finally]
else:
continue # [continue-in-finally]
for i in range(10):
pass
else:
continue # [continue-in-finally]

Binary file not shown.

View File

@@ -5,4 +5,8 @@ goodVar = os.getenv("TESTING", None)
dictVarBad = os.getenv("AAA", {"a", 7}) # [invalid-envvar-default]
print(os.getenv("TEST", False)) # [invalid-envvar-default]
os.getenv("AA", "GOOD")
os.getenv("AA", f"GOOD")
os.getenv("AA", "GOOD" + "BAD")
os.getenv("AA", "GOOD" + 1)
os.getenv("AA", "GOOD %s" % "BAD")
os.getenv("B", Z)

View File

@@ -7,6 +7,9 @@ os.getenv(key="testingAgain")
os.getenv(key=11) # [invalid-envvar-value]
os.getenv(["hello"]) # [invalid-envvar-value]
os.getenv(key="foo", default="bar")
os.getenv(key=f"foo", default="bar")
os.getenv(key="foo" + "bar", default=1)
os.getenv(key=1 + "bar", default=1) # [invalid-envvar-value]
AA = "aa"
os.getenv(AA)

View File

@@ -0,0 +1,50 @@
import sys
def print_python_version():
print(sys.version)
return None # [useless-return]
def print_python_version():
print(sys.version)
return None # [useless-return]
def print_python_version():
print(sys.version)
return None # [useless-return]
class SomeClass:
def print_python_version(self):
print(sys.version)
return None # [useless-return]
def print_python_version():
if 2 * 2 == 4:
return
print(sys.version)
def print_python_version():
if 2 * 2 == 4:
return None
return
def print_python_version():
if 2 * 2 == 4:
return None
def print_python_version():
"""This function returns None."""
return None
def print_python_version():
"""This function returns None."""
print(sys.version)
return None # [useless-return]

View File

@@ -1,24 +1,29 @@
# These should be changed
# Error (`from unittest import mock`)
if True:
import mock
# Error (`from unittest import mock`)
if True:
import mock, sys
# This goes to from unitest import mock
# Error (`from unittest.mock import *`)
if True:
from mock import *
# Error (`from unittest import mock`)
import mock.mock
# Mock should go on a new line as `from unittest import mock`
# Error (`from unittest import mock`)
import contextlib, mock, sys
# Mock should go on a new line as `from unittest import mock`
# Error (`from unittest import mock`)
import mock, sys
x = "This code should be preserved one line below the mock"
# Mock should go on a new line as `from unittest import mock`
# Error (`from unittest import mock`)
from mock import mock
# Should keep trailing comma
# Error (keep trailing comma)
from mock import (
mock,
a,
@@ -32,7 +37,7 @@ from mock import (
mock,
)
# Should not get a trailing comma
# Error (avoid trailing comma)
from mock import (
mock,
a,
@@ -57,16 +62,16 @@ if True:
c
)
# These should not change:
# OK
import os, io
# Mock should go on a new line as `from unittest import mock`
# Error (`from unittest import mock`)
import mock, mock
# Mock should go on a new line as `from unittest import mock as foo`
# Error (`from unittest import mock as foo`)
import mock as foo
# Mock should go on a new line as `from unittest import mock as foo`
# Error (`from unittest import mock as foo`)
from mock import mock as foo
if True:
@@ -81,8 +86,8 @@ if True:
from mock import mock as foo, mock as bar, mock
# This should be unchanged.
# OK.
x = mock.Mock()
# This should change to `mock.Mock`().
# Error (`mock.Mock()`).
x = mock.mock.Mock()

View File

@@ -41,7 +41,7 @@ if True:
Good,
)
from typing import Callable, Match, Pattern, List, OrderedDict, AbstractSet
from typing import Callable, Match, Pattern, List, OrderedDict, AbstractSet, ContextManager
if True: from collections import (
Mapping, Counter)

View File

@@ -5,3 +5,5 @@ isinstance(1, int) # OK
issubclass("yes", int) # OK
isinstance(1, int | float) # OK
issubclass("yes", int | str) # OK
isinstance(1, ()) # OK
isinstance(1, (int, *(str, bytes))) # OK

View File

@@ -0,0 +1,19 @@
input = [1, 2, 3]
otherInput = [2, 3, 4]
# OK
zip(input, otherInput) # different inputs
zip(input, otherInput[1:]) # different inputs
zip(input, input[2:]) # not successive
zip(input[:-1], input[2:]) # not successive
list(zip(input, otherInput)) # nested call
zip(input, input[1::2]) # not successive
# Errors
zip(input, input[1:])
zip(input, input[1::1])
zip(input[:-1], input[1:])
zip(input[1:], input[2:])
zip(input[1:-1], input[2:])
list(zip(input, input[1:]))
list(zip(input[:-1], input[1:]))

View File

@@ -9,30 +9,30 @@ Running from the repo root should pick up and enforce the appropriate settings f
```console
∴ cargo run -p ruff_cli -- check crates/ruff/resources/test/project/
crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
crates/ruff/resources/test/project/examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
crates/ruff/resources/test/project/examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
crates/ruff/resources/test/project/project/file.py:1:8: F401 `os` imported but unused
crates/ruff/resources/test/project/project/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:1: I001 [*] Import block is un-sorted or un-formatted
crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:8: F401 [*] `numpy` imported but unused
crates/ruff/resources/test/project/examples/.dotfiles/script.py:2:17: F401 [*] `app.app_file` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
crates/ruff/resources/test/project/examples/docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
crates/ruff/resources/test/project/project/file.py:1:8: F401 [*] `os` imported but unused
crates/ruff/resources/test/project/project/import_file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
Found 7 errors.
7 potentially fixable with the --fix option.
[*] 7 potentially fixable with the --fix option.
```
Running from the project directory itself should exhibit the same behavior:
```console
∴ (cd crates/ruff/resources/test/project/ && cargo run -p ruff_cli -- check .)
examples/.dotfiles/script.py:1:1: I001 Import block is un-sorted or un-formatted
examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
examples/docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
project/file.py:1:8: F401 `os` imported but unused
project/import_file.py:1:1: I001 Import block is un-sorted or un-formatted
examples/.dotfiles/script.py:1:1: I001 [*] Import block is un-sorted or un-formatted
examples/.dotfiles/script.py:1:8: F401 [*] `numpy` imported but unused
examples/.dotfiles/script.py:2:17: F401 [*] `app.app_file` imported but unused
examples/docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
examples/docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
project/file.py:1:8: F401 [*] `os` imported but unused
project/import_file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
Found 7 errors.
7 potentially fixable with the --fix option.
[*] 7 potentially fixable with the --fix option.
```
Running from the sub-package directory should exhibit the same behavior, but omit the top-level
@@ -40,10 +40,10 @@ files:
```console
∴ (cd crates/ruff/resources/test/project/examples/docs && cargo run -p ruff_cli -- check .)
docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
Found 2 errors.
2 potentially fixable with the --fix option.
[*] 2 potentially fixable with the --fix option.
```
`--config` should force Ruff to use the specified `pyproject.toml` for all files, and resolve
@@ -51,17 +51,17 @@ file paths from the current working directory:
```console
∴ (cargo run -p ruff_cli -- check --config=crates/ruff/resources/test/project/pyproject.toml crates/ruff/resources/test/project/)
crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:8: F401 `numpy` imported but unused
crates/ruff/resources/test/project/examples/.dotfiles/script.py:2:17: F401 `app.app_file` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/concepts/file.py:1:8: F401 `os` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
crates/ruff/resources/test/project/examples/docs/docs/file.py:1:8: F401 `os` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:3:8: F401 `numpy` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:4:27: F401 `docs.concepts.file` imported but unused
crates/ruff/resources/test/project/examples/excluded/script.py:1:8: F401 `os` imported but unused
crates/ruff/resources/test/project/project/file.py:1:8: F401 `os` imported but unused
crates/ruff/resources/test/project/examples/.dotfiles/script.py:1:8: F401 [*] `numpy` imported but unused
crates/ruff/resources/test/project/examples/.dotfiles/script.py:2:17: F401 [*] `app.app_file` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/concepts/file.py:1:8: F401 [*] `os` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
crates/ruff/resources/test/project/examples/docs/docs/file.py:1:8: F401 [*] `os` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:3:8: F401 [*] `numpy` imported but unused
crates/ruff/resources/test/project/examples/docs/docs/file.py:4:27: F401 [*] `docs.concepts.file` imported but unused
crates/ruff/resources/test/project/examples/excluded/script.py:1:8: F401 [*] `os` imported but unused
crates/ruff/resources/test/project/project/file.py:1:8: F401 [*] `os` imported but unused
Found 9 errors.
9 potentially fixable with the --fix option.
[*] 9 potentially fixable with the --fix option.
```
Running from a parent directory should "ignore" the `exclude` (hence, `concepts/file.py` gets
@@ -69,21 +69,21 @@ included in the output):
```console
∴ (cd crates/ruff/resources/test/project/examples && cargo run -p ruff_cli -- check --config=docs/ruff.toml .)
docs/docs/concepts/file.py:5:5: F841 Local variable `x` is assigned to but never used
docs/docs/file.py:1:1: I001 Import block is un-sorted or un-formatted
docs/docs/file.py:8:5: F841 Local variable `x` is assigned to but never used
excluded/script.py:5:5: F841 Local variable `x` is assigned to but never used
docs/docs/concepts/file.py:5:5: F841 [*] Local variable `x` is assigned to but never used
docs/docs/file.py:1:1: I001 [*] Import block is un-sorted or un-formatted
docs/docs/file.py:8:5: F841 [*] Local variable `x` is assigned to but never used
excluded/script.py:5:5: F841 [*] Local variable `x` is assigned to but never used
Found 4 errors.
4 potentially fixable with the --fix option.
[*] 4 potentially fixable with the --fix option.
```
Passing an excluded directory directly should report errors in the contained files:
```console
∴ cargo run -p ruff_cli -- check crates/ruff/resources/test/project/examples/excluded/
crates/ruff/resources/test/project/examples/excluded/script.py:1:8: F401 `os` imported but unused
crates/ruff/resources/test/project/examples/excluded/script.py:1:8: F401 [*] `os` imported but unused
Found 1 error.
1 potentially fixable with the --fix option.
[*] 1 potentially fixable with the --fix option.
```
Unless we `--force-exclude`:

View File

@@ -9,7 +9,7 @@ use ruff_python_ast::source_code::Locator;
use ruff_python_ast::types::Range;
use crate::linter::FixTable;
use crate::registry::AsRule;
use crate::registry::{AsRule, Rule};
pub mod helpers;
@@ -39,7 +39,7 @@ fn apply_fixes<'a>(
.as_ref()
.map(|fix| (diagnostic.kind.rule(), fix))
})
.sorted_by_key(|(.., fix)| fix.location)
.sorted_by(|(rule1, fix1), (rule2, fix2)| cmp_fix(*rule1, *rule2, fix1, fix2))
{
// If we already applied an identical fix as part of another correction, skip
// any re-application.
@@ -92,6 +92,18 @@ pub(crate) fn apply_fix(fix: &Fix, locator: &Locator) -> String {
output
}
/// Compare two fixes.
fn cmp_fix(rule1: Rule, rule2: Rule, fix1: &Fix, fix2: &Fix) -> std::cmp::Ordering {
fix1.location
.cmp(&fix2.location)
.then_with(|| match (&rule1, &rule2) {
// Apply `EndsInPeriod` fixes before `NewLineAfterLastParagraph` fixes.
(Rule::EndsInPeriod, Rule::NewLineAfterLastParagraph) => std::cmp::Ordering::Less,
(Rule::NewLineAfterLastParagraph, Rule::EndsInPeriod) => std::cmp::Ordering::Greater,
_ => std::cmp::Ordering::Equal,
})
}
#[cfg(test)]
mod tests {
use rustpython_parser::ast::Location;

View File

@@ -1,3 +1,4 @@
use ruff_python_ast::scope::ScopeStack;
use rustpython_parser::ast::{Expr, Stmt};
use ruff_python_ast::types::Range;
@@ -7,7 +8,7 @@ use ruff_python_ast::visibility::{Visibility, VisibleScope};
use crate::checkers::ast::AnnotationContext;
use crate::docstrings::definition::Definition;
type Context<'a> = (Vec<usize>, Vec<RefEquality<'a, Stmt>>);
type Context<'a> = (ScopeStack, Vec<RefEquality<'a, Stmt>>);
/// A collection of AST nodes that are deferred for later analysis.
/// Used to, e.g., store functions, whose bodies shouldn't be analyzed until all

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,7 @@ pub fn check_file_path(
let mut diagnostics: Vec<Diagnostic> = vec![];
// flake8-no-pep420
if settings.rules.enabled(&Rule::ImplicitNamespacePackage) {
if settings.rules.enabled(Rule::ImplicitNamespacePackage) {
if let Some(diagnostic) =
implicit_namespace_package(path, package, &settings.project_root, &settings.src)
{
@@ -24,7 +24,7 @@ pub fn check_file_path(
}
// pep8-naming
if settings.rules.enabled(&Rule::InvalidModuleName) {
if settings.rules.enabled(Rule::InvalidModuleName) {
if let Some(diagnostic) = invalid_module_name(path, package) {
diagnostics.push(diagnostic);
}

View File

@@ -38,7 +38,7 @@ pub fn check_imports(
// Enforce import rules.
let mut diagnostics = vec![];
if settings.rules.enabled(&Rule::UnsortedImports) {
if settings.rules.enabled(Rule::UnsortedImports) {
for block in &blocks {
if !block.imports.is_empty() {
if let Some(diagnostic) = isort::rules::organize_imports(
@@ -49,7 +49,7 @@ pub fn check_imports(
}
}
}
if settings.rules.enabled(&Rule::MissingRequiredImport) {
if settings.rules.enabled(Rule::MissingRequiredImport) {
diagnostics.extend(isort::rules::add_required_imports(
&blocks, python_ast, locator, stylist, settings, autofix,
));

View File

@@ -166,7 +166,7 @@ pub fn check_logical_lines(
}
#[cfg(feature = "logical_lines")]
let should_fix = autofix.into() && settings.rules.should_fix(&Rule::MissingWhitespace);
let should_fix = autofix.into() && settings.rules.should_fix(Rule::MissingWhitespace);
#[cfg(not(feature = "logical_lines"))]
let should_fix = false;
@@ -181,7 +181,7 @@ pub fn check_logical_lines(
if line.flags.contains(TokenFlags::BRACKET) {
#[cfg(feature = "logical_lines")]
let should_fix =
autofix.into() && settings.rules.should_fix(&Rule::WhitespaceBeforeParameters);
autofix.into() && settings.rules.should_fix(Rule::WhitespaceBeforeParameters);
#[cfg(not(feature = "logical_lines"))]
let should_fix = false;

View File

@@ -24,7 +24,7 @@ pub fn check_noqa(
settings: &Settings,
autofix: flags::Autofix,
) -> Vec<usize> {
let enforce_noqa = settings.rules.enabled(&Rule::UnusedNOQA);
let enforce_noqa = settings.rules.enabled(Rule::UnusedNOQA);
// Whether the file is exempted from all checks.
let mut file_exempted = false;
@@ -188,7 +188,7 @@ pub fn check_noqa(
valid_codes.push(code);
} else {
if let Ok(rule) = Rule::from_code(code) {
if settings.rules.enabled(&rule) {
if settings.rules.enabled(rule) {
unmatched_codes.push(code);
} else {
disabled_codes.push(code);

View File

@@ -32,28 +32,28 @@ pub fn check_physical_lines(
let mut diagnostics: Vec<Diagnostic> = vec![];
let mut has_any_shebang = false;
let enforce_blanket_noqa = settings.rules.enabled(&Rule::BlanketNOQA);
let enforce_shebang_not_executable = settings.rules.enabled(&Rule::ShebangNotExecutable);
let enforce_shebang_missing = settings.rules.enabled(&Rule::ShebangMissingExecutableFile);
let enforce_shebang_whitespace = settings.rules.enabled(&Rule::ShebangWhitespace);
let enforce_shebang_newline = settings.rules.enabled(&Rule::ShebangNewline);
let enforce_shebang_python = settings.rules.enabled(&Rule::ShebangPython);
let enforce_blanket_type_ignore = settings.rules.enabled(&Rule::BlanketTypeIgnore);
let enforce_doc_line_too_long = settings.rules.enabled(&Rule::DocLineTooLong);
let enforce_line_too_long = settings.rules.enabled(&Rule::LineTooLong);
let enforce_no_newline_at_end_of_file = settings.rules.enabled(&Rule::NoNewLineAtEndOfFile);
let enforce_unnecessary_coding_comment = settings.rules.enabled(&Rule::UTF8EncodingDeclaration);
let enforce_mixed_spaces_and_tabs = settings.rules.enabled(&Rule::MixedSpacesAndTabs);
let enforce_bidirectional_unicode = settings.rules.enabled(&Rule::BidirectionalUnicode);
let enforce_trailing_whitespace = settings.rules.enabled(&Rule::TrailingWhitespace);
let enforce_blanket_noqa = settings.rules.enabled(Rule::BlanketNOQA);
let enforce_shebang_not_executable = settings.rules.enabled(Rule::ShebangNotExecutable);
let enforce_shebang_missing = settings.rules.enabled(Rule::ShebangMissingExecutableFile);
let enforce_shebang_whitespace = settings.rules.enabled(Rule::ShebangWhitespace);
let enforce_shebang_newline = settings.rules.enabled(Rule::ShebangNewline);
let enforce_shebang_python = settings.rules.enabled(Rule::ShebangPython);
let enforce_blanket_type_ignore = settings.rules.enabled(Rule::BlanketTypeIgnore);
let enforce_doc_line_too_long = settings.rules.enabled(Rule::DocLineTooLong);
let enforce_line_too_long = settings.rules.enabled(Rule::LineTooLong);
let enforce_no_newline_at_end_of_file = settings.rules.enabled(Rule::NoNewLineAtEndOfFile);
let enforce_unnecessary_coding_comment = settings.rules.enabled(Rule::UTF8EncodingDeclaration);
let enforce_mixed_spaces_and_tabs = settings.rules.enabled(Rule::MixedSpacesAndTabs);
let enforce_bidirectional_unicode = settings.rules.enabled(Rule::BidirectionalUnicode);
let enforce_trailing_whitespace = settings.rules.enabled(Rule::TrailingWhitespace);
let enforce_blank_line_contains_whitespace =
settings.rules.enabled(&Rule::BlankLineContainsWhitespace);
let enforce_indentation_contains_tabs = settings.rules.enabled(&Rule::IndentationContainsTabs);
settings.rules.enabled(Rule::BlankLineContainsWhitespace);
let enforce_indentation_contains_tabs = settings.rules.enabled(Rule::IndentationContainsTabs);
let fix_unnecessary_coding_comment =
autofix.into() && settings.rules.should_fix(&Rule::UTF8EncodingDeclaration);
autofix.into() && settings.rules.should_fix(Rule::UTF8EncodingDeclaration);
let fix_shebang_whitespace =
autofix.into() && settings.rules.should_fix(&Rule::ShebangWhitespace);
autofix.into() && settings.rules.should_fix(Rule::ShebangWhitespace);
let mut commented_lines_iter = commented_lines.iter().peekable();
let mut doc_lines_iter = doc_lines.iter().peekable();
@@ -165,7 +165,7 @@ pub fn check_physical_lines(
if let Some(diagnostic) = no_newline_at_end_of_file(
locator,
stylist,
autofix.into() && settings.rules.should_fix(&Rule::NoNewLineAtEndOfFile),
autofix.into() && settings.rules.should_fix(Rule::NoNewLineAtEndOfFile),
) {
diagnostics.push(diagnostic);
}

View File

@@ -8,7 +8,7 @@ use crate::registry::{AsRule, Rule};
use crate::rules::ruff::rules::Context;
use crate::rules::{
eradicate, flake8_commas, flake8_implicit_str_concat, flake8_pyi, flake8_quotes, pycodestyle,
pyupgrade, ruff,
pylint, pyupgrade, ruff,
};
use crate::settings::{flags, Settings};
use ruff_diagnostics::Diagnostic;
@@ -23,41 +23,43 @@ pub fn check_tokens(
) -> Vec<Diagnostic> {
let mut diagnostics: Vec<Diagnostic> = vec![];
let enforce_ambiguous_unicode_character = settings
.rules
.enabled(&Rule::AmbiguousUnicodeCharacterString)
|| settings
.rules
.enabled(&Rule::AmbiguousUnicodeCharacterDocstring)
|| settings
.rules
.enabled(&Rule::AmbiguousUnicodeCharacterComment);
let enforce_quotes = settings.rules.enabled(&Rule::BadQuotesInlineString)
|| settings.rules.enabled(&Rule::BadQuotesMultilineString)
|| settings.rules.enabled(&Rule::BadQuotesDocstring)
|| settings.rules.enabled(&Rule::AvoidableEscapedQuote);
let enforce_commented_out_code = settings.rules.enabled(&Rule::CommentedOutCode);
let enforce_compound_statements = settings
.rules
.enabled(&Rule::MultipleStatementsOnOneLineColon)
|| settings
.rules
.enabled(&Rule::MultipleStatementsOnOneLineSemicolon)
|| settings.rules.enabled(&Rule::UselessSemicolon);
let enforce_invalid_escape_sequence = settings.rules.enabled(&Rule::InvalidEscapeSequence);
let enforce_implicit_string_concatenation = settings
.rules
.enabled(&Rule::SingleLineImplicitStringConcatenation)
|| settings
.rules
.enabled(&Rule::MultiLineImplicitStringConcatenation);
let enforce_trailing_comma = settings.rules.enabled(&Rule::TrailingCommaMissing)
|| settings
.rules
.enabled(&Rule::TrailingCommaOnBareTupleProhibited)
|| settings.rules.enabled(&Rule::TrailingCommaProhibited);
let enforce_extraneous_parenthesis = settings.rules.enabled(&Rule::ExtraneousParentheses);
let enforce_type_comment_in_stub = settings.rules.enabled(&Rule::TypeCommentInStub);
let enforce_ambiguous_unicode_character = settings.rules.any_enabled(&[
Rule::AmbiguousUnicodeCharacterString,
Rule::AmbiguousUnicodeCharacterDocstring,
Rule::AmbiguousUnicodeCharacterComment,
]);
let enforce_invalid_string_character = settings.rules.any_enabled(&[
Rule::InvalidCharacterBackspace,
Rule::InvalidCharacterSub,
Rule::InvalidCharacterEsc,
Rule::InvalidCharacterNul,
Rule::InvalidCharacterZeroWidthSpace,
]);
let enforce_quotes = settings.rules.any_enabled(&[
Rule::BadQuotesInlineString,
Rule::BadQuotesMultilineString,
Rule::BadQuotesDocstring,
Rule::AvoidableEscapedQuote,
]);
let enforce_commented_out_code = settings.rules.enabled(Rule::CommentedOutCode);
let enforce_compound_statements = settings.rules.any_enabled(&[
Rule::MultipleStatementsOnOneLineColon,
Rule::MultipleStatementsOnOneLineSemicolon,
Rule::UselessSemicolon,
]);
let enforce_invalid_escape_sequence = settings.rules.enabled(Rule::InvalidEscapeSequence);
let enforce_implicit_string_concatenation = settings.rules.any_enabled(&[
Rule::SingleLineImplicitStringConcatenation,
Rule::MultiLineImplicitStringConcatenation,
]);
let enforce_trailing_comma = settings.rules.any_enabled(&[
Rule::TrailingCommaMissing,
Rule::TrailingCommaOnBareTupleProhibited,
Rule::TrailingCommaProhibited,
]);
let enforce_extraneous_parenthesis = settings.rules.enabled(Rule::ExtraneousParentheses);
let enforce_type_comment_in_stub = settings.rules.enabled(Rule::TypeCommentInStub);
// RUF001, RUF002, RUF003
if enforce_ambiguous_unicode_character {
@@ -111,11 +113,23 @@ pub fn check_tokens(
locator,
*start,
*end,
autofix.into() && settings.rules.should_fix(&Rule::InvalidEscapeSequence),
autofix.into() && settings.rules.should_fix(Rule::InvalidEscapeSequence),
));
}
}
}
// PLE2510, PLE2512, PLE2513
if enforce_invalid_string_character {
for (start, tok, end) in tokens.iter().flatten() {
if matches!(tok, Tok::String { .. }) {
diagnostics.extend(
pylint::rules::invalid_string_characters(locator, *start, *end, autofix.into())
.into_iter()
.filter(|diagnostic| settings.rules.enabled(diagnostic.kind.rule())),
);
}
}
}
// E701, E702, E703
if enforce_compound_statements {

View File

@@ -166,11 +166,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pylint, "C3002") => Rule::UnnecessaryDirectLambdaCall,
(Pylint, "E0100") => Rule::YieldInInit,
(Pylint, "E0101") => Rule::ReturnInInit,
(Pylint, "E0116") => Rule::ContinueInFinally,
(Pylint, "E0117") => Rule::NonlocalWithoutBinding,
(Pylint, "E0118") => Rule::UsedPriorGlobalDeclaration,
(Pylint, "E0604") => Rule::InvalidAllObject,
(Pylint, "E0605") => Rule::InvalidAllFormat,
(Pylint, "W1508") => Rule::InvalidEnvvarDefault,
(Pylint, "E1142") => Rule::AwaitOutsideAsync,
(Pylint, "E1205") => Rule::LoggingTooManyArgs,
(Pylint, "E1206") => Rule::LoggingTooFewArgs,
@@ -178,6 +178,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pylint, "E1310") => Rule::BadStrStripCall,
(Pylint, "E1507") => Rule::InvalidEnvvarValue,
(Pylint, "E2502") => Rule::BidirectionalUnicode,
(Pylint, "E2510") => Rule::InvalidCharacterBackspace,
(Pylint, "E2512") => Rule::InvalidCharacterSub,
(Pylint, "E2513") => Rule::InvalidCharacterEsc,
(Pylint, "E2514") => Rule::InvalidCharacterNul,
(Pylint, "E2515") => Rule::InvalidCharacterZeroWidthSpace,
(Pylint, "R0133") => Rule::ComparisonOfConstant,
(Pylint, "R0206") => Rule::PropertyWithParameters,
(Pylint, "R0402") => Rule::ConsiderUsingFromImport,
@@ -186,12 +191,14 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pylint, "R0913") => Rule::TooManyArguments,
(Pylint, "R0915") => Rule::TooManyStatements,
(Pylint, "R1701") => Rule::ConsiderMergingIsinstance,
(Pylint, "R1711") => Rule::UselessReturn,
(Pylint, "R1722") => Rule::ConsiderUsingSysExit,
(Pylint, "R2004") => Rule::MagicValueComparison,
(Pylint, "R5501") => Rule::CollapsibleElseIf,
(Pylint, "W0120") => Rule::UselessElseOnLoop,
(Pylint, "W0602") => Rule::GlobalVariableNotAssigned,
(Pylint, "W0603") => Rule::GlobalStatement,
(Pylint, "W1508") => Rule::InvalidEnvvarDefault,
(Pylint, "W2901") => Rule::RedefinedLoopName,
// flake8-builtins
@@ -226,6 +233,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Bugbear, "025") => Rule::DuplicateTryBlockException,
(Flake8Bugbear, "026") => Rule::StarArgUnpackingAfterKeywordArg,
(Flake8Bugbear, "027") => Rule::EmptyMethodWithoutAbstractDecorator,
(Flake8Bugbear, "028") => Rule::NoExplicitStacklevel,
(Flake8Bugbear, "029") => Rule::ExceptWithEmptyTuple,
(Flake8Bugbear, "030") => Rule::ExceptWithNonExceptionClasses,
(Flake8Bugbear, "032") => Rule::UnintentionalTypeAnnotation,
@@ -660,6 +668,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Ruff, "003") => Rule::AmbiguousUnicodeCharacterComment,
(Ruff, "005") => Rule::UnpackInsteadOfConcatenatingToCollectionLiteral,
(Ruff, "006") => Rule::AsyncioDanglingTask,
(Ruff, "007") => Rule::PairwiseOverZipped,
(Ruff, "100") => Rule::UnusedNOQA,
// flake8-django

View File

@@ -107,15 +107,21 @@ pub fn extract_isort_directives(lxr: &[LexResult]) -> IsortDirectives {
// omit a space after the colon. The remaining action comments are
// required to include the space, and must appear on their own lines.
let comment_text = comment_text.trim_end();
if comment_text == "# isort: split" {
if matches!(comment_text, "# isort: split" | "# ruff: isort: split") {
splits.push(start.row());
} else if comment_text == "# isort: skip_file" || comment_text == "# isort:skip_file" {
} else if matches!(
comment_text,
"# isort: skip_file"
| "# isort:skip_file"
| "# ruff: isort: skip_file"
| "# ruff: isort:skip_file"
) {
return IsortDirectives {
skip_file: true,
..IsortDirectives::default()
};
} else if off.is_some() {
if comment_text == "# isort: on" {
if comment_text == "# isort: on" || comment_text == "# ruff: isort: on" {
if let Some(start) = off {
for row in start.row() + 1..=end.row() {
exclusions.insert(row);
@@ -126,7 +132,7 @@ pub fn extract_isort_directives(lxr: &[LexResult]) -> IsortDirectives {
} else {
if comment_text.contains("isort: skip") || comment_text.contains("isort:skip") {
exclusions.insert(start.row());
} else if comment_text == "# isort: off" {
} else if comment_text == "# isort: off" || comment_text == "# ruff: isort: off" {
off = Some(start);
}
}

View File

@@ -22,11 +22,12 @@ pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
}
/// Create a set with codes matching the pattern/code pairs.
pub(crate) fn ignores_from_path<'a>(
pub(crate) fn ignores_from_path(
path: &Path,
pattern_code_pairs: &'a [(GlobMatcher, GlobMatcher, FxHashSet<Rule>)],
) -> FxHashSet<&'a Rule> {
pattern_code_pairs: &[(GlobMatcher, GlobMatcher, FxHashSet<Rule>)],
) -> FxHashSet<Rule> {
let (file_path, file_basename) = extract_path_names(path).expect("Unable to parse filename");
pattern_code_pairs
.iter()
.filter_map(|(absolute, basename, codes)| {
@@ -37,20 +38,21 @@ pub(crate) fn ignores_from_path<'a>(
basename.glob().regex(),
codes
);
return Some(codes.iter());
}
if absolute.is_match(file_path) {
Some(codes)
} else if absolute.is_match(file_path) {
debug!(
"Adding per-file ignores for {:?} due to absolute match on {:?}: {:?}",
path,
absolute.glob().regex(),
codes
);
return Some(codes.iter());
Some(codes)
} else {
None
}
None
})
.flatten()
.copied()
.collect()
}

View File

@@ -50,7 +50,7 @@ impl<T> LinterResult<T> {
}
}
pub type FixTable = FxHashMap<&'static Rule, usize>;
pub type FixTable = FxHashMap<Rule, usize>;
/// Generate `Diagnostic`s from the source code contents at the
/// given `Path`.
@@ -74,7 +74,7 @@ pub fn check_path(
// Collect doc lines. This requires a rare mix of tokens (for comments) and AST
// (for docstrings), which demands special-casing at this level.
let use_doc_lines = settings.rules.enabled(&Rule::DocLineTooLong);
let use_doc_lines = settings.rules.enabled(Rule::DocLineTooLong);
let mut doc_lines = vec![];
if use_doc_lines {
doc_lines.extend(doc_lines_from_tokens(&tokens));
@@ -159,14 +159,14 @@ pub fn check_path(
}
}
Err(parse_error) => {
if settings.rules.enabled(&Rule::SyntaxError) {
if settings.rules.enabled(Rule::SyntaxError) {
pycodestyle::rules::syntax_error(&mut diagnostics, &parse_error);
}
// If the syntax error is ignored, suppress it (regardless of whether
// `Rule::SyntaxError` is enabled).
if !rule_is_ignored(
&Rule::SyntaxError,
Rule::SyntaxError,
parse_error.location.row(),
&directives.noqa_line_for,
locator,
@@ -204,7 +204,7 @@ pub fn check_path(
if !diagnostics.is_empty() && !settings.per_file_ignores.is_empty() {
let ignores = fs::ignores_from_path(path, &settings.per_file_ignores);
if !ignores.is_empty() {
diagnostics.retain(|diagnostic| !ignores.contains(diagnostic.kind.rule()));
diagnostics.retain(|diagnostic| !ignores.contains(&diagnostic.kind.rule()));
}
};

View File

@@ -1,8 +1,31 @@
use std::sync::Mutex;
use anyhow::Result;
use colored::Colorize;
use fern;
use log::Level;
use once_cell::sync::Lazy;
pub(crate) static WARNINGS: Lazy<Mutex<Vec<&'static str>>> = Lazy::new(Mutex::default);
/// Warn a user once, with uniqueness determined by the given ID.
#[macro_export]
macro_rules! warn_user_once_by_id {
($id:expr, $($arg:tt)*) => {
use colored::Colorize;
use log::warn;
if let Ok(mut states) = $crate::logging::WARNINGS.lock() {
if !states.contains(&$id) {
let message = format!("{}", format_args!($($arg)*));
warn!("{}", message.bold());
states.push($id);
}
}
};
}
/// Warn a user once, with uniqueness determined by the calling location itself.
#[macro_export]
macro_rules! warn_user_once {
($($arg:tt)*) => {

View File

@@ -121,7 +121,7 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
/// Returns `true` if the string list of `codes` includes `code` (or an alias
/// thereof).
pub fn includes(needle: &Rule, haystack: &[&str]) -> bool {
pub fn includes(needle: Rule, haystack: &[&str]) -> bool {
let needle = needle.noqa_code();
haystack
.iter()
@@ -130,7 +130,7 @@ pub fn includes(needle: &Rule, haystack: &[&str]) -> bool {
/// Returns `true` if the given [`Rule`] is ignored at the specified `lineno`.
pub fn rule_is_ignored(
code: &Rule,
code: Rule,
lineno: usize,
noqa_line_for: &IntMap<usize, usize>,
locator: &Locator,
@@ -174,7 +174,7 @@ fn add_noqa_inner(
line_ending: &LineEnding,
) -> (usize, String) {
// Map of line number to set of (non-ignored) diagnostic codes that are triggered on that line.
let mut matches_by_line: FxHashMap<usize, FxHashSet<&Rule>> = FxHashMap::default();
let mut matches_by_line: FxHashMap<usize, FxHashSet<Rule>> = FxHashMap::default();
// Whether the file is exempted from all checks.
let mut file_exempted = false;
@@ -280,7 +280,7 @@ fn add_noqa_inner(
output.push_str(" # noqa: ");
// Add codes.
push_codes(&mut output, rules.iter().map(|r| r.noqa_code()));
push_codes(&mut output, rules.iter().map(Rule::noqa_code));
output.push_str(line_ending);
count += 1;
}

View File

@@ -143,6 +143,7 @@ ruff_macros::register_rules!(
rules::pyflakes::rules::UnusedAnnotation,
rules::pyflakes::rules::RaiseNotImplemented,
// pylint
rules::pylint::rules::UselessReturn,
rules::pylint::rules::YieldInInit,
rules::pylint::rules::InvalidAllObject,
rules::pylint::rules::InvalidAllFormat,
@@ -150,8 +151,14 @@ ruff_macros::register_rules!(
rules::pylint::rules::InvalidEnvvarValue,
rules::pylint::rules::BadStringFormatType,
rules::pylint::rules::BidirectionalUnicode,
rules::pylint::rules::InvalidCharacterBackspace,
rules::pylint::rules::InvalidCharacterSub,
rules::pylint::rules::InvalidCharacterEsc,
rules::pylint::rules::InvalidCharacterNul,
rules::pylint::rules::InvalidCharacterZeroWidthSpace,
rules::pylint::rules::BadStrStripCall,
rules::pylint::rules::CollapsibleElseIf,
rules::pylint::rules::ContinueInFinally,
rules::pylint::rules::UselessImportAlias,
rules::pylint::rules::UnnecessaryDirectLambdaCall,
rules::pylint::rules::NonlocalWithoutBinding,
@@ -185,6 +192,7 @@ ruff_macros::register_rules!(
rules::flake8_bugbear::rules::UnreliableCallableCheck,
rules::flake8_bugbear::rules::StripWithMultiCharacters,
rules::flake8_bugbear::rules::MutableArgumentDefault,
rules::flake8_bugbear::rules::NoExplicitStacklevel,
rules::flake8_bugbear::rules::UnusedLoopControlVariable,
rules::flake8_bugbear::rules::FunctionCallArgumentDefault,
rules::flake8_bugbear::rules::GetAttrWithConstant,
@@ -601,6 +609,7 @@ ruff_macros::register_rules!(
rules::ruff::rules::UnpackInsteadOfConcatenatingToCollectionLiteral,
rules::ruff::rules::AsyncioDanglingTask,
rules::ruff::rules::UnusedNOQA,
rules::ruff::rules::PairwiseOverZipped,
// flake8-django
rules::flake8_django::rules::NullableModelStringField,
rules::flake8_django::rules::LocalsInRenderFunction,
@@ -610,6 +619,10 @@ ruff_macros::register_rules!(
rules::flake8_django::rules::NonLeadingReceiverDecorator,
);
pub trait AsRule {
fn rule(&self) -> Rule;
}
impl Rule {
pub fn from_code(code: &str) -> Result<Self, FromCodeError> {
let (linter, code) = Linter::parse_code(code).ok_or(FromCodeError::Unknown)?;
@@ -848,6 +861,11 @@ impl Rule {
| Rule::BadQuotesMultilineString
| Rule::CommentedOutCode
| Rule::MultiLineImplicitStringConcatenation
| Rule::InvalidCharacterBackspace
| Rule::InvalidCharacterSub
| Rule::InvalidCharacterEsc
| Rule::InvalidCharacterNul
| Rule::InvalidCharacterZeroWidthSpace
| Rule::ExtraneousParentheses
| Rule::InvalidEscapeSequence
| Rule::SingleLineImplicitStringConcatenation
@@ -916,6 +934,7 @@ pub const INCOMPATIBLE_CODES: &[(Rule, Rule, &str); 2] = &[
#[cfg(test)]
mod tests {
use std::mem::size_of;
use strum::IntoEnumIterator;
use super::{Linter, Rule, RuleNamespace};
@@ -961,4 +980,9 @@ mod tests {
assert_eq!(code, format!("{}{rest}", linter.common_prefix()));
}
}
#[test]
fn rule_size() {
assert_eq!(2, size_of::<Rule>());
}
}

View File

@@ -128,7 +128,7 @@ pub fn resolve_configuration(
// Resolve the current path.
let options = pyproject::load_options(&path)
.map_err(|err| anyhow!("Failed to parse `{}`: {}", path.to_string_lossy(), err))?;
.map_err(|err| anyhow!("Failed to parse `{}`: {}", path.display(), err))?;
let project_root = relativity.resolve(&path);
let configuration = Configuration::from_options(options, &project_root)?;

View File

@@ -18,7 +18,6 @@ static REDIRECTS: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
// The following are here because we don't yet have the many-to-one mapping enabled.
("SIM111", "SIM110"),
// The following are deprecated.
("C4", "C40"),
("C9", "C90"),
("T1", "T10"),
("T2", "T20"),

View File

@@ -277,6 +277,7 @@ pub(crate) enum Specificity {
Code5Chars,
}
#[cfg(feature = "clap")]
mod clap_completion {
use clap::builder::{PossibleValue, TypedValueParser, ValueParserFactory};
use strum::IntoEnumIterator;
@@ -316,9 +317,7 @@ mod clap_completion {
.map_err(|e| clap::Error::raw(clap::error::ErrorKind::InvalidValue, e))
}
fn possible_values(
&self,
) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
fn possible_values(&self) -> Option<Box<dyn Iterator<Item = PossibleValue> + '_>> {
Some(Box::new(
std::iter::once(PossibleValue::new("ALL").help("all rules")).chain(
Linter::iter()

View File

@@ -61,7 +61,7 @@ pub fn commented_out_code(
// Verify that the comment is on its own line, and that it contains code.
if is_standalone_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
let mut diagnostic = Diagnostic::new(CommentedOutCode, Range::new(start, end));
if autofix.into() && settings.rules.should_fix(&Rule::CommentedOutCode) {
if autofix.into() && settings.rules.should_fix(Rule::CommentedOutCode) {
diagnostic.amend(Fix::deletion(location, end_location));
}
Some(diagnostic)

View File

@@ -140,7 +140,7 @@ pub fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
&& checker
.settings
.rules
.enabled(&Rule::SysVersionSlice1Referenced)
.enabled(Rule::SysVersionSlice1Referenced)
{
checker.diagnostics.push(Diagnostic::new(
SysVersionSlice1Referenced,
@@ -150,7 +150,7 @@ pub fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
&& checker
.settings
.rules
.enabled(&Rule::SysVersionSlice3Referenced)
.enabled(Rule::SysVersionSlice3Referenced)
{
checker.diagnostics.push(Diagnostic::new(
SysVersionSlice3Referenced,
@@ -165,13 +165,13 @@ pub fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
..
} => {
if *i == BigInt::from(2)
&& checker.settings.rules.enabled(&Rule::SysVersion2Referenced)
&& checker.settings.rules.enabled(Rule::SysVersion2Referenced)
{
checker
.diagnostics
.push(Diagnostic::new(SysVersion2Referenced, Range::from(value)));
} else if *i == BigInt::from(0)
&& checker.settings.rules.enabled(&Rule::SysVersion0Referenced)
&& checker.settings.rules.enabled(Rule::SysVersion0Referenced)
{
checker
.diagnostics
@@ -210,7 +210,7 @@ pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &
&& checker
.settings
.rules
.enabled(&Rule::SysVersionInfo0Eq3Referenced)
.enabled(Rule::SysVersionInfo0Eq3Referenced)
{
checker.diagnostics.push(Diagnostic::new(
SysVersionInfo0Eq3Referenced,
@@ -231,7 +231,7 @@ pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &
}],
) = (ops, comparators)
{
if checker.settings.rules.enabled(&Rule::SysVersionInfo1CmpInt) {
if checker.settings.rules.enabled(Rule::SysVersionInfo1CmpInt) {
checker
.diagnostics
.push(Diagnostic::new(SysVersionInfo1CmpInt, Range::from(left)));
@@ -259,7 +259,7 @@ pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &
if checker
.settings
.rules
.enabled(&Rule::SysVersionInfoMinorCmpInt)
.enabled(Rule::SysVersionInfoMinorCmpInt)
{
checker.diagnostics.push(Diagnostic::new(
SysVersionInfoMinorCmpInt,
@@ -286,12 +286,12 @@ pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &
) = (ops, comparators)
{
if s.len() == 1 {
if checker.settings.rules.enabled(&Rule::SysVersionCmpStr10) {
if checker.settings.rules.enabled(Rule::SysVersionCmpStr10) {
checker
.diagnostics
.push(Diagnostic::new(SysVersionCmpStr10, Range::from(left)));
}
} else if checker.settings.rules.enabled(&Rule::SysVersionCmpStr3) {
} else if checker.settings.rules.enabled(Rule::SysVersionCmpStr3) {
checker
.diagnostics
.push(Diagnostic::new(SysVersionCmpStr3, Range::from(left)));

View File

@@ -492,7 +492,7 @@ pub fn definition(
// ANN401 for dynamically typed arguments
if let Some(annotation) = &arg.node.annotation {
has_any_typed_arg = true;
if checker.settings.rules.enabled(&Rule::AnyType) {
if checker.settings.rules.enabled(Rule::AnyType) {
check_dynamically_typed(
checker,
annotation,
@@ -507,7 +507,7 @@ pub fn definition(
if checker
.settings
.rules
.enabled(&Rule::MissingTypeFunctionArgument)
.enabled(Rule::MissingTypeFunctionArgument)
{
diagnostics.push(Diagnostic::new(
MissingTypeFunctionArgument {
@@ -525,7 +525,7 @@ pub fn definition(
if let Some(expr) = &arg.node.annotation {
has_any_typed_arg = true;
if !checker.settings.flake8_annotations.allow_star_arg_any {
if checker.settings.rules.enabled(&Rule::AnyType) {
if checker.settings.rules.enabled(Rule::AnyType) {
let name = &arg.node.arg;
check_dynamically_typed(
checker,
@@ -539,7 +539,7 @@ pub fn definition(
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker.settings.rules.enabled(&Rule::MissingTypeArgs) {
if checker.settings.rules.enabled(Rule::MissingTypeArgs) {
diagnostics.push(Diagnostic::new(
MissingTypeArgs {
name: arg.node.arg.to_string(),
@@ -556,7 +556,7 @@ pub fn definition(
if let Some(expr) = &arg.node.annotation {
has_any_typed_arg = true;
if !checker.settings.flake8_annotations.allow_star_arg_any {
if checker.settings.rules.enabled(&Rule::AnyType) {
if checker.settings.rules.enabled(Rule::AnyType) {
let name = &arg.node.arg;
check_dynamically_typed(
checker,
@@ -570,7 +570,7 @@ pub fn definition(
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker.settings.rules.enabled(&Rule::MissingTypeKwargs) {
if checker.settings.rules.enabled(Rule::MissingTypeKwargs) {
diagnostics.push(Diagnostic::new(
MissingTypeKwargs {
name: arg.node.arg.to_string(),
@@ -587,7 +587,7 @@ pub fn definition(
if let Some(arg) = args.posonlyargs.first().or_else(|| args.args.first()) {
if arg.node.annotation.is_none() {
if visibility::is_classmethod(&checker.ctx, cast::decorator_list(stmt)) {
if checker.settings.rules.enabled(&Rule::MissingTypeCls) {
if checker.settings.rules.enabled(Rule::MissingTypeCls) {
diagnostics.push(Diagnostic::new(
MissingTypeCls {
name: arg.node.arg.to_string(),
@@ -596,7 +596,7 @@ pub fn definition(
));
}
} else {
if checker.settings.rules.enabled(&Rule::MissingTypeSelf) {
if checker.settings.rules.enabled(Rule::MissingTypeSelf) {
diagnostics.push(Diagnostic::new(
MissingTypeSelf {
name: arg.node.arg.to_string(),
@@ -614,7 +614,7 @@ pub fn definition(
// ANN201, ANN202, ANN401
if let Some(expr) = &returns {
has_typed_return = true;
if checker.settings.rules.enabled(&Rule::AnyType) {
if checker.settings.rules.enabled(Rule::AnyType) {
check_dynamically_typed(checker, expr, || name.to_string(), &mut diagnostics);
}
} else if !(
@@ -626,7 +626,7 @@ pub fn definition(
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypeClassMethod)
.enabled(Rule::MissingReturnTypeClassMethod)
{
diagnostics.push(Diagnostic::new(
MissingReturnTypeClassMethod {
@@ -641,7 +641,7 @@ pub fn definition(
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypeStaticMethod)
.enabled(Rule::MissingReturnTypeStaticMethod)
{
diagnostics.push(Diagnostic::new(
MissingReturnTypeStaticMethod {
@@ -656,7 +656,7 @@ pub fn definition(
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypeSpecialMethod)
.enabled(Rule::MissingReturnTypeSpecialMethod)
{
if !(checker.settings.flake8_annotations.mypy_init_return && has_any_typed_arg)
{
@@ -681,7 +681,7 @@ pub fn definition(
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypeSpecialMethod)
.enabled(Rule::MissingReturnTypeSpecialMethod)
{
diagnostics.push(Diagnostic::new(
MissingReturnTypeSpecialMethod {
@@ -696,7 +696,7 @@ pub fn definition(
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypePublicFunction)
.enabled(Rule::MissingReturnTypePublicFunction)
{
diagnostics.push(Diagnostic::new(
MissingReturnTypePublicFunction {
@@ -710,7 +710,7 @@ pub fn definition(
if checker
.settings
.rules
.enabled(&Rule::MissingReturnTypePrivateFunction)
.enabled(Rule::MissingReturnTypePrivateFunction)
{
diagnostics.push(Diagnostic::new(
MissingReturnTypePrivateFunction {

View File

@@ -41,6 +41,7 @@ mod tests {
#[test_case(Rule::StarArgUnpackingAfterKeywordArg, Path::new("B026.py"); "B026")]
#[test_case(Rule::EmptyMethodWithoutAbstractDecorator, Path::new("B027.py"); "B027")]
#[test_case(Rule::EmptyMethodWithoutAbstractDecorator, Path::new("B027.pyi"); "B027_pyi")]
#[test_case(Rule::NoExplicitStacklevel, Path::new("B028.py"); "B028")]
#[test_case(Rule::ExceptWithEmptyTuple, Path::new("B029.py"); "B029")]
#[test_case(Rule::ExceptWithNonExceptionClasses, Path::new("B030.py"); "B030")]
#[test_case(Rule::UnintentionalTypeAnnotation, Path::new("B032.py"); "B032")]

View File

@@ -115,7 +115,7 @@ pub fn abstract_base_class(
if !checker
.settings
.rules
.enabled(&Rule::EmptyMethodWithoutAbstractDecorator)
.enabled(Rule::EmptyMethodWithoutAbstractDecorator)
{
continue;
}
@@ -135,7 +135,7 @@ pub fn abstract_base_class(
if checker
.settings
.rules
.enabled(&Rule::AbstractBaseClassWithoutAbstractMethod)
.enabled(Rule::AbstractBaseClassWithoutAbstractMethod)
{
if !has_abstract_method {
checker.diagnostics.push(Diagnostic::new(

View File

@@ -2,7 +2,8 @@ use rustpython_parser::ast::{Expr, ExprKind};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{Range, ScopeKind};
use ruff_python_ast::scope::ScopeKind;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
@@ -30,7 +31,7 @@ fn is_cache_func(checker: &Checker, expr: &Expr) -> bool {
/// B019
pub fn cached_instance_method(checker: &mut Checker, decorator_list: &[Expr]) {
if !matches!(checker.ctx.current_scope().kind, ScopeKind::Class(_)) {
if !matches!(checker.ctx.scope().kind, ScopeKind::Class(_)) {
return;
}
for decorator in decorator_list {

View File

@@ -83,7 +83,7 @@ fn duplicate_handler_exceptions<'a>(
if checker
.settings
.rules
.enabled(&Rule::DuplicateHandlerException)
.enabled(Rule::DuplicateHandlerException)
{
// TODO(charlie): Handle "BaseException" and redundant exception aliases.
if !duplicates.is_empty() {
@@ -149,7 +149,7 @@ pub fn duplicate_exceptions(checker: &mut Checker, handlers: &[Excepthandler]) {
if checker
.settings
.rules
.enabled(&Rule::DuplicateTryBlockException)
.enabled(Rule::DuplicateTryBlockException)
{
for (name, exprs) in duplicates {
for expr in exprs {

View File

@@ -25,6 +25,7 @@ pub use loop_variable_overrides_iterator::{
loop_variable_overrides_iterator, LoopVariableOverridesIterator,
};
pub use mutable_argument_default::{mutable_argument_default, MutableArgumentDefault};
pub use no_explicit_stacklevel::{no_explicit_stacklevel, NoExplicitStacklevel};
pub use raise_without_from_inside_except::{
raise_without_from_inside_except, RaiseWithoutFromInsideExcept,
};
@@ -63,6 +64,7 @@ mod getattr_with_constant;
mod jump_statement_in_finally;
mod loop_variable_overrides_iterator;
mod mutable_argument_default;
mod no_explicit_stacklevel;
mod raise_without_from_inside_except;
mod redundant_tuple_in_exception_handler;
mod setattr_with_constant;

View File

@@ -0,0 +1,68 @@
use rustpython_parser::ast::{Expr, Keyword};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::SimpleCallArgs;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for `warnings.warn` calls without an explicit `stacklevel` keyword
/// argument.
///
/// ## Why is this bad?
/// The `warnings.warn` method uses a `stacklevel` of 1 by default, which
/// limits the rendered stack trace to that of the line on which the
/// `warn` method is called.
///
/// It's recommended to use a `stacklevel` of 2 or higher, give the caller
/// more context about the warning.
///
/// ## Example
/// ```python
/// warnings.warn("This is a warning")
/// ```
///
/// Use instead:
/// ```python
/// warnings.warn("This is a warning", stacklevel=2)
/// ```
#[violation]
pub struct NoExplicitStacklevel;
impl Violation for NoExplicitStacklevel {
#[derive_message_formats]
fn message(&self) -> String {
format!("No explicit `stacklevel` keyword argument found")
}
}
/// B028
pub fn no_explicit_stacklevel(
checker: &mut Checker,
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
) {
if !checker
.ctx
.resolve_call_path(func)
.map_or(false, |call_path| {
call_path.as_slice() == ["warnings", "warn"]
})
{
return;
}
if SimpleCallArgs::new(args, keywords)
.keyword_argument("stacklevel")
.is_some()
{
return;
}
checker
.diagnostics
.push(Diagnostic::new(NoExplicitStacklevel, Range::from(func)));
}

View File

@@ -163,9 +163,8 @@ pub fn unused_loop_control_variable(
if let Some(rename) = rename {
if certainty.into() && checker.patch(diagnostic.kind.rule()) {
// Find the `BindingKind::LoopVar` corresponding to the name.
let scope = checker.ctx.current_scope();
let scope = checker.ctx.scope();
let binding = scope
.bindings
.get(name)
.into_iter()
.chain(scope.rebounds.get(name).into_iter().flatten())

View File

@@ -0,0 +1,31 @@
---
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
expression: diagnostics
---
- kind:
name: NoExplicitStacklevel
body: "No explicit `stacklevel` keyword argument found"
suggestion: ~
fixable: false
location:
row: 8
column: 0
end_location:
row: 8
column: 13
fix: ~
parent: ~
- kind:
name: NoExplicitStacklevel
body: "No explicit `stacklevel` keyword argument found"
suggestion: ~
fixable: false
location:
row: 9
column: 0
end_location:
row: 9
column: 13
fix: ~
parent: ~

View File

@@ -260,7 +260,7 @@ pub fn trailing_commas(
end_location: comma.2,
},
);
if autofix.into() && settings.rules.should_fix(&Rule::TrailingCommaProhibited) {
if autofix.into() && settings.rules.should_fix(Rule::TrailingCommaProhibited) {
diagnostic.amend(Fix::deletion(comma.0, comma.2));
}
diagnostics.push(diagnostic);
@@ -304,7 +304,7 @@ pub fn trailing_commas(
end_location: missing_comma.2,
},
);
if autofix.into() && settings.rules.should_fix(&Rule::TrailingCommaMissing) {
if autofix.into() && settings.rules.should_fix(Rule::TrailingCommaMissing) {
// Create a replacement that includes the final bracket (or other token),
// rather than just inserting a comma at the end. This prevents the UP034 autofix
// removing any brackets in the same linter pass - doing both at the same time could

View File

@@ -162,7 +162,7 @@ pub fn string_in_exception(checker: &mut Checker, exc: &Expr) {
value: Constant::Str(string),
..
} => {
if checker.settings.rules.enabled(&Rule::RawStringInException) {
if checker.settings.rules.enabled(Rule::RawStringInException) {
if string.len() > checker.settings.flake8_errmsg.max_string_length {
checker
.diagnostics
@@ -172,7 +172,7 @@ pub fn string_in_exception(checker: &mut Checker, exc: &Expr) {
}
// Check for f-strings
ExprKind::JoinedStr { .. } => {
if checker.settings.rules.enabled(&Rule::FStringInException) {
if checker.settings.rules.enabled(Rule::FStringInException) {
checker
.diagnostics
.push(Diagnostic::new(FStringInException, Range::from(first)));
@@ -180,7 +180,7 @@ pub fn string_in_exception(checker: &mut Checker, exc: &Expr) {
}
// Check for .format() calls
ExprKind::Call { func, .. } => {
if checker.settings.rules.enabled(&Rule::DotFormatInException) {
if checker.settings.rules.enabled(Rule::DotFormatInException) {
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if attr == "format" && matches!(value.node, ExprKind::Constant { .. }) {
checker.diagnostics.push(Diagnostic::new(

View File

@@ -43,14 +43,14 @@ fn check_msg(checker: &mut Checker, msg: &Expr) {
// Check for string concatenation and percent format.
ExprKind::BinOp { op, .. } => match op {
Operator::Add => {
if checker.settings.rules.enabled(&Rule::LoggingStringConcat) {
if checker.settings.rules.enabled(Rule::LoggingStringConcat) {
checker
.diagnostics
.push(Diagnostic::new(LoggingStringConcat, Range::from(msg)));
}
}
Operator::Mod => {
if checker.settings.rules.enabled(&Rule::LoggingPercentFormat) {
if checker.settings.rules.enabled(Rule::LoggingPercentFormat) {
checker
.diagnostics
.push(Diagnostic::new(LoggingPercentFormat, Range::from(msg)));
@@ -60,7 +60,7 @@ fn check_msg(checker: &mut Checker, msg: &Expr) {
},
// Check for f-strings.
ExprKind::JoinedStr { .. } => {
if checker.settings.rules.enabled(&Rule::LoggingFString) {
if checker.settings.rules.enabled(Rule::LoggingFString) {
checker
.diagnostics
.push(Diagnostic::new(LoggingFString, Range::from(msg)));
@@ -68,7 +68,7 @@ fn check_msg(checker: &mut Checker, msg: &Expr) {
}
// Check for .format() calls.
ExprKind::Call { func, .. } => {
if checker.settings.rules.enabled(&Rule::LoggingStringFormat) {
if checker.settings.rules.enabled(Rule::LoggingStringFormat) {
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if attr == "format" && matches!(value.node, ExprKind::Constant { .. }) {
checker
@@ -151,7 +151,7 @@ pub fn logging_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords:
}
// G010
if checker.settings.rules.enabled(&Rule::LoggingWarn)
if checker.settings.rules.enabled(Rule::LoggingWarn)
&& matches!(logging_level, LoggingLevel::Warn)
{
let mut diagnostic = Diagnostic::new(LoggingWarn, level_call_range);
@@ -166,18 +166,18 @@ pub fn logging_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords:
}
// G101
if checker.settings.rules.enabled(&Rule::LoggingExtraAttrClash) {
if checker.settings.rules.enabled(Rule::LoggingExtraAttrClash) {
if let Some(extra) = find_keyword(keywords, "extra") {
check_log_record_attr_clash(checker, extra);
}
}
// G201, G202
if checker.settings.rules.enabled(&Rule::LoggingExcInfo)
if checker.settings.rules.enabled(Rule::LoggingExcInfo)
|| checker
.settings
.rules
.enabled(&Rule::LoggingRedundantExcInfo)
.enabled(Rule::LoggingRedundantExcInfo)
{
if !checker.ctx.in_exception_handler() {
return;
@@ -206,7 +206,7 @@ pub fn logging_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords:
match logging_level {
LoggingLevel::Error => {
if checker.settings.rules.enabled(&Rule::LoggingExcInfo) {
if checker.settings.rules.enabled(Rule::LoggingExcInfo) {
checker
.diagnostics
.push(Diagnostic::new(LoggingExcInfo, level_call_range));
@@ -216,7 +216,7 @@ pub fn logging_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords:
if checker
.settings
.rules
.enabled(&Rule::LoggingRedundantExcInfo)
.enabled(Rule::LoggingRedundantExcInfo)
{
checker.diagnostics.push(Diagnostic::new(
LoggingRedundantExcInfo,

View File

@@ -1,20 +1,25 @@
use rustpython_parser::ast::{Arguments, Constant, Expr, ExprKind, Operator, Unaryop};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
#[violation]
pub struct TypedArgumentSimpleDefaults;
/// PYI011
impl Violation for TypedArgumentSimpleDefaults {
impl AlwaysAutofixableViolation for TypedArgumentSimpleDefaults {
#[derive_message_formats]
fn message(&self) -> String {
format!("Only simple default values allowed for typed arguments")
}
fn autofix_title(&self) -> String {
"Replace default value by `...`".to_string()
}
}
#[violation]
@@ -28,6 +33,14 @@ impl Violation for ArgumentSimpleDefaults {
}
}
const ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS: &[&[&str]] = &[
&["math", "inf"],
&["math", "nan"],
&["math", "e"],
&["math", "pi"],
&["math", "tau"],
];
const ALLOWED_ATTRIBUTES_IN_DEFAULTS: &[&[&str]] = &[
&["sys", "stdin"],
&["sys", "stdout"],
@@ -99,6 +112,21 @@ fn is_valid_default_value_with_annotation(default: &Expr, checker: &Checker) ->
return checker.locator.slice(operand).len() <= 10;
}
}
// Ex) `-math.inf`, `-math.pi`, etc.
if let ExprKind::Attribute { .. } = &operand.node {
if checker
.ctx
.resolve_call_path(operand)
.map_or(false, |call_path| {
ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS.iter().any(|target| {
// reject `-math.nan`
call_path.as_slice() == *target && *target != ["math", "nan"]
})
})
{
return true;
}
}
}
ExprKind::BinOp {
left,
@@ -134,14 +162,15 @@ fn is_valid_default_value_with_annotation(default: &Expr, checker: &Checker) ->
}
}
}
// Ex) `sys.stdin`, etc.
// Ex) `math.inf`, `sys.stdin`, etc.
ExprKind::Attribute { .. } => {
if checker
.ctx
.resolve_call_path(default)
.map_or(false, |call_path| {
ALLOWED_ATTRIBUTES_IN_DEFAULTS
ALLOWED_MATH_ATTRIBUTES_IN_DEFAULTS
.iter()
.chain(ALLOWED_ATTRIBUTES_IN_DEFAULTS.iter())
.any(|target| call_path.as_slice() == *target)
})
{
@@ -164,10 +193,18 @@ pub fn typed_argument_simple_defaults(checker: &mut Checker, args: &Arguments) {
{
if arg.node.annotation.is_some() {
if !is_valid_default_value_with_annotation(default, checker) {
checker.diagnostics.push(Diagnostic::new(
TypedArgumentSimpleDefaults,
Range::from(default),
));
let mut diagnostic =
Diagnostic::new(TypedArgumentSimpleDefaults, Range::from(default));
if checker.patch(diagnostic.kind.rule()) {
diagnostic.amend(Fix::replacement(
"...".to_string(),
default.location,
default.end_location.unwrap(),
));
}
checker.diagnostics.push(diagnostic);
}
}
}
@@ -183,10 +220,18 @@ pub fn typed_argument_simple_defaults(checker: &mut Checker, args: &Arguments) {
{
if kwarg.node.annotation.is_some() {
if !is_valid_default_value_with_annotation(default, checker) {
checker.diagnostics.push(Diagnostic::new(
TypedArgumentSimpleDefaults,
Range::from(default),
));
let mut diagnostic =
Diagnostic::new(TypedArgumentSimpleDefaults, Range::from(default));
if checker.patch(diagnostic.kind.rule()) {
diagnostic.amend(Fix::replacement(
"...".to_string(),
default.location,
default.end_location.unwrap(),
));
}
checker.diagnostics.push(diagnostic);
}
}
}

View File

@@ -118,7 +118,7 @@ pub fn unrecognized_platform(
&& checker
.settings
.rules
.enabled(&Rule::UnrecognizedPlatformCheck)
.enabled(Rule::UnrecognizedPlatformCheck)
{
checker
.diagnostics
@@ -137,7 +137,7 @@ pub fn unrecognized_platform(
&& checker
.settings
.rules
.enabled(&Rule::UnrecognizedPlatformName)
.enabled(Rule::UnrecognizedPlatformName)
{
checker.diagnostics.push(Diagnostic::new(
UnrecognizedPlatformName {
@@ -151,7 +151,7 @@ pub fn unrecognized_platform(
if checker
.settings
.rules
.enabled(&Rule::UnrecognizedPlatformCheck)
.enabled(Rule::UnrecognizedPlatformCheck)
{
checker
.diagnostics

View File

@@ -5,144 +5,321 @@ expression: diagnostics
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
suggestion: "Replace default value by `...`"
fixable: true
location:
row: 3
row: 10
column: 13
end_location:
row: 3
row: 10
column: 23
fix: ~
fix:
content: "..."
location:
row: 10
column: 13
end_location:
row: 10
column: 23
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
suggestion: "Replace default value by `...`"
fixable: true
location:
row: 9
row: 16
column: 8
end_location:
row: 13
row: 20
column: 5
fix: ~
fix:
content: "..."
location:
row: 16
column: 8
end_location:
row: 20
column: 5
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
suggestion: "Replace default value by `...`"
fixable: true
location:
row: 18
row: 25
column: 8
end_location:
row: 22
row: 29
column: 5
fix: ~
fix:
content: "..."
location:
row: 25
column: 8
end_location:
row: 29
column: 5
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
suggestion: "Replace default value by `...`"
fixable: true
location:
row: 27
column: 8
end_location:
row: 31
column: 5
fix: ~
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
location:
row: 36
row: 34
column: 8
end_location:
row: 38
column: 5
fix: ~
fix:
content: "..."
location:
row: 34
column: 8
end_location:
row: 38
column: 5
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
suggestion: "Replace default value by `...`"
fixable: true
location:
row: 41
column: 13
row: 43
column: 8
end_location:
row: 42
column: 11
fix: ~
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
location:
row: 45
column: 13
end_location:
row: 46
column: 12
fix: ~
column: 5
fix:
content: "..."
location:
row: 43
column: 8
end_location:
row: 45
column: 5
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
suggestion: "Replace default value by `...`"
fixable: true
location:
row: 48
column: 13
end_location:
row: 49
column: 11
fix:
content: "..."
location:
row: 48
column: 13
end_location:
row: 49
column: 11
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: "Replace default value by `...`"
fixable: true
location:
row: 52
column: 13
end_location:
row: 53
column: 12
fix:
content: "..."
location:
row: 52
column: 13
end_location:
row: 53
column: 12
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: "Replace default value by `...`"
fixable: true
location:
row: 56
column: 16
end_location:
row: 50
row: 57
column: 7
fix: ~
fix:
content: "..."
location:
row: 56
column: 16
end_location:
row: 57
column: 7
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
suggestion: "Replace default value by `...`"
fixable: true
location:
row: 53
row: 60
column: 13
end_location:
row: 54
column: 7
fix: ~
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
location:
row: 57
column: 17
end_location:
row: 58
column: 8
fix: ~
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: ~
fixable: false
location:
row: 61
column: 7
fix:
content: "..."
location:
row: 60
column: 13
end_location:
row: 61
column: 7
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: "Replace default value by `...`"
fixable: true
location:
row: 64
column: 17
end_location:
row: 62
row: 65
column: 8
fix:
content: "..."
location:
row: 64
column: 17
end_location:
row: 65
column: 8
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: "Replace default value by `...`"
fixable: true
location:
row: 68
column: 17
end_location:
row: 69
column: 10
fix: ~
fix:
content: "..."
location:
row: 68
column: 17
end_location:
row: 69
column: 10
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: "Replace default value by `...`"
fixable: true
location:
row: 96
column: 15
end_location:
row: 96
column: 18
fix:
content: "..."
location:
row: 96
column: 15
end_location:
row: 96
column: 18
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: "Replace default value by `...`"
fixable: true
location:
row: 99
column: 15
end_location:
row: 99
column: 21
fix:
content: "..."
location:
row: 99
column: 15
end_location:
row: 99
column: 21
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: "Replace default value by `...`"
fixable: true
location:
row: 105
column: 15
end_location:
row: 105
column: 24
fix:
content: "..."
location:
row: 105
column: 15
end_location:
row: 105
column: 24
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: "Replace default value by `...`"
fixable: true
location:
row: 108
column: 17
end_location:
row: 109
column: 8
fix:
content: "..."
location:
row: 108
column: 17
end_location:
row: 109
column: 8
parent: ~
- kind:
name: TypedArgumentSimpleDefaults
body: Only simple default values allowed for typed arguments
suggestion: "Replace default value by `...`"
fixable: true
location:
row: 115
column: 16
end_location:
row: 115
column: 23
fix:
content: "..."
location:
row: 115
column: 16
end_location:
row: 115
column: 23
parent: ~

View File

@@ -277,7 +277,7 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &E
if checker
.settings
.rules
.enabled(&Rule::IncorrectFixtureParenthesesStyle)
.enabled(Rule::IncorrectFixtureParenthesesStyle)
&& !checker.settings.flake8_pytest_style.fixture_parentheses
&& args.is_empty()
&& keywords.is_empty()
@@ -287,7 +287,7 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &E
pytest_fixture_parentheses(checker, decorator, fix, "", "()");
}
if checker.settings.rules.enabled(&Rule::FixturePositionalArgs) && !args.is_empty() {
if checker.settings.rules.enabled(Rule::FixturePositionalArgs) && !args.is_empty() {
checker.diagnostics.push(Diagnostic::new(
FixturePositionalArgs {
function: func_name.to_string(),
@@ -299,7 +299,7 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &E
if checker
.settings
.rules
.enabled(&Rule::ExtraneousScopeFunction)
.enabled(Rule::ExtraneousScopeFunction)
{
let scope_keyword = keywords
.iter()
@@ -333,7 +333,7 @@ fn check_fixture_decorator(checker: &mut Checker, func_name: &str, decorator: &E
if checker
.settings
.rules
.enabled(&Rule::IncorrectFixtureParenthesesStyle)
.enabled(Rule::IncorrectFixtureParenthesesStyle)
&& checker.settings.flake8_pytest_style.fixture_parentheses
{
let fix = Fix::insertion("()".to_string(), decorator.end_location.unwrap());
@@ -354,7 +354,7 @@ fn check_fixture_returns(checker: &mut Checker, func: &Stmt, func_name: &str, bo
if checker
.settings
.rules
.enabled(&Rule::IncorrectFixtureNameUnderscore)
.enabled(Rule::IncorrectFixtureNameUnderscore)
&& visitor.has_return_with_value
&& func_name.starts_with('_')
{
@@ -367,7 +367,7 @@ fn check_fixture_returns(checker: &mut Checker, func: &Stmt, func_name: &str, bo
} else if checker
.settings
.rules
.enabled(&Rule::MissingFixtureNameUnderscore)
.enabled(Rule::MissingFixtureNameUnderscore)
&& !visitor.has_return_with_value
&& !visitor.has_yield_from
&& !func_name.starts_with('_')
@@ -380,7 +380,7 @@ fn check_fixture_returns(checker: &mut Checker, func: &Stmt, func_name: &str, bo
));
}
if checker.settings.rules.enabled(&Rule::UselessYieldFixture) {
if checker.settings.rules.enabled(Rule::UselessYieldFixture) {
if let Some(stmt) = body.last() {
if let StmtKind::Expr { value, .. } = &stmt.node {
if let ExprKind::Yield { .. } = value.node {
@@ -462,7 +462,7 @@ fn check_fixture_marks(checker: &mut Checker, decorators: &[Expr]) {
if checker
.settings
.rules
.enabled(&Rule::UnnecessaryAsyncioMarkOnFixture)
.enabled(Rule::UnnecessaryAsyncioMarkOnFixture)
{
if name == "asyncio" {
let mut diagnostic =
@@ -479,7 +479,7 @@ fn check_fixture_marks(checker: &mut Checker, decorators: &[Expr]) {
if checker
.settings
.rules
.enabled(&Rule::ErroneousUseFixturesOnFixture)
.enabled(Rule::ErroneousUseFixturesOnFixture)
{
if name == "usefixtures" {
let mut diagnostic =
@@ -508,20 +508,17 @@ pub fn fixture(
if checker
.settings
.rules
.enabled(&Rule::IncorrectFixtureParenthesesStyle)
|| checker.settings.rules.enabled(&Rule::FixturePositionalArgs)
.enabled(Rule::IncorrectFixtureParenthesesStyle)
|| checker.settings.rules.enabled(Rule::FixturePositionalArgs)
|| checker
.settings
.rules
.enabled(&Rule::ExtraneousScopeFunction)
.enabled(Rule::ExtraneousScopeFunction)
{
check_fixture_decorator(checker, func_name, decorator);
}
if checker
.settings
.rules
.enabled(&Rule::DeprecatedYieldFixture)
if checker.settings.rules.enabled(Rule::DeprecatedYieldFixture)
&& checker.settings.flake8_pytest_style.fixture_parentheses
{
check_fixture_decorator_name(checker, decorator);
@@ -530,12 +527,12 @@ pub fn fixture(
if (checker
.settings
.rules
.enabled(&Rule::MissingFixtureNameUnderscore)
.enabled(Rule::MissingFixtureNameUnderscore)
|| checker
.settings
.rules
.enabled(&Rule::IncorrectFixtureNameUnderscore)
|| checker.settings.rules.enabled(&Rule::UselessYieldFixture))
.enabled(Rule::IncorrectFixtureNameUnderscore)
|| checker.settings.rules.enabled(Rule::UselessYieldFixture))
&& !has_abstractmethod_decorator(decorators, checker)
{
check_fixture_returns(checker, func, func_name, body);
@@ -544,7 +541,7 @@ pub fn fixture(
if checker
.settings
.rules
.enabled(&Rule::FixtureFinalizerCallback)
.enabled(Rule::FixtureFinalizerCallback)
{
check_fixture_addfinalizer(checker, args, body);
}
@@ -552,11 +549,11 @@ pub fn fixture(
if checker
.settings
.rules
.enabled(&Rule::UnnecessaryAsyncioMarkOnFixture)
.enabled(Rule::UnnecessaryAsyncioMarkOnFixture)
|| checker
.settings
.rules
.enabled(&Rule::ErroneousUseFixturesOnFixture)
.enabled(Rule::ErroneousUseFixturesOnFixture)
{
check_fixture_marks(checker, decorators);
}
@@ -565,7 +562,7 @@ pub fn fixture(
if checker
.settings
.rules
.enabled(&Rule::FixtureParamWithoutValue)
.enabled(Rule::FixtureParamWithoutValue)
&& func_name.starts_with("test_")
{
check_test_function_args(checker, args);

View File

@@ -123,11 +123,11 @@ pub fn marks(checker: &mut Checker, decorators: &[Expr]) {
let enforce_parentheses = checker
.settings
.rules
.enabled(&Rule::IncorrectMarkParenthesesStyle);
.enabled(Rule::IncorrectMarkParenthesesStyle);
let enforce_useless_usefixtures = checker
.settings
.rules
.enabled(&Rule::UseFixturesWithoutParameters);
.enabled(Rule::UseFixturesWithoutParameters);
for mark in get_mark_decorators(decorators) {
if enforce_parentheses {

View File

@@ -380,7 +380,7 @@ pub fn parametrize(checker: &mut Checker, decorators: &[Expr]) {
if checker
.settings
.rules
.enabled(&Rule::ParametrizeNamesWrongType)
.enabled(Rule::ParametrizeNamesWrongType)
{
if let Some(names) = args.get(0) {
check_names(checker, names);
@@ -389,7 +389,7 @@ pub fn parametrize(checker: &mut Checker, decorators: &[Expr]) {
if checker
.settings
.rules
.enabled(&Rule::ParametrizeValuesWrongType)
.enabled(Rule::ParametrizeValuesWrongType)
{
if let Some(names) = args.get(0) {
if let Some(values) = args.get(1) {

View File

@@ -67,11 +67,7 @@ const fn is_non_trivial_with_body(body: &[Stmt]) -> bool {
pub fn raises_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords: &[Keyword]) {
if is_pytest_raises(checker, func) {
if checker
.settings
.rules
.enabled(&Rule::RaisesWithoutException)
{
if checker.settings.rules.enabled(Rule::RaisesWithoutException) {
if args.is_empty() && keywords.is_empty() {
checker
.diagnostics
@@ -79,7 +75,7 @@ pub fn raises_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords:
}
}
if checker.settings.rules.enabled(&Rule::RaisesTooBroad) {
if checker.settings.rules.enabled(Rule::RaisesTooBroad) {
let match_keyword = keywords
.iter()
.find(|kw| kw.node.arg == Some("match".to_string()));

View File

@@ -281,7 +281,7 @@ fn docstring(
},
Range::new(start, end),
);
if autofix.into() && settings.rules.should_fix(&Rule::BadQuotesDocstring) {
if autofix.into() && settings.rules.should_fix(Rule::BadQuotesDocstring) {
let quote_count = if trivia.is_multiline { 3 } else { 1 };
let string_contents = &trivia.raw_text[quote_count..trivia.raw_text.len() - quote_count];
let quote = good_docstring(&quotes_settings.docstring_quotes).repeat(quote_count);
@@ -356,7 +356,7 @@ fn strings(
Range::new(*start, *end),
);
if autofix.into() && settings.rules.should_fix(&Rule::BadQuotesMultilineString) {
if autofix.into() && settings.rules.should_fix(Rule::BadQuotesMultilineString) {
let string_contents = &trivia.raw_text[3..trivia.raw_text.len() - 3];
let quote = good_multiline(&quotes_settings.multiline_quotes);
let mut fixed_contents = String::with_capacity(
@@ -386,7 +386,7 @@ fn strings(
{
let mut diagnostic =
Diagnostic::new(AvoidableEscapedQuote, Range::new(*start, *end));
if autofix.into() && settings.rules.should_fix(&Rule::AvoidableEscapedQuote) {
if autofix.into() && settings.rules.should_fix(Rule::AvoidableEscapedQuote) {
let quote = bad_single(&quotes_settings.inline_quotes);
let mut fixed_contents =
@@ -445,7 +445,7 @@ fn strings(
},
Range::new(*start, *end),
);
if autofix.into() && settings.rules.should_fix(&Rule::BadQuotesInlineString) {
if autofix.into() && settings.rules.should_fix(Rule::BadQuotesInlineString) {
let quote = good_single(&quotes_settings.inline_quotes);
let mut fixed_contents =
String::with_capacity(trivia.prefix.len() + string_contents.len() + 2);

View File

@@ -514,13 +514,13 @@ pub fn function(checker: &mut Checker, body: &[Stmt]) {
return;
}
if checker.settings.rules.enabled(&Rule::SuperfluousElseReturn)
|| checker.settings.rules.enabled(&Rule::SuperfluousElseRaise)
if checker.settings.rules.enabled(Rule::SuperfluousElseReturn)
|| checker.settings.rules.enabled(Rule::SuperfluousElseRaise)
|| checker
.settings
.rules
.enabled(&Rule::SuperfluousElseContinue)
|| checker.settings.rules.enabled(&Rule::SuperfluousElseBreak)
.enabled(Rule::SuperfluousElseContinue)
|| checker.settings.rules.enabled(Rule::SuperfluousElseBreak)
{
if superfluous_elif(checker, &stack) {
return;
@@ -536,20 +536,20 @@ pub fn function(checker: &mut Checker, body: &[Stmt]) {
}
if !result_exists(&stack.returns) {
if checker.settings.rules.enabled(&Rule::UnnecessaryReturnNone) {
if checker.settings.rules.enabled(Rule::UnnecessaryReturnNone) {
unnecessary_return_none(checker, &stack);
}
return;
}
if checker.settings.rules.enabled(&Rule::ImplicitReturnValue) {
if checker.settings.rules.enabled(Rule::ImplicitReturnValue) {
implicit_return_value(checker, &stack);
}
if checker.settings.rules.enabled(&Rule::ImplicitReturn) {
if checker.settings.rules.enabled(Rule::ImplicitReturn) {
implicit_return(checker, last_stmt);
}
if checker.settings.rules.enabled(&Rule::UnnecessaryAssign) {
if checker.settings.rules.enabled(Rule::UnnecessaryAssign) {
for (_, expr) in &stack.returns {
if let Some(expr) = expr {
unnecessary_assign(checker, &stack, expr);

View File

@@ -3,7 +3,8 @@ use rustpython_parser::ast::{Expr, ExprKind};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::collect_call_path;
use ruff_python_ast::types::{Range, ScopeKind};
use ruff_python_ast::scope::ScopeKind;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;

View File

@@ -3,7 +3,8 @@ use rustpython_parser::ast::{Cmpop, Expr, ExprKind, Stmt, StmtKind, Unaryop};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::{create_expr, unparse_expr};
use ruff_python_ast::types::{Range, ScopeKind};
use ruff_python_ast::scope::ScopeKind;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
@@ -93,7 +94,7 @@ pub fn negation_with_equal_op(checker: &mut Checker, expr: &Expr, op: &Unaryop,
}
// Avoid flagging issues in dunder implementations.
if let ScopeKind::Function(def) = &checker.ctx.current_scope().kind {
if let ScopeKind::Function(def) = &checker.ctx.scope().kind {
if DUNDER_METHODS.contains(&def.name) {
return;
}
@@ -144,7 +145,7 @@ pub fn negation_with_not_equal_op(
}
// Avoid flagging issues in dunder implementations.
if let ScopeKind::Function(def) = &checker.ctx.current_scope().kind {
if let ScopeKind::Function(def) = &checker.ctx.scope().kind {
if DUNDER_METHODS.contains(&def.name) {
return;
}

View File

@@ -201,7 +201,7 @@ pub fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt, sibling:
.or_else(|| sibling.and_then(|sibling| return_values_for_siblings(stmt, sibling)))
{
if loop_info.return_value && !loop_info.next_return_value {
if checker.settings.rules.enabled(&Rule::ReimplementedBuiltin) {
if checker.settings.rules.enabled(Rule::ReimplementedBuiltin) {
let contents = return_stmt(
"any",
loop_info.test,
@@ -233,7 +233,7 @@ pub fn convert_for_loop_to_any_all(checker: &mut Checker, stmt: &Stmt, sibling:
}
if !loop_info.return_value && loop_info.next_return_value {
if checker.settings.rules.enabled(&Rule::ReimplementedBuiltin) {
if checker.settings.rules.enabled(Rule::ReimplementedBuiltin) {
// Invert the condition.
let test = {
if let ExprKind::UnaryOp {

View File

@@ -3,7 +3,7 @@ use rustpython_parser::ast::{Constant, Expr, ExprKind};
use ruff_python_ast::context::Context;
use ruff_python_ast::helpers::{map_callable, to_call_path};
use ruff_python_ast::types::{Binding, BindingKind, ExecutionContext, ScopeKind};
use ruff_python_ast::scope::{Binding, BindingKind, ExecutionContext, ScopeKind};
/// Return `true` if [`Expr`] is a guard for a type-checking block.
pub fn is_type_checking_block(context: &Context, test: &Expr) -> bool {
@@ -71,7 +71,7 @@ pub fn runtime_evaluated(
}
fn runtime_evaluated_base_class(context: &Context, base_classes: &[String]) -> bool {
if let ScopeKind::Class(class_def) = &context.current_scope().kind {
if let ScopeKind::Class(class_def) = &context.scope().kind {
for base in class_def.bases.iter() {
if let Some(call_path) = context.resolve_call_path(base) {
if base_classes
@@ -87,7 +87,7 @@ fn runtime_evaluated_base_class(context: &Context, base_classes: &[String]) -> b
}
fn runtime_evaluated_decorators(context: &Context, decorators: &[String]) -> bool {
if let ScopeKind::Class(class_def) = &context.current_scope().kind {
if let ScopeKind::Class(class_def) = &context.scope().kind {
for decorator in class_def.decorator_list.iter() {
if let Some(call_path) = context.resolve_call_path(map_callable(decorator)) {
if decorators

View File

@@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{Binding, BindingKind, ExecutionContext};
use ruff_python_ast::scope::{Binding, BindingKind, ExecutionContext};
#[violation]
pub struct RuntimeImportInTypeCheckingBlock {

View File

@@ -2,7 +2,7 @@ use std::path::Path;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{Binding, BindingKind, ExecutionContext};
use ruff_python_ast::scope::{Binding, BindingKind, ExecutionContext};
use crate::rules::isort::{categorize, ImportType};
use crate::settings::Settings;

View File

@@ -1,14 +1,13 @@
use std::iter;
use regex::Regex;
use rustc_hash::FxHashMap;
use rustpython_parser::ast::{Arg, Arguments};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::function_type;
use ruff_python_ast::function_type::FunctionType;
use ruff_python_ast::types::{Binding, FunctionDef, Lambda, Scope, ScopeKind};
use ruff_python_ast::scope::{Bindings, FunctionDef, Lambda, Scope, ScopeKind};
use ruff_python_ast::visibility;
use crate::checkers::ast::Checker;
@@ -85,8 +84,8 @@ impl Violation for UnusedLambdaArgument {
fn function(
argumentable: &Argumentable,
args: &Arguments,
values: &FxHashMap<&str, usize>,
bindings: &[Binding],
values: &Scope,
bindings: &Bindings,
dummy_variable_rgx: &Regex,
ignore_variadic_names: bool,
) -> Vec<Diagnostic> {
@@ -112,8 +111,8 @@ fn function(
fn method(
argumentable: &Argumentable,
args: &Arguments,
values: &FxHashMap<&str, usize>,
bindings: &[Binding],
values: &Scope,
bindings: &Bindings,
dummy_variable_rgx: &Regex,
ignore_variadic_names: bool,
) -> Vec<Diagnostic> {
@@ -139,14 +138,14 @@ fn method(
fn call<'a>(
argumentable: &Argumentable,
args: impl Iterator<Item = &'a Arg>,
values: &FxHashMap<&str, usize>,
bindings: &[Binding],
values: &Scope,
bindings: &Bindings,
dummy_variable_rgx: &Regex,
) -> Vec<Diagnostic> {
let mut diagnostics: Vec<Diagnostic> = vec![];
for arg in args {
if let Some(binding) = values
.get(&arg.node.arg.as_str())
.get(arg.node.arg.as_str())
.map(|index| &bindings[*index])
{
if !binding.used()
@@ -168,7 +167,7 @@ pub fn unused_arguments(
checker: &Checker,
parent: &Scope,
scope: &Scope,
bindings: &[Binding],
bindings: &Bindings,
) -> Vec<Diagnostic> {
match &scope.kind {
ScopeKind::Function(FunctionDef {
@@ -196,7 +195,7 @@ pub fn unused_arguments(
function(
&Argumentable::Function,
args,
&scope.bindings,
scope,
bindings,
&checker.settings.dummy_variable_rgx,
checker
@@ -225,7 +224,7 @@ pub fn unused_arguments(
method(
&Argumentable::Method,
args,
&scope.bindings,
scope,
bindings,
&checker.settings.dummy_variable_rgx,
checker
@@ -254,7 +253,7 @@ pub fn unused_arguments(
method(
&Argumentable::ClassMethod,
args,
&scope.bindings,
scope,
bindings,
&checker.settings.dummy_variable_rgx,
checker
@@ -283,7 +282,7 @@ pub fn unused_arguments(
function(
&Argumentable::StaticMethod,
args,
&scope.bindings,
scope,
bindings,
&checker.settings.dummy_variable_rgx,
checker
@@ -306,7 +305,7 @@ pub fn unused_arguments(
function(
&Argumentable::Lambda,
args,
&scope.bindings,
scope,
bindings,
&checker.settings.dummy_variable_rgx,
checker

View File

@@ -24,13 +24,13 @@ impl Argumentable {
}
}
pub const fn rule_code(&self) -> &Rule {
pub const fn rule_code(&self) -> Rule {
match self {
Self::Function => &Rule::UnusedFunctionArgument,
Self::Method => &Rule::UnusedMethodArgument,
Self::ClassMethod => &Rule::UnusedClassMethodArgument,
Self::StaticMethod => &Rule::UnusedStaticMethodArgument,
Self::Lambda => &Rule::UnusedLambdaArgument,
Self::Function => Rule::UnusedFunctionArgument,
Self::Method => Rule::UnusedMethodArgument,
Self::ClassMethod => Rule::UnusedClassMethodArgument,
Self::StaticMethod => Rule::UnusedStaticMethodArgument,
Self::Lambda => Rule::UnusedLambdaArgument,
}
}
}

View File

@@ -94,7 +94,7 @@ fn force_single_line_imports<'a>(
} else {
CommentSet {
atop: vec![],
inline: vec![],
inline: comment_set.inline.clone(),
}
},
TrailingComma::Absent,
@@ -143,7 +143,7 @@ pub fn format_imports(
let block = annotate_imports(&block.imports, comments, locator, split_on_trailing_comma);
// Normalize imports (i.e., deduplicate, aggregate `from` imports).
let block = normalize_imports(block, combine_as_imports);
let block = normalize_imports(block, combine_as_imports, force_single_line);
let mut output = String::new();
@@ -373,6 +373,7 @@ mod tests {
#[test_case(Path::new("inline_comments.py"))]
#[test_case(Path::new("insert_empty_lines.py"))]
#[test_case(Path::new("insert_empty_lines.pyi"))]
#[test_case(Path::new("isort_skip_file.py"))]
#[test_case(Path::new("leading_prefix.py"))]
#[test_case(Path::new("magic_trailing_comma.py"))]
#[test_case(Path::new("natural_order.py"))]
@@ -391,12 +392,12 @@ mod tests {
#[test_case(Path::new("preserve_tabs_2.py"))]
#[test_case(Path::new("relative_imports_order.py"))]
#[test_case(Path::new("reorder_within_section.py"))]
#[test_case(Path::new("ruff_skip_file.py"))]
#[test_case(Path::new("separate_first_party_imports.py"))]
#[test_case(Path::new("separate_future_imports.py"))]
#[test_case(Path::new("separate_local_folder_imports.py"))]
#[test_case(Path::new("separate_third_party_imports.py"))]
#[test_case(Path::new("skip.py"))]
#[test_case(Path::new("skip_file.py"))]
#[test_case(Path::new("sort_similar_imports.py"))]
#[test_case(Path::new("split.py"))]
#[test_case(Path::new("star_before_others.py"))]

View File

@@ -3,7 +3,11 @@ use crate::rules::isort::types::TrailingComma;
use super::types::{AliasData, ImportBlock, ImportFromData};
use super::AnnotatedImport;
pub fn normalize_imports(imports: Vec<AnnotatedImport>, combine_as_imports: bool) -> ImportBlock {
pub fn normalize_imports(
imports: Vec<AnnotatedImport>,
combine_as_imports: bool,
force_single_line: bool,
) -> ImportBlock {
let mut block = ImportBlock::default();
for import in imports {
match import {
@@ -55,7 +59,7 @@ pub fn normalize_imports(imports: Vec<AnnotatedImport>, combine_as_imports: bool
.import_from_star
.entry(ImportFromData { module, level })
.or_default()
} else if alias.asname.is_none() || combine_as_imports {
} else if alias.asname.is_none() || combine_as_imports || force_single_line {
block
.import_from
.entry(ImportFromData { module, level })
@@ -89,7 +93,7 @@ pub fn normalize_imports(imports: Vec<AnnotatedImport>, combine_as_imports: bool
.import_from_star
.entry(ImportFromData { module, level })
.or_default()
} else if alias.asname.is_none() || combine_as_imports {
} else if alias.asname.is_none() || combine_as_imports || force_single_line {
block
.import_from
.entry(ImportFromData { module, level })

View File

@@ -168,7 +168,7 @@ fn add_required_import(
MissingRequiredImport(required_import.clone()),
Range::new(Location::default(), Location::default()),
);
if autofix.into() && settings.rules.should_fix(&Rule::MissingRequiredImport) {
if autofix.into() && settings.rules.should_fix(Rule::MissingRequiredImport) {
// Determine the location at which the import should be inserted.
let splice = helpers::find_splice_location(python_ast, locator);

View File

@@ -11,15 +11,15 @@ expression: diagnostics
row: 1
column: 0
end_location:
row: 19
row: 25
column: 0
fix:
content: "import math\nimport sys\nfrom logging.handlers import FileHandler, StreamHandler\nfrom os import path, uname\n\n# comment 5\nfrom bar import a # comment 6\nfrom bar import b # comment 7\nfrom foo import bar # comment 3\nfrom foo2 import bar2 # comment 4\n\n# comment 1\n# comment 2\nfrom third_party import lib1\nfrom third_party import lib2\nfrom third_party import lib3\nfrom third_party import lib4\nfrom third_party import lib5\nfrom third_party import lib6\nfrom third_party import lib7\n"
content: "import math\nimport sys\nfrom json import detect_encoding\nfrom json import dump\nfrom json import dumps as json_dumps\nfrom json import load\nfrom json import loads as json_loads\nfrom logging.handlers import FileHandler, StreamHandler\nfrom os import path, uname\n\n# comment 6\nfrom bar import a # comment 7\nfrom bar import b # comment 8\nfrom foo import bar # comment 3\nfrom foo2 import bar2 # comment 4\nfrom foo3 import bar3 # comment 5\nfrom foo3 import baz3 # comment 5\n\n# comment 1\n# comment 2\nfrom third_party import lib1\nfrom third_party import lib2\nfrom third_party import lib3\nfrom third_party import lib4\nfrom third_party import lib5\nfrom third_party import lib6\nfrom third_party import lib7\n"
location:
row: 1
column: 0
end_location:
row: 19
row: 25
column: 0
parent: ~

View File

@@ -0,0 +1,6 @@
---
source: crates/ruff/src/rules/isort/mod.rs
expression: diagnostics
---
[]

View File

@@ -0,0 +1,6 @@
---
source: crates/ruff/src/rules/isort/mod.rs
expression: diagnostics
---
[]

View File

@@ -8,18 +8,18 @@ expression: diagnostics
suggestion: Organize imports
fixable: true
location:
row: 12
row: 20
column: 0
end_location:
row: 14
row: 22
column: 0
fix:
content: " import abc\n import collections\n"
location:
row: 12
row: 20
column: 0
end_location:
row: 14
row: 22
column: 0
parent: ~
- kind:
@@ -28,18 +28,18 @@ expression: diagnostics
suggestion: Organize imports
fixable: true
location:
row: 19
row: 27
column: 0
end_location:
row: 21
row: 29
column: 0
fix:
content: " import abc\n import collections\n"
location:
row: 19
row: 27
column: 0
end_location:
row: 21
row: 29
column: 0
parent: ~

View File

@@ -1,6 +0,0 @@
---
source: src/rules/isort/mod.rs
expression: diagnostics
---
[]

View File

@@ -1,4 +1,4 @@
use rustpython_parser::ast::{ExcepthandlerKind, ExprKind, Stmt, StmtKind};
use rustpython_parser::ast::{ExcepthandlerKind, Stmt, StmtKind};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@@ -50,13 +50,18 @@ use ruff_python_ast::source_code::Locator;
pub struct ComplexStructure {
pub name: String,
pub complexity: usize,
pub max_complexity: usize,
}
impl Violation for ComplexStructure {
#[derive_message_formats]
fn message(&self) -> String {
let ComplexStructure { name, complexity } = self;
format!("`{name}` is too complex ({complexity})")
let ComplexStructure {
name,
complexity,
max_complexity,
} = self;
format!("`{name}` is too complex ({complexity} > {max_complexity})")
}
}
@@ -74,13 +79,10 @@ fn get_complexity_number(stmts: &[Stmt]) -> usize {
complexity += get_complexity_number(body);
complexity += get_complexity_number(orelse);
}
StmtKind::While { test, body, orelse } => {
StmtKind::While { body, orelse, .. } => {
complexity += 1;
complexity += get_complexity_number(body);
complexity += get_complexity_number(orelse);
if let ExprKind::BoolOp { .. } = &test.node {
complexity += 1;
}
}
StmtKind::Match { cases, .. } => {
complexity += 1;
@@ -100,8 +102,10 @@ fn get_complexity_number(stmts: &[Stmt]) -> usize {
orelse,
finalbody,
} => {
complexity += 1;
complexity += get_complexity_number(body);
if !orelse.is_empty() {
complexity += 1;
}
complexity += get_complexity_number(orelse);
complexity += get_complexity_number(finalbody);
for handler in handlers {
@@ -136,6 +140,7 @@ pub fn function_is_too_complex(
ComplexStructure {
name: name.to_string(),
complexity,
max_complexity,
},
identifier_range(stmt, locator),
))
@@ -307,7 +312,7 @@ def nested_try_finally():
print(3)
"#;
let stmts = parser::parse_program(source, "<filename>")?;
assert_eq!(get_complexity_number(&stmts), 3);
assert_eq!(get_complexity_number(&stmts), 1);
Ok(())
}
@@ -374,4 +379,33 @@ class Class:
assert_eq!(get_complexity_number(&stmts), 9);
Ok(())
}
#[test]
fn finally() -> Result<()> {
let source = r#"
def process_detect_lines():
try:
pass
finally:
pass
"#;
let stmts = parser::parse_program(source, "<filename>")?;
assert_eq!(get_complexity_number(&stmts), 1);
Ok(())
}
#[test]
fn if_in_finally() -> Result<()> {
let source = r#"
def process_detect_lines():
try:
pass
finally:
if res:
errors.append(f"Non-zero exit code {res}")
"#;
let stmts = parser::parse_program(source, "<filename>")?;
assert_eq!(get_complexity_number(&stmts), 2);
Ok(())
}
}

View File

@@ -4,7 +4,7 @@ expression: diagnostics
---
- kind:
name: ComplexStructure
body: "`trivial` is too complex (1)"
body: "`trivial` is too complex (1 > 0)"
suggestion: ~
fixable: false
location:
@@ -17,7 +17,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`expr_as_statement` is too complex (1)"
body: "`expr_as_statement` is too complex (1 > 0)"
suggestion: ~
fixable: false
location:
@@ -30,7 +30,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`sequential` is too complex (1)"
body: "`sequential` is too complex (1 > 0)"
suggestion: ~
fixable: false
location:
@@ -43,7 +43,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`if_elif_else_dead_path` is too complex (3)"
body: "`if_elif_else_dead_path` is too complex (3 > 0)"
suggestion: ~
fixable: false
location:
@@ -56,7 +56,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`nested_ifs` is too complex (3)"
body: "`nested_ifs` is too complex (3 > 0)"
suggestion: ~
fixable: false
location:
@@ -69,7 +69,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`for_loop` is too complex (2)"
body: "`for_loop` is too complex (2 > 0)"
suggestion: ~
fixable: false
location:
@@ -82,7 +82,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`for_else` is too complex (2)"
body: "`for_else` is too complex (2 > 0)"
suggestion: ~
fixable: false
location:
@@ -95,7 +95,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`recursive` is too complex (2)"
body: "`recursive` is too complex (2 > 0)"
suggestion: ~
fixable: false
location:
@@ -108,7 +108,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`nested_functions` is too complex (3)"
body: "`nested_functions` is too complex (3 > 0)"
suggestion: ~
fixable: false
location:
@@ -121,7 +121,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`a` is too complex (2)"
body: "`a` is too complex (2 > 0)"
suggestion: ~
fixable: false
location:
@@ -134,7 +134,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`b` is too complex (1)"
body: "`b` is too complex (1 > 0)"
suggestion: ~
fixable: false
location:
@@ -147,7 +147,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`try_else` is too complex (4)"
body: "`try_else` is too complex (4 > 0)"
suggestion: ~
fixable: false
location:
@@ -160,7 +160,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`nested_try_finally` is too complex (3)"
body: "`nested_try_finally` is too complex (1 > 0)"
suggestion: ~
fixable: false
location:
@@ -173,7 +173,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`foobar` is too complex (3)"
body: "`foobar` is too complex (3 > 0)"
suggestion: ~
fixable: false
location:
@@ -186,7 +186,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`annotated_assign` is too complex (1)"
body: "`annotated_assign` is too complex (1 > 0)"
suggestion: ~
fixable: false
location:
@@ -199,7 +199,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`handle` is too complex (9)"
body: "`handle` is too complex (9 > 0)"
suggestion: ~
fixable: false
location:
@@ -212,7 +212,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`a` is too complex (1)"
body: "`a` is too complex (1 > 0)"
suggestion: ~
fixable: false
location:
@@ -225,7 +225,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`b` is too complex (2)"
body: "`b` is too complex (2 > 0)"
suggestion: ~
fixable: false
location:
@@ -238,7 +238,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`c` is too complex (1)"
body: "`c` is too complex (1 > 0)"
suggestion: ~
fixable: false
location:
@@ -251,7 +251,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`error` is too complex (1)"
body: "`error` is too complex (1 > 0)"
suggestion: ~
fixable: false
location:
@@ -264,7 +264,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`info` is too complex (1)"
body: "`info` is too complex (1 > 0)"
suggestion: ~
fixable: false
location:
@@ -277,7 +277,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`exception` is too complex (1)"
body: "`exception` is too complex (1 > 0)"
suggestion: ~
fixable: false
location:

View File

@@ -4,7 +4,7 @@ expression: diagnostics
---
- kind:
name: ComplexStructure
body: "`try_else` is too complex (4)"
body: "`try_else` is too complex (4 > 3)"
suggestion: ~
fixable: false
location:
@@ -17,7 +17,7 @@ expression: diagnostics
parent: ~
- kind:
name: ComplexStructure
body: "`handle` is too complex (9)"
body: "`handle` is too complex (9 > 3)"
suggestion: ~
fixable: false
location:

View File

@@ -47,7 +47,7 @@ mod tests {
);
let actual: Vec<Rule> = diagnostics
.into_iter()
.map(|diagnostic| diagnostic.kind.rule().clone())
.map(|diagnostic| diagnostic.kind.rule())
.collect();
assert_eq!(actual, expected);
}

View File

@@ -3,7 +3,8 @@ use rustpython_parser::ast::{Expr, ExprKind};
use ruff_diagnostics::Violation;
use ruff_diagnostics::{Diagnostic, DiagnosticKind};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{BindingKind, Range};
use ruff_python_ast::scope::BindingKind;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::Rule;
@@ -52,10 +53,10 @@ impl Violation for UseOfDotValues {
pub fn check_attr(checker: &mut Checker, attr: &str, value: &Expr, attr_expr: &Expr) {
let rules = &checker.settings.rules;
let violation: DiagnosticKind = match attr {
"ix" if rules.enabled(&Rule::UseOfDotIx) => UseOfDotIx.into(),
"at" if rules.enabled(&Rule::UseOfDotAt) => UseOfDotAt.into(),
"iat" if rules.enabled(&Rule::UseOfDotIat) => UseOfDotIat.into(),
"values" if rules.enabled(&Rule::UseOfDotValues) => UseOfDotValues.into(),
"ix" if rules.enabled(Rule::UseOfDotIx) => UseOfDotIx.into(),
"at" if rules.enabled(Rule::UseOfDotAt) => UseOfDotAt.into(),
"iat" if rules.enabled(Rule::UseOfDotIat) => UseOfDotIat.into(),
"values" if rules.enabled(Rule::UseOfDotValues) => UseOfDotValues.into(),
_ => return,
};

View File

@@ -3,7 +3,8 @@ use rustpython_parser::ast::{Expr, ExprKind};
use ruff_diagnostics::Violation;
use ruff_diagnostics::{Diagnostic, DiagnosticKind};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{BindingKind, Range};
use ruff_python_ast::scope::BindingKind;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::Rule;
@@ -65,13 +66,13 @@ pub fn check_call(checker: &mut Checker, func: &Expr) {
let rules = &checker.settings.rules;
let ExprKind::Attribute { value, attr, .. } = &func.node else {return};
let violation: DiagnosticKind = match attr.as_str() {
"isnull" if rules.enabled(&Rule::UseOfDotIsNull) => UseOfDotIsNull.into(),
"notnull" if rules.enabled(&Rule::UseOfDotNotNull) => UseOfDotNotNull.into(),
"pivot" | "unstack" if rules.enabled(&Rule::UseOfDotPivotOrUnstack) => {
"isnull" if rules.enabled(Rule::UseOfDotIsNull) => UseOfDotIsNull.into(),
"notnull" if rules.enabled(Rule::UseOfDotNotNull) => UseOfDotNotNull.into(),
"pivot" | "unstack" if rules.enabled(Rule::UseOfDotPivotOrUnstack) => {
UseOfDotPivotOrUnstack.into()
}
"read_table" if rules.enabled(&Rule::UseOfDotReadTable) => UseOfDotReadTable.into(),
"stack" if rules.enabled(&Rule::UseOfDotStack) => UseOfDotStack.into(),
"read_table" if rules.enabled(Rule::UseOfDotReadTable) => UseOfDotReadTable.into(),
"stack" if rules.enabled(Rule::UseOfDotStack) => UseOfDotStack.into(),
_ => return,
};

View File

@@ -3,8 +3,8 @@ use rustpython_parser::ast::Stmt;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::identifier_range;
use ruff_python_ast::scope::{Scope, ScopeKind};
use ruff_python_ast::source_code::Locator;
use ruff_python_ast::types::{Scope, ScopeKind};
/// ## What it does
/// Checks for functions with "dunder" names (that is, names with two

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