Compare commits

..

89 Commits

Author SHA1 Message Date
Charlie Marsh
4a7167520a Match pylint logic 2023-03-17 17:31:34 -04:00
Charlie Marsh
41ab37e792 Rebase 2023-03-17 17:07:51 -04:00
Charlie Marsh
0b1806fdfa Merge branch 'main' into useless_return 2023-03-17 17:04:37 -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
Charlie Marsh
aa97a092bd Bump version to v0.0.255 (#3485) 2023-03-13 14:06:51 -04:00
Micha Reiser
685c242761 refactor(ruff_python_ast): Split get_argument (#3478) 2023-03-13 18:18:25 +01:00
Jonathan Plasse
b540407b74 Infer target-version from project metadata (#3470)
* Infer target-version from project metadata

* Fix requires-python with ">=3.8.16"

* Load requires-python at runtime

* Use upstream VersionSpecifiers

* Add debug information when parsing ruff.toml

* Display debug only if target_version is not set

* Bump pep440-rs to add impl Error for Pep440Error
2023-03-13 18:16:01 +01:00
Charlie Marsh
3a5fbd6d74 Upgrade RustPython to fix Serde dependency (#3481) 2023-03-13 12:29:31 -04:00
Charlie Marsh
227679b5cb Re-enable the T and C linter prefix selectors (#3452) 2023-03-13 08:20:30 -04:00
Charlie Marsh
c2750a59ab Implement an iterator for universal newlines (#3454)
# Summary

We need to support CR line endings (as opposed to LF and CRLF line endings, which are already supported). They're rare, but they do appear in Python code, and we tend to panic on any file that uses them.

Our `Locator` abstraction now supports CR line endings. However, Rust's `str#lines` implementation does _not_.

This PR adds a `UniversalNewlineIterator` implementation that respects all of CR, LF, and CRLF line endings, and plugs it into most of the `.lines()` call sites.

As an alternative design, it could be nice if we could leverage `Locator` for this. We've already computed all of the line endings, so we could probably iterate much more efficiently?

# Test Plan

Largely relying on automated testing, however, also ran over some known failure cases, like #3404.
2023-03-13 00:01:29 -04:00
Charlie Marsh
2a4d6ab3b2 Remove unnecessary Path::new from fs calls (#3476) 2023-03-12 23:18:23 -04:00
Charlie Marsh
7a80bcec58 Output GitLab paths relative to CI_PROJECT_DIR (#3475) 2023-03-13 03:03:37 +00:00
Y.D.X
297749a3a8 [doc] Update FAQ on Flake8 for structural pattern matching (#3473) 2023-03-13 02:27:22 +00:00
Charlie Marsh
8955e32b5c Respect ignores for runtime-import-in-type-checking-block (TCH004) (#3474) 2023-03-13 02:23:26 +00:00
Charlie Marsh
cd192eddf9 Add some new users to the README (#3471) 2023-03-13 00:08:58 +00:00
Jacob Latonis
675227db5c pylint: E1507 invalid-envvar-value (#3467) 2023-03-12 21:43:06 +00:00
Charlie Marsh
a65c6806a6 Avoid respecting noqa directives when RUF100 is enabled (#3469) 2023-03-12 14:37:35 -04:00
Calum Young
6c576872d4 List changes for all ecosystem repos (#3461) 2023-03-12 14:30:38 -04:00
Xuehai Pan
9858df1ac9 [FIX] PYI011: recognize Bool / Float / Complex numbers as simple defaults (#3459) 2023-03-12 17:34:09 +00:00
Charlie Marsh
7fb7268e8a Use a hash to fingerprint GitLab CI output (#3456) 2023-03-12 00:22:39 -05:00
Jacob Latonis
0f78f27713 pylint: W1508 invalid-envvar-default (#3449) 2023-03-11 16:44:42 -05:00
Charlie Marsh
12a6fc7041 Avoid removing un-aliased exceptions in OSError-aliased handlers (#3451) 2023-03-11 15:24:11 -05:00
Micha Reiser
d2988043af perf: Optimize UTF8/ASCII byte offset index (#3439) 2023-03-11 13:12:10 +01:00
Micha Reiser
cc8b13d3a7 refactor: Replace Vec in options metadata with static array (#3433) 2023-03-11 09:03:56 +00:00
Charlie Marsh
1e081cf9a6 Flag deprecated (but renamed) imports in UP035 (#3448) 2023-03-11 01:06:32 -05:00
Charlie Marsh
841bcf1cdd Remove unnecessary Serde derives (#3447) 2023-03-11 00:16:51 -05:00
Charlie Marsh
7062d1db16 Run ecosystem CI checks without --isolated (#3445) 2023-03-10 19:03:51 -05:00
Jonathan Plasse
8b561313aa Remove empty line after RUF100 auto-fix (#3414) 2023-03-10 22:57:13 +00:00
Samuel Cormier-Iijima
cfa2924664 Setup ecosystem CI (#3390)
This PR sets up an "ecosystem" check as an optional part of the CI step for pull requests. The primary piece of this is a new script in `scripts/check_ecosystem.py` which takes two ruff binaries as input and compares their outputs against a corpus of open-source code in parallel. I used ruff's `text` reporting format and stdlib's `difflib` (rather than JSON output and jsondiffs) to avoid adding another dependency. There is a new ecosystem-comment workflow to add a comment to the PR (see [this link](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/) which explains why it needs to be done as a new workflow for security reasons).
2023-03-10 17:39:07 -05:00
Florian Best
a3aeec6377 docs(pycodestyle): document rules (#3407) 2023-03-10 22:36:38 +00:00
Micha Reiser
b983d5eb3f fix: method red not found in release builds (#3434) 2023-03-10 10:17:35 +01:00
kyoto7250
bb3bb24b59 Autofix PIE810 rule violations (#3411) 2023-03-10 05:17:22 +00:00
Charlie Marsh
872829ca72 When "Args" and "Parameters" are present, prefer NumPy style (#3430) 2023-03-10 02:58:05 +00:00
Charlie Marsh
2383228709 Respect --show-fixes with --fix-only (#3426) 2023-03-09 21:37:39 +00:00
Aryaman Marathe
952307d39d [pylint] C1901: compare-to-empty-string (#3405) 2023-03-09 21:33:34 +00:00
tomecki
d20474da87 fix for autofix 2023-02-25 13:12:27 +01:00
tomecki
b7df7ee370 Merge branch 'useless_return' of github.com:tomecki/ruff into useless_return 2023-02-25 13:08:38 +01:00
tomecki
d6f0d1fab2 Merge branch 'charliermarsh:main' into useless_return 2023-02-25 13:08:09 +01:00
tomecki
c9bf979e61 Merge branch 'main' into useless_return 2023-02-25 13:06:51 +01:00
tomecki
e556fb4c77 Update crates/ruff/src/rules/pylint/rules/useless_return.rs
Co-authored-by: Jeong YunWon <69878+youknowone@users.noreply.github.com>
2023-02-25 12:59:13 +01:00
tomecki
02ae14037b Merge branch 'main' into useless_return 2023-02-22 09:38:18 +01:00
tomecki
385ccb2a50 rename for clarity 2023-02-21 20:54:22 +01:00
tomecki
ecacccbd5e cleanup 2023-02-21 20:39:33 +01:00
tomecki
bd7b472902 Merge branch 'main' into useless_return 2023-02-21 20:28:48 +01:00
tomecki
89b1b06dc7 wip 2023-02-21 20:28:33 +01:00
303 changed files with 8588 additions and 3253 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: |
@@ -79,6 +79,11 @@ jobs:
env:
# 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
cargo-test-wasm:
@@ -94,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
@@ -107,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: |
@@ -123,3 +128,51 @@ jobs:
- uses: crate-ci/typos@master
with:
files: .
ecosystem:
name: "ecosystem"
runs-on: ubuntu-latest
needs: cargo-test
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v3
- 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 }}
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

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

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

77
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.254"
version = "0.0.256"
dependencies = [
"anyhow",
"clap 4.1.8",
@@ -1503,6 +1514,12 @@ dependencies = [
"once_cell",
]
[[package]]
name = "pathdiff"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]]
name = "peg"
version = "0.8.1"
@@ -1530,6 +1547,18 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739"
[[package]]
name = "pep440_rs"
version = "0.2.0"
source = "git+https://github.com/konstin/pep440-rs.git?rev=a8fef4ec47f4c25b070b39cdbe6a0b9847e49941#a8fef4ec47f4c25b070b39cdbe6a0b9847e49941"
dependencies = [
"lazy_static",
"regex",
"serde",
"tracing",
"unicode-width",
]
[[package]]
name = "percent-encoding"
version = "2.2.0"
@@ -1953,7 +1982,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.254"
version = "0.0.256"
dependencies = [
"anyhow",
"bisection",
@@ -1979,6 +2008,9 @@ dependencies = [
"num-traits",
"once_cell",
"path-absolutize",
"pathdiff",
"pep440_rs",
"pretty_assertions",
"regex",
"result-like",
"ruff_cache",
@@ -2003,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"
@@ -2016,7 +2063,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.254"
version = "0.0.256"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2261,7 +2308,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=1871a1632e310985414211222f5bf8069678892f#1871a1632e310985414211222f5bf8069678892f"
source = "git+https://github.com/RustPython/RustPython.git?rev=c15f670f2c30cfae6b41a1874893590148c74bc4#c15f670f2c30cfae6b41a1874893590148c74bc4"
dependencies = [
"num-bigint",
"rustpython-compiler-core",
@@ -2270,7 +2317,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=1871a1632e310985414211222f5bf8069678892f#1871a1632e310985414211222f5bf8069678892f"
source = "git+https://github.com/RustPython/RustPython.git?rev=c15f670f2c30cfae6b41a1874893590148c74bc4#c15f670f2c30cfae6b41a1874893590148c74bc4"
dependencies = [
"ascii",
"bitflags",
@@ -2295,7 +2342,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=1871a1632e310985414211222f5bf8069678892f#1871a1632e310985414211222f5bf8069678892f"
source = "git+https://github.com/RustPython/RustPython.git?rev=c15f670f2c30cfae6b41a1874893590148c74bc4#c15f670f2c30cfae6b41a1874893590148c74bc4"
dependencies = [
"bitflags",
"bstr 0.2.17",
@@ -2309,7 +2356,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=1871a1632e310985414211222f5bf8069678892f#1871a1632e310985414211222f5bf8069678892f"
source = "git+https://github.com/RustPython/RustPython.git?rev=c15f670f2c30cfae6b41a1874893590148c74bc4#c15f670f2c30cfae6b41a1874893590148c74bc4"
dependencies = [
"ahash",
"anyhow",
@@ -2834,9 +2881,21 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.30"

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" }
@@ -26,11 +30,11 @@ proc-macro2 = { version = "1.0.51" }
quote = { version = "1.0.23" }
regex = { version = "1.7.1" }
rustc-hash = { version = "1.1.0" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "1871a1632e310985414211222f5bf8069678892f" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "c15f670f2c30cfae6b41a1874893590148c74bc4" }
rustpython-parser = { features = [
"lalrpop",
"serde",
], git = "https://github.com/RustPython/RustPython.git", rev = "1871a1632e310985414211222f5bf8069678892f" }
], git = "https://github.com/RustPython/RustPython.git", rev = "c15f670f2c30cfae6b41a1874893590148c74bc4" }
schemars = { version = "0.8.12" }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = { version = "1.0.93" }
@@ -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.254'
rev: 'v0.0.256'
hooks:
- id: ruff
```
@@ -306,6 +306,13 @@ Ruff is used in a number of major open-source projects, including:
- [meson-python](https://github.com/mesonbuild/meson-python)
- [ZenML](https://github.com/zenml-io/zenml)
- [delta-rs](https://github.com/delta-io/delta-rs)
- [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.254"
version = "0.0.256"
edition = { workspace = true }
rust-version = { workspace = true }

View File

@@ -46,8 +46,15 @@ fn main() -> Result<()> {
.map(|tool| ExternalConfig {
black: tool.black.as_ref(),
isort: tool.isort.as_ref(),
..Default::default()
})
.unwrap_or_default();
let external_config = ExternalConfig {
project: pyproject
.as_ref()
.and_then(|pyproject| pyproject.project.as_ref()),
..external_config
};
// Create Ruff's pyproject.toml section.
let pyproject = flake8_to_ruff::convert(&config, &external_config, args.plugin)?;

View File

@@ -1,19 +1,17 @@
[package]
name = "ruff"
version = "0.0.254"
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.256"
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
documentation.workspace = true
homepage.workspace = true
repository.workspace = true
readme = "README.md"
license = "MIT"
[lib]
name = "ruff"
crate-type = ["cdylib", "rlib"]
doctest = false
[dependencies]
ruff_cache = { path = "../ruff_cache" }
@@ -27,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" }
@@ -48,6 +46,10 @@ path-absolutize = { workspace = true, features = [
"once_cell_cache",
"use_unix_paths_on_wasm",
] }
pathdiff = { version = "0.2.1" }
pep440_rs = { git = "https://github.com/konstin/pep440-rs.git", features = [
"serde",
], rev = "a8fef4ec47f4c25b070b39cdbe6a0b9847e49941" }
regex = { workspace = true }
result-like = { version = "0.4.6" }
rustc-hash = { workspace = true }
@@ -65,10 +67,10 @@ thiserror = { version = "1.0.38" }
toml = { workspace = true }
[dev-dependencies]
insta = { workspace = true, features = ["yaml", "redactions"] }
test-case = { workspace = true }
criterion = { version = "0.4.0" }
insta = { workspace = true, features = ["yaml", "redactions"] }
pretty_assertions = "1.3.0"
test-case = { workspace = true }
[features]
default = []

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

@@ -6,6 +6,8 @@ obj.endswith("foo") or obj.endswith("bar")
obj.startswith(foo) or obj.startswith(bar)
# error
obj.startswith(foo) or obj.startswith("foo")
# error
obj.endswith(foo) or obj.startswith(foo) or obj.startswith("foo")
# ok
obj.startswith(("foo", "bar"))

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

@@ -43,3 +43,6 @@ def f21(
def f22(
x=-42.5j + 4.3j, # Error PYI014
) -> None: ...
def f23(
x=True, # OK
) -> 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

@@ -0,0 +1,12 @@
import os
tempVar = os.getenv("TEST", 12) # [invalid-envvar-default]
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

@@ -0,0 +1,15 @@
import os
os.getenv(1) # [invalid-envvar-value]
os.getenv("a")
os.getenv("test")
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,39 @@
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

View File

@@ -0,0 +1,10 @@
import socket
from kombu import Connection, exceptions
try:
conn = Connection(settings.CELERY_BROKER_URL)
conn.ensure_connection(max_retries=2)
conn._close()
except (socket.error, exceptions.OperationalError):
return HttpResponseServerError("cache: cannot connect to broker.")

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

@@ -0,0 +1,13 @@
# noqa
# noqa # comment
print() # noqa
print() # noqa # comment
print(a) # noqa
print(a) # noqa # comment
# noqa: E501, F821
# noqa: E501, F821 # comment
print() # noqa: E501, F821
print() # noqa: E501, F821 # comment
print(a) # noqa: E501, F821
print(a) # noqa: E501, F821 # comment

View File

@@ -9,8 +9,8 @@ use rustpython_parser::{lexer, Mode, Tok};
use ruff_diagnostics::Fix;
use ruff_python_ast::helpers;
use ruff_python_ast::helpers::to_absolute;
use ruff_python_ast::newlines::NewlineWithTrailingNewline;
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
use ruff_python_ast::whitespace::LinesWithTrailingNewline;
use crate::cst::helpers::compose_module_path;
use crate::cst::matchers::match_module;
@@ -100,7 +100,7 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool>
/// of a multi-statement line.
fn trailing_semicolon(stmt: &Stmt, locator: &Locator) -> Option<Location> {
let contents = locator.skip(stmt.end_location.unwrap());
for (row, line) in LinesWithTrailingNewline::from(contents).enumerate() {
for (row, line) in NewlineWithTrailingNewline::from(contents).enumerate() {
let trimmed = line.trim();
if trimmed.starts_with(';') {
let column = line
@@ -123,7 +123,7 @@ fn trailing_semicolon(stmt: &Stmt, locator: &Locator) -> Option<Location> {
fn next_stmt_break(semicolon: Location, locator: &Locator) -> Location {
let start_location = Location::new(semicolon.row(), semicolon.column() + 1);
let contents = locator.skip(start_location);
for (row, line) in LinesWithTrailingNewline::from(contents).enumerate() {
for (row, line) in NewlineWithTrailingNewline::from(contents).enumerate() {
let trimmed = line.trim();
// Skip past any continuations.
if trimmed.starts_with('\\') {

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

@@ -5,6 +5,7 @@ use nohash_hasher::IntMap;
use rustpython_parser::ast::Location;
use ruff_diagnostics::{Diagnostic, Fix};
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::types::Range;
use crate::codes::NoqaCode;
@@ -22,8 +23,8 @@ pub fn check_noqa(
noqa_line_for: &IntMap<usize, usize>,
settings: &Settings,
autofix: flags::Autofix,
) {
let enforce_noqa = settings.rules.enabled(&Rule::UnusedNOQA);
) -> Vec<usize> {
let enforce_noqa = settings.rules.enabled(Rule::UnusedNOQA);
// Whether the file is exempted from all checks.
let mut file_exempted = false;
@@ -38,7 +39,7 @@ pub fn check_noqa(
// Indices of diagnostics that were ignored by a `noqa` directive.
let mut ignored_diagnostics = vec![];
let lines: Vec<&str> = contents.lines().collect();
let lines: Vec<&str> = contents.universal_newlines().collect();
for lineno in commented_lines {
match extract_file_exemption(lines[lineno - 1]) {
Exemption::All => {
@@ -97,7 +98,7 @@ pub fn check_noqa(
ignored_diagnostics.push(index);
continue;
}
(Directive::Codes(.., codes), matches) => {
(Directive::Codes(.., codes, _), matches) => {
if noqa::includes(diagnostic.kind.rule(), codes) {
matches.push(diagnostic.kind.rule().noqa_code());
ignored_diagnostics.push(index);
@@ -124,7 +125,7 @@ pub fn check_noqa(
ignored_diagnostics.push(index);
continue;
}
(Directive::Codes(.., codes), matches) => {
(Directive::Codes(.., codes, _), matches) => {
if noqa::includes(diagnostic.kind.rule(), codes) {
matches.push(diagnostic.kind.rule().noqa_code());
ignored_diagnostics.push(index);
@@ -140,7 +141,7 @@ pub fn check_noqa(
if enforce_noqa {
for (row, (directive, matches)) in noqa_directives {
match directive {
Directive::All(spaces, start_byte, end_byte) => {
Directive::All(leading_spaces, start_byte, end_byte, trailing_spaces) => {
if matches.is_empty() {
let start = lines[row][..start_byte].chars().count();
let end = start + lines[row][start_byte..end_byte].chars().count();
@@ -150,15 +151,27 @@ pub fn check_noqa(
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
);
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start - spaces),
Location::new(row + 1, lines[row].chars().count()),
));
if start - leading_spaces == 0 && end == lines[row].chars().count() {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, 0),
Location::new(row + 2, 0),
));
} else if end == lines[row].chars().count() {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start - leading_spaces),
Location::new(row + 1, end + trailing_spaces),
));
} else {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start),
Location::new(row + 1, end + trailing_spaces),
));
}
}
diagnostics.push(diagnostic);
}
}
Directive::Codes(spaces, start_byte, end_byte, codes) => {
Directive::Codes(leading_spaces, start_byte, end_byte, codes, trailing_spaces) => {
let mut disabled_codes = vec![];
let mut unknown_codes = vec![];
let mut unmatched_codes = vec![];
@@ -175,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);
@@ -218,15 +231,28 @@ pub fn check_noqa(
);
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
if valid_codes.is_empty() {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start - spaces),
Location::new(row + 1, lines[row].chars().count()),
));
if start - leading_spaces == 0 && end == lines[row].chars().count()
{
diagnostic.amend(Fix::deletion(
Location::new(row + 1, 0),
Location::new(row + 2, 0),
));
} else if end == lines[row].chars().count() {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start - leading_spaces),
Location::new(row + 1, end + trailing_spaces),
));
} else {
diagnostic.amend(Fix::deletion(
Location::new(row + 1, start),
Location::new(row + 1, end + trailing_spaces),
));
}
} else {
diagnostic.amend(Fix::replacement(
format!("# noqa: {}", valid_codes.join(", ")),
Location::new(row + 1, start),
Location::new(row + 1, lines[row].chars().count()),
Location::new(row + 1, end),
));
}
}
@@ -239,7 +265,5 @@ pub fn check_noqa(
}
ignored_diagnostics.sort_unstable();
for index in ignored_diagnostics.iter().rev() {
diagnostics.swap_remove(*index);
}
ignored_diagnostics
}

View File

@@ -3,7 +3,8 @@
use std::path::Path;
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::source_code::Stylist;
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::source_code::{Locator, Stylist};
use crate::registry::Rule;
use crate::rules::flake8_executable::helpers::{extract_shebang, ShebangDirective};
@@ -21,8 +22,8 @@ use crate::settings::{flags, Settings};
pub fn check_physical_lines(
path: &Path,
locator: &Locator,
stylist: &Stylist,
contents: &str,
commented_lines: &[usize],
doc_lines: &[usize],
settings: &Settings,
@@ -31,32 +32,32 @@ 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();
for (index, line) in contents.lines().enumerate() {
for (index, line) in locator.contents().universal_newlines().enumerate() {
while commented_lines_iter
.next_if(|lineno| &(index + 1) == *lineno)
.is_some()
@@ -162,9 +163,9 @@ pub fn check_physical_lines(
if enforce_no_newline_at_end_of_file {
if let Some(diagnostic) = no_newline_at_end_of_file(
locator,
stylist,
contents,
autofix.into() && settings.rules.should_fix(&Rule::NoNewLineAtEndOfFile),
autofix.into() && settings.rules.should_fix(Rule::NoNewLineAtEndOfFile),
) {
diagnostics.push(diagnostic);
}
@@ -199,8 +200,8 @@ mod tests {
let check_with_max_line_length = |line_length: usize| {
check_physical_lines(
Path::new("foo.py"),
&locator,
&stylist,
line,
&[],
&[],
&Settings {

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

@@ -161,35 +161,44 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pyflakes, "901") => Rule::RaiseNotImplemented,
// pylint
(Pylint, "C0414") => Rule::UselessImportAlias,
(Pylint, "C1901") => Rule::CompareToEmptyString,
(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, "E1142") => Rule::AwaitOutsideAsync,
(Pylint, "E1205") => Rule::LoggingTooManyArgs,
(Pylint, "E1206") => Rule::LoggingTooFewArgs,
(Pylint, "E1307") => Rule::BadStringFormatType,
(Pylint, "E2502") => Rule::BidirectionalUnicode,
(Pylint, "E1310") => Rule::BadStrStripCall,
(Pylint, "C0414") => Rule::UselessImportAlias,
(Pylint, "C3002") => Rule::UnnecessaryDirectLambdaCall,
(Pylint, "E0117") => Rule::NonlocalWithoutBinding,
(Pylint, "E0118") => Rule::UsedPriorGlobalDeclaration,
(Pylint, "E1142") => Rule::AwaitOutsideAsync,
(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,
(Pylint, "C1901") => Rule::CompareToEmptyString,
(Pylint, "R0133") => Rule::ComparisonOfConstant,
(Pylint, "R0911") => Rule::TooManyReturnStatements,
(Pylint, "R0912") => Rule::TooManyBranches,
(Pylint, "R0913") => Rule::TooManyArguments,
(Pylint, "R0915") => Rule::TooManyStatements,
(Pylint, "R1701") => Rule::ConsiderMergingIsinstance,
(Pylint, "R1711") => Rule::UselessReturn,
(Pylint, "R1722") => Rule::ConsiderUsingSysExit,
(Pylint, "R5501") => Rule::CollapsibleElseIf,
(Pylint, "R2004") => Rule::MagicValueComparison,
(Pylint, "R5501") => Rule::CollapsibleElseIf,
(Pylint, "W0120") => Rule::UselessElseOnLoop,
(Pylint, "W0602") => Rule::GlobalVariableNotAssigned,
(Pylint, "W0603") => Rule::GlobalStatement,
(Pylint, "R0911") => Rule::TooManyReturnStatements,
(Pylint, "R0913") => Rule::TooManyArguments,
(Pylint, "R0912") => Rule::TooManyBranches,
(Pylint, "R0915") => Rule::TooManyStatements,
(Pylint, "W1508") => Rule::InvalidEnvvarDefault,
(Pylint, "W2901") => Rule::RedefinedLoopName,
// flake8-builtins
@@ -224,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,
@@ -373,7 +383,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pyupgrade, "032") => Rule::FString,
(Pyupgrade, "033") => Rule::FunctoolsCache,
(Pyupgrade, "034") => Rule::ExtraneousParentheses,
(Pyupgrade, "035") => Rule::ImportReplacements,
(Pyupgrade, "035") => Rule::DeprecatedImport,
(Pyupgrade, "036") => Rule::OutdatedVersionBlock,
(Pyupgrade, "037") => Rule::QuotedAnnotation,
(Pyupgrade, "038") => Rule::IsinstanceWithTuple,
@@ -658,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

@@ -20,6 +20,7 @@ use crate::rules::{
};
use crate::settings::options::Options;
use crate::settings::pyproject::Pyproject;
use crate::settings::types::PythonVersion;
use crate::warn_user;
const DEFAULT_SELECTORS: &[RuleSelector] = &[
@@ -424,6 +425,15 @@ pub fn convert(
}
}
if let Some(project) = &external_config.project {
if let Some(requires_python) = &project.requires_python {
if options.target_version.is_none() {
options.target_version =
PythonVersion::get_minimum_supported_version(requires_python);
}
}
}
// Create the pyproject.toml.
Ok(Pyproject::new(options))
}
@@ -439,13 +449,17 @@ fn resolve_select(plugins: &[Plugin]) -> HashSet<RuleSelector> {
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::str::FromStr;
use anyhow::Result;
use itertools::Itertools;
use pep440_rs::VersionSpecifiers;
use pretty_assertions::assert_eq;
use super::super::plugin::Plugin;
use super::convert;
use crate::flake8_to_ruff::converter::DEFAULT_SELECTORS;
use crate::flake8_to_ruff::pep621::Project;
use crate::flake8_to_ruff::ExternalConfig;
use crate::registry::Linter;
use crate::rule_selector::RuleSelector;
@@ -453,6 +467,7 @@ mod tests {
use crate::rules::{flake8_quotes, pydocstyle};
use crate::settings::options::Options;
use crate::settings::pyproject::Pyproject;
use crate::settings::types::PythonVersion;
fn default_options(plugins: impl IntoIterator<Item = RuleSelector>) -> Options {
Options {
@@ -609,4 +624,25 @@ mod tests {
Ok(())
}
#[test]
fn it_converts_project_requires_python() -> Result<()> {
let actual = convert(
&HashMap::from([("flake8".to_string(), HashMap::default())]),
&ExternalConfig {
project: Some(&Project {
requires_python: Some(VersionSpecifiers::from_str(">=3.8.16, <3.11")?),
}),
..ExternalConfig::default()
},
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
target_version: Some(PythonVersion::Py38),
..default_options([])
});
assert_eq!(actual, expected);
Ok(())
}
}

View File

@@ -1,8 +1,10 @@
use super::black::Black;
use super::isort::Isort;
use super::pep621::Project;
#[derive(Default)]
pub struct ExternalConfig<'a> {
pub black: Option<&'a Black>,
pub isort: Option<&'a Isort>,
pub project: Option<&'a Project>,
}

View File

@@ -3,6 +3,7 @@ mod converter;
mod external_config;
mod isort;
mod parser;
pub mod pep621;
mod plugin;
mod pyproject;

View File

@@ -0,0 +1,10 @@
//! Extract PEP 621 configuration settings from a pyproject.toml.
use pep440_rs::VersionSpecifiers;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct Project {
#[serde(alias = "requires-python", alias = "requires_python")]
pub requires_python: Option<VersionSpecifiers>,
}

View File

@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
use super::black::Black;
use super::isort::Isort;
use super::pep621::Project;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Tools {
@@ -15,6 +16,7 @@ pub struct Tools {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Pyproject {
pub tool: Option<Tools>,
pub project: Option<Project>,
}
pub fn parse<P: AsRef<Path>>(path: P) -> Result<Pyproject> {

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()
}
@@ -74,10 +76,20 @@ pub fn normalize_path_to<P: AsRef<Path>, R: AsRef<Path>>(path: P, project_root:
}
/// Convert an absolute path to be relative to the current working directory.
pub fn relativize_path(path: impl AsRef<Path>) -> String {
pub fn relativize_path<P: AsRef<Path>>(path: P) -> String {
let path = path.as_ref();
if let Ok(path) = path.strip_prefix(&*path_dedot::CWD) {
return format!("{}", path.display());
}
format!("{}", path.display())
}
/// Convert an absolute path to be relative to the specified project root.
pub fn relativize_path_to<P: AsRef<Path>, R: AsRef<Path>>(path: P, project_root: R) -> String {
format!(
"{}",
pathdiff::diff_paths(&path, project_root)
.expect("Could not diff paths")
.display()
)
}

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,
@@ -191,8 +191,8 @@ pub fn check_path(
{
diagnostics.extend(check_physical_lines(
path,
locator,
stylist,
contents,
indexer.commented_lines(),
&doc_lines,
settings,
@@ -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()));
}
};
@@ -215,7 +215,7 @@ pub fn check_path(
.iter_enabled()
.any(|rule_code| rule_code.lint_source().is_noqa())
{
check_noqa(
let ignored = check_noqa(
&mut diagnostics,
contents,
indexer.commented_lines(),
@@ -223,6 +223,11 @@ pub fn check_path(
settings,
error.as_ref().map_or(autofix, |_| flags::Autofix::Disabled),
);
if noqa.into() {
for index in ignored.iter().rev() {
diagnostics.swap_remove(*index);
}
}
}
LinterResult::new(diagnostics, error)

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

@@ -12,6 +12,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_parser::ast::Location;
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::source_code::{LineEnding, Locator};
use ruff_python_ast::types::Range;
@@ -21,7 +22,7 @@ use crate::rule_redirects::get_redirect_target;
static NOQA_LINE_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?P<spaces>\s*)(?P<noqa>(?i:# noqa)(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?)",
r"(?P<leading_spaces>\s*)(?P<noqa>(?i:# noqa)(?::\s?(?P<codes>(?:[A-Z]+[0-9]+)(?:[,\s]+[A-Z]+[0-9]+)*))?)(?P<trailing_spaces>\s*)",
)
.unwrap()
});
@@ -73,35 +74,42 @@ pub fn extract_file_exemption(line: &str) -> Exemption {
#[derive(Debug)]
pub enum Directive<'a> {
None,
All(usize, usize, usize),
Codes(usize, usize, usize, Vec<&'a str>),
All(usize, usize, usize, usize),
Codes(usize, usize, usize, Vec<&'a str>, usize),
}
/// Extract the noqa `Directive` from a line of Python source code.
pub fn extract_noqa_directive(line: &str) -> Directive {
match NOQA_LINE_REGEX.captures(line) {
Some(caps) => match caps.name("spaces") {
Some(spaces) => match caps.name("noqa") {
Some(noqa) => match caps.name("codes") {
Some(codes) => {
let codes: Vec<&str> = SPLIT_COMMA_REGEX
.split(codes.as_str().trim())
.map(str::trim)
.filter(|code| !code.is_empty())
.collect();
if codes.is_empty() {
warn!("Expected rule codes on `noqa` directive: \"{line}\"");
Some(caps) => match caps.name("leading_spaces") {
Some(leading_spaces) => match caps.name("trailing_spaces") {
Some(trailing_spaces) => match caps.name("noqa") {
Some(noqa) => match caps.name("codes") {
Some(codes) => {
let codes: Vec<&str> = SPLIT_COMMA_REGEX
.split(codes.as_str().trim())
.map(str::trim)
.filter(|code| !code.is_empty())
.collect();
if codes.is_empty() {
warn!("Expected rule codes on `noqa` directive: \"{line}\"");
}
Directive::Codes(
leading_spaces.as_str().chars().count(),
noqa.start(),
noqa.end(),
codes,
trailing_spaces.as_str().chars().count(),
)
}
Directive::Codes(
spaces.as_str().chars().count(),
None => Directive::All(
leading_spaces.as_str().chars().count(),
noqa.start(),
noqa.end(),
codes,
)
}
None => {
Directive::All(spaces.as_str().chars().count(), noqa.start(), noqa.end())
}
trailing_spaces.as_str().chars().count(),
),
},
None => Directive::None,
},
None => Directive::None,
},
@@ -113,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()
@@ -122,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,
@@ -135,7 +143,7 @@ pub fn rule_is_ignored(
match extract_noqa_directive(line) {
Directive::None => false,
Directive::All(..) => true,
Directive::Codes(.., codes) => includes(code, &codes),
Directive::Codes(.., codes, _) => includes(code, &codes),
}
}
@@ -166,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;
@@ -174,7 +182,7 @@ fn add_noqa_inner(
// Codes that are globally exempted (within the current file).
let mut file_exemptions: Vec<NoqaCode> = vec![];
let lines: Vec<&str> = contents.lines().collect();
let lines: Vec<&str> = contents.universal_newlines().collect();
for lineno in commented_lines {
match extract_file_exemption(lines[lineno - 1]) {
Exemption::All => {
@@ -216,7 +224,7 @@ fn add_noqa_inner(
Directive::All(..) => {
continue;
}
Directive::Codes(.., codes) => {
Directive::Codes(.., codes, _) => {
if includes(diagnostic.kind.rule(), &codes) {
continue;
}
@@ -236,7 +244,7 @@ fn add_noqa_inner(
Directive::All(..) => {
continue;
}
Directive::Codes(.., codes) => {
Directive::Codes(.., codes, _) => {
if includes(diagnostic.kind.rule(), &codes) {
continue;
}
@@ -256,7 +264,7 @@ fn add_noqa_inner(
let mut count: usize = 0;
let mut output = String::new();
for (lineno, line) in contents.lines().enumerate() {
for (lineno, line) in lines.into_iter().enumerate() {
match matches_by_line.get(&lineno) {
None => {
output.push_str(line);
@@ -272,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;
}
@@ -281,7 +289,7 @@ fn add_noqa_inner(
output.push_str(line);
output.push_str(line_ending);
}
Directive::Codes(_, start_byte, _, existing) => {
Directive::Codes(_, start_byte, _, existing, _) => {
// Reconstruct the line based on the preserved rule codes.
// This enables us to tally the number of edits.
let mut formatted = String::with_capacity(line.len());

View File

@@ -143,13 +143,22 @@ 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,
rules::pylint::rules::InvalidEnvvarDefault,
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,
@@ -183,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,
@@ -339,7 +349,7 @@ ruff_macros::register_rules!(
rules::pyupgrade::rules::FString,
rules::pyupgrade::rules::FunctoolsCache,
rules::pyupgrade::rules::ExtraneousParentheses,
rules::pyupgrade::rules::ImportReplacements,
rules::pyupgrade::rules::DeprecatedImport,
rules::pyupgrade::rules::OutdatedVersionBlock,
rules::pyupgrade::rules::QuotedAnnotation,
rules::pyupgrade::rules::IsinstanceWithTuple,
@@ -599,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,
@@ -608,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)?;
@@ -803,7 +818,7 @@ impl Linter {
}
}
#[derive(is_macro::Is)]
#[derive(is_macro::Is, Copy, Clone)]
pub enum LintSource {
Ast,
Io,
@@ -818,9 +833,9 @@ pub enum LintSource {
impl Rule {
/// The source for the diagnostic (either the AST, the filesystem, or the
/// physical lines).
pub const fn lint_source(&self) -> &'static LintSource {
pub const fn lint_source(&self) -> LintSource {
match self {
Rule::UnusedNOQA => &LintSource::Noqa,
Rule::UnusedNOQA => LintSource::Noqa,
Rule::BlanketNOQA
| Rule::BlanketTypeIgnore
| Rule::DocLineTooLong
@@ -836,7 +851,7 @@ impl Rule {
| Rule::ShebangWhitespace
| Rule::TrailingWhitespace
| Rule::IndentationContainsTabs
| Rule::BlankLineContainsWhitespace => &LintSource::PhysicalLines,
| Rule::BlankLineContainsWhitespace => LintSource::PhysicalLines,
Rule::AmbiguousUnicodeCharacterComment
| Rule::AmbiguousUnicodeCharacterDocstring
| Rule::AmbiguousUnicodeCharacterString
@@ -846,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
@@ -855,10 +875,10 @@ impl Rule {
| Rule::UselessSemicolon
| Rule::MultipleStatementsOnOneLineSemicolon
| Rule::TrailingCommaProhibited
| Rule::TypeCommentInStub => &LintSource::Tokens,
Rule::IOError => &LintSource::Io,
Rule::UnsortedImports | Rule::MissingRequiredImport => &LintSource::Imports,
Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => &LintSource::Filesystem,
| Rule::TypeCommentInStub => LintSource::Tokens,
Rule::IOError => LintSource::Io,
Rule::UnsortedImports | Rule::MissingRequiredImport => LintSource::Imports,
Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => LintSource::Filesystem,
#[cfg(feature = "logical_lines")]
Rule::IndentationWithInvalidMultiple
| Rule::IndentationWithInvalidMultipleComment
@@ -890,8 +910,8 @@ impl Rule {
| Rule::WhitespaceAfterOpenBracket
| Rule::WhitespaceBeforeCloseBracket
| Rule::WhitespaceBeforeParameters
| Rule::WhitespaceBeforePunctuation => &LintSource::LogicalLines,
_ => &LintSource::Ast,
| Rule::WhitespaceBeforePunctuation => LintSource::LogicalLines,
_ => LintSource::Ast,
}
}
}
@@ -914,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};
@@ -959,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,9 +18,7 @@ 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.
("C", "C4"),
("C9", "C90"),
("T", "T10"),
("T1", "T10"),
("T2", "T20"),
// TODO(charlie): Remove by 2023-02-01.

View File

@@ -15,9 +15,17 @@ use crate::rule_redirects::get_redirect;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RuleSelector {
/// All rules
/// Select all rules.
All,
/// Legacy category to select both the `mccabe` and `flake8-comprehensions` linters
/// via a single selector.
C,
/// Legacy category to select both the `flake8-debugger` and `flake8-print` linters
/// via a single selector.
T,
/// Select all rules for a given linter.
Linter(Linter),
/// Select all rules for a given linter with a given prefix.
Prefix {
prefix: RuleCodePrefix,
redirected_from: Option<&'static str>,
@@ -36,6 +44,10 @@ impl FromStr for RuleSelector {
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "ALL" {
Ok(Self::All)
} else if s == "C" {
Ok(Self::C)
} else if s == "T" {
Ok(Self::T)
} else {
let (s, redirected_from) = match get_redirect(s) {
Some((from, target)) => (target, Some(from)),
@@ -70,6 +82,8 @@ impl RuleSelector {
pub fn prefix_and_code(&self) -> (&'static str, &'static str) {
match self {
RuleSelector::All => ("", "ALL"),
RuleSelector::C => ("", "C"),
RuleSelector::T => ("", "T"),
RuleSelector::Prefix { prefix, .. } => {
(prefix.linter().common_prefix(), prefix.short_code())
}
@@ -138,6 +152,16 @@ impl IntoIterator for &RuleSelector {
fn into_iter(self) -> Self::IntoIter {
match self {
RuleSelector::All => RuleSelectorIter::All(Rule::iter()),
RuleSelector::C => RuleSelectorIter::Chain(
Linter::Flake8Comprehensions
.into_iter()
.chain(Linter::McCabe.into_iter()),
),
RuleSelector::T => RuleSelectorIter::Chain(
Linter::Flake8Debugger
.into_iter()
.chain(Linter::Flake8Print.into_iter()),
),
RuleSelector::Linter(linter) => RuleSelectorIter::Vec(linter.into_iter()),
RuleSelector::Prefix { prefix, .. } => RuleSelectorIter::Vec(prefix.into_iter()),
}
@@ -146,6 +170,7 @@ impl IntoIterator for &RuleSelector {
pub enum RuleSelectorIter {
All(RuleIter),
Chain(std::iter::Chain<std::vec::IntoIter<Rule>, std::vec::IntoIter<Rule>>),
Vec(std::vec::IntoIter<Rule>),
}
@@ -155,13 +180,14 @@ impl Iterator for RuleSelectorIter {
fn next(&mut self) -> Option<Self::Item> {
match self {
RuleSelectorIter::All(iter) => iter.next(),
RuleSelectorIter::Chain(iter) => iter.next(),
RuleSelectorIter::Vec(iter) => iter.next(),
}
}
}
/// A const alternative to the `impl From<RuleCodePrefix> for RuleSelector`
// to let us keep the fields of RuleSelector private.
/// to let us keep the fields of [`RuleSelector`] private.
// Note that Rust doesn't yet support `impl const From<RuleCodePrefix> for
// RuleSelector` (see https://github.com/rust-lang/rust/issues/67792).
// TODO(martin): Remove once RuleSelector is an enum with Linter & Rule variants
@@ -177,7 +203,7 @@ impl JsonSchema for RuleSelector {
"RuleSelector".to_string()
}
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> Schema {
Schema::Object(SchemaObject {
instance_type: Some(InstanceType::String.into()),
enum_values: Some(
@@ -221,6 +247,8 @@ impl RuleSelector {
pub(crate) fn specificity(&self) -> Specificity {
match self {
RuleSelector::All => Specificity::All,
RuleSelector::T => Specificity::LinterGroup,
RuleSelector::C => Specificity::LinterGroup,
RuleSelector::Linter(..) => Specificity::Linter,
RuleSelector::Prefix { prefix, .. } => {
let prefix: &'static str = prefix.short_code();
@@ -240,6 +268,7 @@ impl RuleSelector {
#[derive(EnumIter, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum Specificity {
All,
LinterGroup,
Linter,
Code1Char,
Code2Chars,
@@ -248,6 +277,7 @@ pub(crate) enum Specificity {
Code5Chars,
}
#[cfg(feature = "clap")]
mod clap_completion {
use clap::builder::{PossibleValue, TypedValueParser, ValueParserFactory};
use strum::IntoEnumIterator;
@@ -287,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

@@ -108,7 +108,7 @@ pub fn bad_file_permissions(
.map_or(false, |call_path| call_path.as_slice() == ["os", "chmod"])
{
let call_args = SimpleCallArgs::new(args, keywords);
if let Some(mode_arg) = call_args.get_argument("mode", Some(1)) {
if let Some(mode_arg) = call_args.argument("mode", 1) {
if let Some(int_value) = get_int_value(mode_arg) {
if (int_value & WRITE_WORLD > 0) || (int_value & EXECUTE_GROUP > 0) {
checker.diagnostics.push(Diagnostic::new(

View File

@@ -25,7 +25,7 @@ impl Violation for HashlibInsecureHashFunction {
const WEAK_HASHES: [&str; 4] = ["md4", "md5", "sha", "sha1"];
fn is_used_for_security(call_args: &SimpleCallArgs) -> bool {
match call_args.get_argument("usedforsecurity", None) {
match call_args.keyword_argument("usedforsecurity") {
Some(expr) => !matches!(
&expr.node,
ExprKind::Constant {
@@ -67,7 +67,7 @@ pub fn hashlib_insecure_hash_functions(
return;
}
if let Some(name_arg) = call_args.get_argument("name", Some(0)) {
if let Some(name_arg) = call_args.argument("name", 0) {
if let Some(hash_func_name) = string_literal(name_arg) {
if WEAK_HASHES.contains(&hash_func_name.to_lowercase().as_str()) {
checker.diagnostics.push(Diagnostic::new(

View File

@@ -46,7 +46,7 @@ pub fn jinja2_autoescape_false(
{
let call_args = SimpleCallArgs::new(args, keywords);
if let Some(autoescape_arg) = call_args.get_argument("autoescape", None) {
if let Some(autoescape_arg) = call_args.keyword_argument("autoescape") {
match &autoescape_arg.node {
ExprKind::Constant {
value: Constant::Bool(true),

View File

@@ -33,7 +33,7 @@ pub fn logging_config_insecure_listen(
{
let call_args = SimpleCallArgs::new(args, keywords);
if call_args.get_argument("verify", None).is_none() {
if call_args.keyword_argument("verify").is_none() {
checker.diagnostics.push(Diagnostic::new(
LoggingConfigInsecureListen,
Range::from(func),

View File

@@ -56,7 +56,7 @@ pub fn request_with_no_cert_validation(
None
}) {
let call_args = SimpleCallArgs::new(args, keywords);
if let Some(verify_arg) = call_args.get_argument("verify", None) {
if let Some(verify_arg) = call_args.keyword_argument("verify") {
if let ExprKind::Constant {
value: Constant::Bool(false),
..

View File

@@ -44,7 +44,7 @@ pub fn request_without_timeout(
})
{
let call_args = SimpleCallArgs::new(args, keywords);
if let Some(timeout_arg) = call_args.get_argument("timeout", None) {
if let Some(timeout_arg) = call_args.keyword_argument("timeout") {
if let Some(timeout) = match &timeout_arg.node {
ExprKind::Constant {
value: value @ Constant::None,

View File

@@ -33,7 +33,7 @@ pub fn snmp_insecure_version(
})
{
let call_args = SimpleCallArgs::new(args, keywords);
if let Some(mp_model_arg) = call_args.get_argument("mpModel", None) {
if let Some(mp_model_arg) = call_args.keyword_argument("mpModel") {
if let ExprKind::Constant {
value: Constant::Int(value),
..

View File

@@ -39,7 +39,7 @@ pub fn unsafe_yaml_load(checker: &mut Checker, func: &Expr, args: &[Expr], keywo
.map_or(false, |call_path| call_path.as_slice() == ["yaml", "load"])
{
let call_args = SimpleCallArgs::new(args, keywords);
if let Some(loader_arg) = call_args.get_argument("Loader", Some(1)) {
if let Some(loader_arg) = call_args.argument("Loader", 1) {
if !checker
.ctx
.resolve_call_path(loader_arg)

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

@@ -1,6 +1,4 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Eq)]
pub enum DebuggerUsingType {
Call(String),
Import(String),

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
@@ -146,12 +146,12 @@ pub fn logging_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords:
);
// G001 - G004
if let Some(format_arg) = call_args.get_argument("msg", Some(0)) {
if let Some(format_arg) = call_args.argument("msg", 0) {
check_msg(checker, format_arg);
}
// 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,12 +1,18 @@
use itertools::Either::{Left, Right};
use std::collections::BTreeMap;
use std::iter;
use log::error;
use rustc_hash::FxHashSet;
use rustpython_parser::ast::{Boolop, Constant, Expr, ExprKind, Keyword, Stmt, StmtKind};
use rustpython_parser::ast::{
Boolop, Constant, Expr, ExprContext, ExprKind, Keyword, Stmt, StmtKind,
};
use ruff_diagnostics::{AlwaysAutofixableViolation, Violation};
use ruff_diagnostics::{Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::helpers::{match_trailing_comment, unparse_expr};
use ruff_python_ast::helpers::{create_expr, match_trailing_comment, unparse_expr};
use ruff_python_ast::types::{Range, RefEquality};
use ruff_python_stdlib::identifiers::is_identifier;
use ruff_python_stdlib::keyword::KWLIST;
@@ -120,12 +126,17 @@ pub struct SingleStartsEndsWith {
pub attr: String,
}
impl Violation for SingleStartsEndsWith {
impl AlwaysAutofixableViolation for SingleStartsEndsWith {
#[derive_message_formats]
fn message(&self) -> String {
let SingleStartsEndsWith { attr } = self;
format!("Call `{attr}` once with a `tuple`")
}
fn autofix_title(&self) -> String {
let SingleStartsEndsWith { attr } = self;
format!("Merge into a single `{attr}` call")
}
}
#[violation]
@@ -392,39 +403,116 @@ pub fn no_unnecessary_dict_kwargs(checker: &mut Checker, expr: &Expr, kwargs: &[
}
/// PIE810
pub fn single_starts_ends_with(checker: &mut Checker, values: &[Expr], node: &Boolop) {
if *node != Boolop::Or {
pub fn single_starts_ends_with(checker: &mut Checker, expr: &Expr) {
let ExprKind::BoolOp { op: Boolop::Or, values } = &expr.node else {
return;
}
};
// Given `foo.startswith`, insert ("foo", "startswith") into the set.
let mut seen = FxHashSet::default();
for expr in values {
if let ExprKind::Call {
let mut duplicates = BTreeMap::new();
for (index, call) in values.iter().enumerate() {
let ExprKind::Call {
func,
args,
keywords,
..
} = &expr.node
{
if !(args.len() == 1 && keywords.is_empty()) {
continue;
}
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if attr != "startswith" && attr != "endswith" {
continue;
}
if let ExprKind::Name { id, .. } = &value.node {
if !seen.insert((id, attr)) {
checker.diagnostics.push(Diagnostic::new(
SingleStartsEndsWith {
attr: attr.to_string(),
},
Range::from(value),
));
}
}
} = &call.node else {
continue
};
if !(args.len() == 1 && keywords.is_empty()) {
continue;
}
let ExprKind::Attribute { value, attr, .. } = &func.node else {
continue
};
if attr != "startswith" && attr != "endswith" {
continue;
}
let ExprKind::Name { id: arg_name, .. } = &value.node else {
continue
};
duplicates
.entry((attr.as_str(), arg_name.as_str()))
.or_insert_with(Vec::new)
.push(index);
}
// Generate a `Diagnostic` for each duplicate.
for ((attr_name, arg_name), indices) in duplicates {
if indices.len() > 1 {
let mut diagnostic = Diagnostic::new(
SingleStartsEndsWith {
attr: attr_name.to_string(),
},
Range::from(expr),
);
if checker.patch(diagnostic.kind.rule()) {
let words: Vec<&Expr> = indices
.iter()
.map(|index| &values[*index])
.map(|expr| {
let ExprKind::Call { func: _, args, keywords: _} = &expr.node else {
unreachable!("{}", format!("Indices should only contain `{attr_name}` calls"))
};
args.get(0)
.unwrap_or_else(|| panic!("`{attr_name}` should have one argument"))
})
.collect();
let call = create_expr(ExprKind::Call {
func: Box::new(create_expr(ExprKind::Attribute {
value: Box::new(create_expr(ExprKind::Name {
id: arg_name.to_string(),
ctx: ExprContext::Load,
})),
attr: attr_name.to_string(),
ctx: ExprContext::Load,
})),
args: vec![create_expr(ExprKind::Tuple {
elts: words
.iter()
.flat_map(|value| {
if let ExprKind::Tuple { elts, .. } = &value.node {
Left(elts.iter())
} else {
Right(iter::once(*value))
}
})
.map(Clone::clone)
.collect(),
ctx: ExprContext::Load,
})],
keywords: vec![],
});
// Generate the combined `BoolOp`.
let mut call = Some(call);
let bool_op = create_expr(ExprKind::BoolOp {
op: Boolop::Or,
values: values
.iter()
.enumerate()
.filter_map(|(index, elt)| {
if indices.contains(&index) {
std::mem::take(&mut call)
} else {
Some(elt.clone())
}
})
.collect(),
});
diagnostic.amend(Fix::replacement(
unparse_expr(&bool_op, checker.stylist),
expr.location,
expr.end_location.unwrap(),
));
}
checker.diagnostics.push(diagnostic);
}
}
}

View File

@@ -5,53 +5,101 @@ expression: diagnostics
- kind:
name: SingleStartsEndsWith
body: "Call `startswith` once with a `tuple`"
suggestion: ~
fixable: false
suggestion: "Merge into a single `startswith` call"
fixable: true
location:
row: 2
column: 25
column: 0
end_location:
row: 2
column: 28
fix: ~
column: 46
fix:
content: "obj.startswith((\"foo\", \"bar\"))"
location:
row: 2
column: 0
end_location:
row: 2
column: 46
parent: ~
- kind:
name: SingleStartsEndsWith
body: "Call `endswith` once with a `tuple`"
suggestion: ~
fixable: false
suggestion: "Merge into a single `endswith` call"
fixable: true
location:
row: 4
column: 23
column: 0
end_location:
row: 4
column: 26
fix: ~
column: 42
fix:
content: "obj.endswith((\"foo\", \"bar\"))"
location:
row: 4
column: 0
end_location:
row: 4
column: 42
parent: ~
- kind:
name: SingleStartsEndsWith
body: "Call `startswith` once with a `tuple`"
suggestion: ~
fixable: false
suggestion: "Merge into a single `startswith` call"
fixable: true
location:
row: 6
column: 23
column: 0
end_location:
row: 6
column: 26
fix: ~
column: 42
fix:
content: "obj.startswith((foo, bar))"
location:
row: 6
column: 0
end_location:
row: 6
column: 42
parent: ~
- kind:
name: SingleStartsEndsWith
body: "Call `startswith` once with a `tuple`"
suggestion: ~
fixable: false
suggestion: "Merge into a single `startswith` call"
fixable: true
location:
row: 8
column: 23
column: 0
end_location:
row: 8
column: 26
fix: ~
column: 44
fix:
content: "obj.startswith((foo, \"foo\"))"
location:
row: 8
column: 0
end_location:
row: 8
column: 44
parent: ~
- kind:
name: SingleStartsEndsWith
body: "Call `startswith` once with a `tuple`"
suggestion: "Merge into a single `startswith` call"
fixable: true
location:
row: 10
column: 0
end_location:
row: 10
column: 65
fix:
content: "obj.endswith(foo) or obj.startswith((foo, \"foo\"))"
location:
row: 10
column: 0
end_location:
row: 10
column: 65
parent: ~

View File

@@ -1,7 +1,6 @@
use std::fmt;
use rustpython_parser::ast::{Expr, ExprKind};
use serde::{Deserialize, Serialize};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
@@ -9,7 +8,7 @@ use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Eq)]
pub enum VarKind {
TypeVar,
ParamSpec,

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"],
@@ -61,42 +74,74 @@ fn is_valid_default_value_with_annotation(default: &Expr, checker: &Checker) ->
value: Constant::Bytes(..),
..
} => return checker.locator.slice(default).len() <= 50,
// Ex) `123`, `True`, `False`, `3.14`
ExprKind::Constant {
value: Constant::Int(..),
value: Constant::Int(..) | Constant::Bool(..) | Constant::Float(..),
..
} => {
return checker.locator.slice(default).len() <= 10;
}
// Ex) `2j`
ExprKind::Constant {
value: Constant::Complex { real, .. },
..
} => {
if *real == 0.0 {
return checker.locator.slice(default).len() <= 10;
}
}
ExprKind::UnaryOp {
op: Unaryop::USub,
operand,
} => {
// Ex) `-1`, `-3.14`
if let ExprKind::Constant {
value: Constant::Int(..),
value: Constant::Int(..) | Constant::Float(..),
..
} = &operand.node
{
return checker.locator.slice(operand).len() <= 10;
}
// Ex) `-2j`
if let ExprKind::Constant {
value: Constant::Complex { real, .. },
..
} = &operand.node
{
if *real == 0.0 {
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,
op: Operator::Add | Operator::Sub,
right,
} => {
// 1 + 2j
// 1 - 2j
// -1 - 2j
// -1 + 2j
// Ex) `1 + 2j`, `1 - 2j`, `-1 - 2j`, `-1 + 2j`
if let ExprKind::Constant {
value: Constant::Complex { .. },
..
} = right.node
{
// 1 + 2j
// 1 - 2j
// Ex) `1 + 2j`, `1 - 2j`
if let ExprKind::Constant {
value: Constant::Int(..),
value: Constant::Int(..) | Constant::Float(..),
..
} = &left.node
{
@@ -106,10 +151,9 @@ fn is_valid_default_value_with_annotation(default: &Expr, checker: &Checker) ->
operand,
} = &left.node
{
// -1 + 2j
// -1 - 2j
// Ex) `-1 + 2j`, `-1 - 2j`
if let ExprKind::Constant {
value: Constant::Int(..),
value: Constant::Int(..) | Constant::Float(..),
..
} = &operand.node
{
@@ -118,14 +162,15 @@ fn is_valid_default_value_with_annotation(default: &Expr, checker: &Checker) ->
}
}
}
// `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)
})
{
@@ -148,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);
}
}
}
@@ -167,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

@@ -22,7 +22,7 @@ impl Violation for FailWithoutMessage {
pub fn fail_call(checker: &mut Checker, func: &Expr, args: &[Expr], keywords: &[Keyword]) {
if is_pytest_fail(func, checker) {
let call_args = SimpleCallArgs::new(args, keywords);
let msg = call_args.get_argument("msg", Some(0));
let msg = call_args.argument("msg", 0);
if let Some(msg) = msg {
if is_empty_or_null_string(msg) {

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

@@ -68,11 +68,11 @@ fn check_patch_call(
new_arg_number: usize,
) -> Option<Diagnostic> {
let simple_args = SimpleCallArgs::new(args, keywords);
if simple_args.get_argument("return_value", None).is_some() {
if simple_args.keyword_argument("return_value").is_some() {
return None;
}
if let Some(new_arg) = simple_args.get_argument("new", Some(new_arg_number)) {
if let Some(new_arg) = simple_args.argument("new", new_arg_number) {
if let ExprKind::Lambda { args, body } = &new_arg.node {
// Walk the lambda body.
let mut visitor = LambdaBodyVisitor {

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

@@ -1,8 +1,6 @@
use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Eq)]
pub enum Branch {
Elif,
Else,

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