Compare commits

...

92 Commits

Author SHA1 Message Date
colin99d
6c2d430430 Updated output format 2023-03-21 13:31:49 -04:00
colin99d
b0242ccfa1 Updated tests 2023-03-21 12:22:38 -04:00
colin99d
8ffc77d0ee Got tests working 2023-03-21 12:22:38 -04:00
colin99d
adc677bc9c Got tests working 2023-03-21 12:22:27 -04:00
colin99d
213e83aff2 Made small changes 2023-03-21 12:22:00 -04:00
colin99d
4b91826a0b Everything ready 2023-03-21 12:21:41 -04:00
Charlie Marsh
27903cdb11 Replace logical_lines feature with debug_assertions (#3648) 2023-03-21 12:16:41 -04:00
Charlie Marsh
3b1709ba1e Avoid attempting infinite open fix with re-bound builtin (#3650) 2023-03-21 15:32:31 +00:00
Dhruv Manilawala
33394e4a69 docs: all flake8-comprehension rules (#3631) 2023-03-21 14:28:19 +00:00
Charlie Marsh
7b9bdc494a Consider same-site fixes to be overlapping (#3638) 2023-03-21 10:09:47 -04:00
James Greenhill
b06ca25421 Add PostHog to users of Ruff in README (#3641) 2023-03-21 10:05:35 -04:00
Jonathan Plasse
c42f8b93d2 Add Swatinem/rust-cache to benchmark-compare job (#3637) 2023-03-21 14:45:09 +01:00
Micha Reiser
f59a22b6e5 Remove unused dependencies (#3644) 2023-03-21 11:02:41 +01:00
Jonathan Plasse
b5edc6dfc9 Add autofix functionality for F523 (#3613) 2023-03-21 03:55:23 +00:00
Charlie Marsh
626169e2ef Avoid raising PEP 604 errors with forward-referenced members (#3640) 2023-03-20 23:49:41 -04:00
Charlie Marsh
e9f359ac5e Convert single-argument %-style format calls (#3600) 2023-03-21 03:35:10 +00:00
Jacob Latonis
318c2c80e2 pylint: Implement binary-op-exception (PLW0711) (#3639) 2023-03-21 03:33:40 +00:00
Jonathan Plasse
92aa3a8178 Use language: system for Rust hooks (#3616) 2023-03-20 22:44:21 -04:00
Jonathan Plasse
22a4ab51f9 Handle UP032 autofix with adjacent keywords (#3636) 2023-03-21 00:17:45 +00:00
Jonathan Plasse
f70a49ed8b Add autofix for magic methods (ANN204) (#3633) 2023-03-20 19:19:20 -04:00
Charlie Marsh
f039bf36a2 Avoid trimming escaped whitespace in D210 (#3635) 2023-03-20 17:17:42 -04:00
Jonathan Plasse
169dd72328 Fix TRY300 false positive (#3634) 2023-03-20 20:55:28 +00:00
Jonathan Plasse
fd39ec4bdd Merge Availability and AutofixKind (#3629) 2023-03-20 16:45:33 +00:00
Charlie Marsh
7c0f17279c Flag PEP 585 and PEP 604 violations in quoted annotations (#3593) 2023-03-20 11:15:44 -04:00
konstin
81d0884974 Add basic jupyter notebook support (#3440)
* Add basic jupyter notebook support behind a feature flag

* Address review comments

* Rename in separate commit to make both git and clippy happy

* cfg(feature = "jupyter_notebook") another test

* Address more review comments

* Address more review comments

* and clippy and windows

* More review comment
2023-03-20 12:06:01 +01:00
Jacob Latonis
a45753f462 [pylint]: Implement assert-on-string-literal (W0129) (#3610) 2023-03-19 23:45:51 -04:00
Zhengbo Wang
b08326162b Doc/CLN: pass pre-commit (#3604) 2023-03-19 19:20:11 +00:00
Dhruv Manilawala
3a65af4dae feat: update C416 with dict comprehension (autofixable) (#3605) 2023-03-19 18:37:28 +00:00
Ville Lindholm
474aa0b196 Fix infinite loop due to rules D207 & W605 (#3609) 2023-03-19 18:29:13 +00:00
Charlie Marsh
4892167217 Avoid panics for implicitly-concatenated docstrings (#3584)
## Summary

In the rare event that a docstring contains an implicit string concatenation, we currently have the potential to panic, because we assume that if a string starts with triple quotes, it _ends_ with triple quotes. But with implicit concatenation, that's not the case: a single `Expr` could start and end with different quote styles, because it can contain multiple string tokens.

Supporting these "properly" is pretty hard. In some cases it's hard to even know what the "right" behavior is. So for now, I'm just detecting and warning, which is better than a panic.

Closes #3543.

Closes #3585.
2023-03-19 14:16:50 -04:00
Micha Reiser
a5494b8541 Bitflag based RuleSet (#3606) 2023-03-19 17:09:06 +01:00
Micha Reiser
9ac9a1c69e Gracefully handle lint panics (#3509) 2023-03-19 17:08:38 +01:00
Rogdham
f06dff8af8 Change broken links in README to beta.ruff.rs (#3607) 2023-03-19 15:17:44 +00:00
Charlie Marsh
fe7443ce2f Use any_enabled in AST checker (#3601) 2023-03-19 10:44:33 -04:00
Henry Schreiner
4bdb2dd362 ci(check_ecosystem): add PyPa/build (#3569) 2023-03-18 19:09:22 -04:00
Henry Schreiner
53a4743631 ci: fix check_ecosystem (#3602) 2023-03-18 19:03:08 -04:00
Charlie Marsh
4ffcd8366a Rename a variety of rules to match updated conventions (#3283) 2023-03-18 17:35:59 -04:00
Charlie Marsh
dfb772c6f1 Avoid removing comment hash for noqa's with trailing content (#3589) 2023-03-18 18:48:52 +00:00
Jonathan Plasse
c21eb06922 Fix D417 false positive (#3596) 2023-03-18 13:14:03 -04:00
Charlie Marsh
16a350c731 Reduce usage of ALL in ecosystem CI (#3590) 2023-03-18 13:13:09 -04:00
Charlie Marsh
fa04861724 Check exclusions prior to resolving pyproject.toml files (#3588) 2023-03-18 13:12:49 -04:00
Micha Reiser
404504ab41 CI Checks: Fix malformed markdown (#3595)
The Benchmark results aren't formatted properly if the ecosystem check finds differences because the ecosystem check doesn't emit a trailing newline.

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

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

@@ -0,0 +1,135 @@
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@v2
- 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 cargo-binstall"
uses: taiki-e/install-action@cargo-binstall
- name: "Install critcmp"
run: cargo binstall critcmp -y
- name: "Linux | Download PR benchmark results"
uses: actions/download-artifact@v3
with:
name: benchmark-results-ubuntu-latest
path: ./target/criterion
- name: "Linux | Compare benchmark results"
shell: bash
run: |
echo "### Benchmark" >> summary.md
echo "#### Linux" >> summary.md
echo "\`\`\`" >> summary.md
critcmp main pr >> summary.md
echo "\`\`\`" >> summary.md
echo "" >> summary.md
- name: "Linux | Cleanup benchmark results"
run: rm -rf ./target/criterion
- name: "Windows | Download PR benchmark results"
uses: actions/download-artifact@v3
with:
name: benchmark-results-windows-latest
path: ./target/criterion
- name: "Windows | Compare benchmark results"
shell: bash
run: |
echo "#### Windows" >> summary.md
echo "\`\`\`" >> summary.md
critcmp main pr >> summary.md
echo "\`\`\`" >> summary.md
echo "" >> summary.md
echo ${{ github.event.pull_request.number }} > pr-number
cat summary.md > $GITHUB_STEP_SUMMARY
- uses: actions/upload-artifact@v3
name: Upload PR Number
with:
name: pr-number
path: pr-number
- uses: actions/upload-artifact@v3
name: Upload Summary
with:
name: summary
path: summary.md

View File

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

View File

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

View File

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

View File

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

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

@@ -0,0 +1,84 @@
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
echo "" >> $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
@@ -28,17 +28,17 @@ repos:
- id: cargo-fmt
name: cargo fmt
entry: cargo fmt --
language: rust
language: system
types: [rust]
- id: clippy
name: clippy
entry: cargo clippy --workspace --all-targets --all-features -- -D warnings
language: rust
language: system
pass_filenames: false
- id: ruff
name: ruff
entry: cargo run -p ruff_cli -- check --no-cache --force-exclude --fix --exit-non-zero-on-fix
language: rust
language: system
types_or: [python, pyi]
require_serial: true
exclude: |
@@ -49,7 +49,7 @@ repos:
- id: dev-generate-all
name: dev-generate-all
entry: cargo dev generate-all
language: rust
language: system
pass_filenames: false
exclude: target

View File

@@ -145,4 +145,4 @@ default.
`pyproject.toml` files are now resolved hierarchically, such that for each Python file, we find
the first `pyproject.toml` file in its path, and use that to determine its lint settings.
See the [README](https://github.com/charliermarsh/ruff#pyprojecttoml-discovery) for more.
See the [documentation](https://beta.ruff.rs/docs/configuration/#python-file-discovery) for more.

41
Cargo.lock generated
View File

@@ -313,13 +313,14 @@ dependencies = [
[[package]]
name = "clap_complete_command"
version = "0.4.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4160b4a4f72ef58bd766bad27c09e6ef1cc9d82a22f6a0f55d152985a4a48e31"
checksum = "183495371ea78d4c9ff638bfc6497d46fed2396e4f9c50aebc1278a4a9919a3d"
dependencies = [
"clap 4.1.8",
"clap_complete",
"clap_complete_fig",
"clap_complete_nushell",
]
[[package]]
@@ -332,6 +333,16 @@ dependencies = [
"clap_complete",
]
[[package]]
name = "clap_complete_nushell"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fa41f5e6aa83bd151b70fd0ceaee703d68cd669522795dc812df9edad1252c"
dependencies = [
"clap 4.1.8",
"clap_complete",
]
[[package]]
name = "clap_derive"
version = "4.1.8"
@@ -769,7 +780,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.255"
version = "0.0.257"
dependencies = [
"anyhow",
"clap 4.1.8",
@@ -1971,7 +1982,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.255"
version = "0.0.257"
dependencies = [
"anyhow",
"bisection",
@@ -1979,7 +1990,6 @@ dependencies = [
"chrono",
"clap 4.1.8",
"colored",
"criterion",
"dirs",
"fern",
"glob",
@@ -2014,6 +2024,7 @@ dependencies = [
"schemars",
"semver",
"serde",
"serde_json",
"shellexpand",
"smallvec",
"strum",
@@ -2024,6 +2035,21 @@ dependencies = [
"toml",
]
[[package]]
name = "ruff_benchmark"
version = "0.0.0"
dependencies = [
"criterion",
"mimalloc",
"once_cell",
"ruff",
"serde",
"serde_json",
"tikv-jemallocator",
"ureq",
"url",
]
[[package]]
name = "ruff_cache"
version = "0.0.0"
@@ -2037,7 +2063,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.255"
version = "0.0.257"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2065,6 +2091,7 @@ dependencies = [
"ruff",
"ruff_cache",
"ruff_diagnostics",
"ruff_python_stdlib",
"rustc-hash",
"serde",
"serde_json",
@@ -2184,7 +2211,6 @@ name = "ruff_python_stdlib"
version = "0.0.0"
dependencies = [
"once_cell",
"regex",
"rustc-hash",
]
@@ -2479,6 +2505,7 @@ version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",

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" }
@@ -33,7 +37,7 @@ rustpython-parser = { features = [
], 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" }
serde_json = { version = "1.0.93", features = ["preserve_order"] }
shellexpand = { version = "3.0.0" }
similar = { version = "2.2.1" }
strum = { version = "0.24.1", features = ["strum_macros"] }
@@ -44,8 +48,7 @@ textwrap = { version = "0.16.0" }
toml = { version = "0.7.2" }
[profile.release]
panic = "abort"
lto = "thin"
lto = "fat"
codegen-units = 1
opt-level = 3
@@ -59,3 +62,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

26
LICENSE
View File

@@ -393,7 +393,6 @@ are:
THE SOFTWARE.
"""
- autoflake, licensed as follows:
"""
Copyright (C) 2012-2018 Steven Myint
@@ -417,6 +416,31 @@ are:
SOFTWARE.
"""
- autotyping, licensed as follows:
"""
MIT License
Copyright (c) 2023 Jelle Zijlstra
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- Flake8, licensed as follows:
"""
== Flake8 License (MIT) ==

View File

@@ -137,7 +137,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.255'
rev: 'v0.0.257'
hooks:
- id: ruff
```
@@ -227,6 +227,54 @@ stylistic rules made obsolete by the use of an autoformatter, like
If you're just getting started with Ruff, **the default rule set is a great place to start**: it
catches a wide variety of common errors (like unused imports) with zero configuration.
Beyond the defaults, Ruff re-implements some of the most popular Flake8 plugins and related code
quality tools, including:
- [autoflake](https://pypi.org/project/autoflake/)
- [eradicate](https://pypi.org/project/eradicate/)
- [flake8-2020](https://pypi.org/project/flake8-2020/)
- [flake8-annotations](https://pypi.org/project/flake8-annotations/)
- [flake8-bandit](https://pypi.org/project/flake8-bandit/) ([#1646](https://github.com/charliermarsh/ruff/issues/1646))
- [flake8-blind-except](https://pypi.org/project/flake8-blind-except/)
- [flake8-boolean-trap](https://pypi.org/project/flake8-boolean-trap/)
- [flake8-bugbear](https://pypi.org/project/flake8-bugbear/)
- [flake8-builtins](https://pypi.org/project/flake8-builtins/)
- [flake8-commas](https://pypi.org/project/flake8-commas/)
- [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/)
- [flake8-datetimez](https://pypi.org/project/flake8-datetimez/)
- [flake8-debugger](https://pypi.org/project/flake8-debugger/)
- [flake8-django](https://pypi.org/project/flake8-django/) ([#2817](https://github.com/charliermarsh/ruff/issues/2817))
- [flake8-docstrings](https://pypi.org/project/flake8-docstrings/)
- [flake8-eradicate](https://pypi.org/project/flake8-eradicate/)
- [flake8-errmsg](https://pypi.org/project/flake8-errmsg/)
- [flake8-executable](https://pypi.org/project/flake8-executable/)
- [flake8-implicit-str-concat](https://pypi.org/project/flake8-implicit-str-concat/)
- [flake8-import-conventions](https://github.com/joaopalmeiro/flake8-import-conventions)
- [flake8-logging-format](https://pypi.org/project/flake8-logging-format/)
- [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420)
- [flake8-pie](https://pypi.org/project/flake8-pie/)
- [flake8-print](https://pypi.org/project/flake8-print/)
- [flake8-pyi](https://pypi.org/project/flake8-pyi/)
- [flake8-pytest-style](https://pypi.org/project/flake8-pytest-style/)
- [flake8-quotes](https://pypi.org/project/flake8-quotes/)
- [flake8-raise](https://pypi.org/project/flake8-raise/)
- [flake8-return](https://pypi.org/project/flake8-return/)
- [flake8-self](https://pypi.org/project/flake8-self/)
- [flake8-simplify](https://pypi.org/project/flake8-simplify/)
- [flake8-super](https://pypi.org/project/flake8-super/)
- [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/)
- [flake8-type-checking](https://pypi.org/project/flake8-type-checking/)
- [flake8-use-pathlib](https://pypi.org/project/flake8-use-pathlib/)
- [isort](https://pypi.org/project/isort/)
- [mccabe](https://pypi.org/project/mccabe/)
- [pandas-vet](https://pypi.org/project/pandas-vet/)
- [pep8-naming](https://pypi.org/project/pep8-naming/)
- [pydocstyle](https://pypi.org/project/pydocstyle/)
- [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks) ([#980](https://github.com/charliermarsh/ruff/issues/980))
- [pyupgrade](https://pypi.org/project/pyupgrade/)
- [tryceratops](https://pypi.org/project/tryceratops/)
- [yesqa](https://pypi.org/project/yesqa/)
<!-- End section: Rules -->
For a complete enumeration of the supported rules, see [_Rules_](https://beta.ruff.rs/docs/rules/).
@@ -280,6 +328,7 @@ Ruff is used in a number of major open-source projects, including:
- [Zulip](https://github.com/zulip/zulip)
- [Bokeh](https://github.com/bokeh/bokeh)
- [Pydantic](https://github.com/pydantic/pydantic)
- [PostHog](https://github.com/PostHog/posthog)
- [Dagster](https://github.com/dagster-io/dagster)
- [Dagger](https://github.com/dagger/dagger)
- [Sphinx](https://github.com/sphinx-doc/sphinx)
@@ -309,6 +358,10 @@ Ruff is used in a number of major open-source projects, including:
- [Starlite](https://github.com/starlite-api/starlite)
- [telemetry-airflow (Mozilla)](https://github.com/mozilla/telemetry-airflow)
- [Stable Baselines3](https://github.com/DLR-RM/stable-baselines3)
- [PaddlePaddle](https://github.com/PaddlePaddle/Paddle)
- [nox](https://github.com/wntrblm/nox)
- [Neon](https://github.com/neondatabase/neon)
- [The Algorithms](https://github.com/TheAlgorithms/Python)
## License

View File

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

View File

@@ -85,8 +85,9 @@ flake8-to-ruff path/to/.flake8 --plugin flake8-builtins --plugin flake8-quotes
ignore unsupported options in the `.flake8` file (or equivalent). (Similarly, Ruff has a few
configuration options that don't exist in Flake8.)
1. Ruff will omit any rule codes that are unimplemented or unsupported by Ruff, including rule
codes from unsupported plugins. (See the [Ruff README](https://github.com/charliermarsh/ruff#user-content-how-does-ruff-compare-to-flake8)
for the complete list of supported plugins.)
codes from unsupported plugins. (See the
[documentation](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) for the complete
list of supported plugins.)
## License

View File

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

View File

@@ -2,3 +2,6 @@ avoid-*
do-not-*
uses-*
*-used
rewrite-*
prefer-*
consider-*

View File

@@ -0,0 +1,42 @@
class Foo:
def __str__(self):
...
def __repr__(self):
...
def __len__(self):
...
def __length_hint__(self):
...
def __init__(self):
...
def __del__(self):
...
def __bool__(self):
...
def __bytes__(self):
...
def __format__(self, format_spec):
...
def __contains__(self, item):
...
def __complex__(self):
...
def __int__(self):
...
def __float__(self):
...
def __index__(self):
...

View File

@@ -0,0 +1,6 @@
import pickle
from telnetlib import Telnet
pickle.loads()
Telnet("localhost", 23)

View File

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

View File

@@ -1,6 +1,19 @@
x = [1, 2, 3]
y = [("a", 1), ("b", 2), ("c", 3)]
z = [(1,), (2,), (3,)]
d = {"a": 1, "b": 2, "c": 3}
[i for i in x]
{i for i in x}
{k: v for k, v in y}
{k: v for k, v in d.items()}
[i for i, in z]
[i for i, j in y]
[i for i in x if i > 1]
[i for i in x for j in x]
{v: k for k, v in y}
{k.foo: k for k in y}
{k["foo"]: k for k in y}
{k: v if v else None for k, v in y}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,32 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"print(paste(\"Hello\",\"WoRld\"))\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "R",
"language": "R",
"name": "ir"
},
"language_info": {
"codemirror_mode": "r",
"file_extension": ".r",
"mimetype": "text/x-r-source",
"name": "R",
"pygments_lexer": "r",
"version": "3.2.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env python
# coding: utf-8
# In[1]:
def unused_variable():
x = 1
y = 2
print(f"cell one: {y}")
unused_variable()
# Let's do another mistake
# In[2]:
def mutable_argument(z=set()):
print(f"cell two: {z}")
mutable_argument()

View File

@@ -0,0 +1 @@
broken "§=($/=(")

View File

@@ -0,0 +1,88 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"cell one: 2\n"
]
}
],
"source": [
"def unused_variable():\n",
" x = 1\n",
" y = 2\n",
" print(f\"cell one: {y}\")\n",
"\n",
"unused_variable()"
],
"metadata": {
"collapsed": false,
"ExecuteTime": {
"start_time": "2023-03-08T23:01:09.705831Z",
"end_time": "2023-03-08T23:01:09.782916Z"
}
}
},
{
"cell_type": "markdown",
"source": [
"Let's do another mistake"
],
"metadata": {
"collapsed": false
}
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": true,
"ExecuteTime": {
"start_time": "2023-03-08T23:01:09.733809Z",
"end_time": "2023-03-08T23:01:09.915760Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"cell two: set()\n"
]
}
],
"source": [
"def mutable_argument(z=set()):\n",
" print(f\"cell two: {z}\")\n",
"\n",
"mutable_argument()\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

View File

@@ -0,0 +1 @@
{}

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

@@ -113,3 +113,13 @@ def f(x, *args, **kwargs):
**kwargs: keyword arguments
"""
return x
class Test:
def f(self, /, arg1: int) -> None:
"""
Some beauty description.
Args:
arg1: some description of arg
"""

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')")
@@ -495,3 +504,12 @@ Testing this incorrectly indented docstring.
x: Test argument.
"""
def implicit_string_concatenation():
"""Toggle the gizmo.
Returns
A value of some sort.
""""Extra content"

View File

@@ -4,6 +4,7 @@
"{1:{0}}".format(1, 2) # No issues
"{1:{0}}".format(1, 2, 3) # F523
"{0}{2}".format(1, 2) # F523, # F524
"{1.arg[1]!r:0{2['arg']}{1}}".format(1, 2, 3, 4) # F523
# With no indexes
"{}".format(1, 2) # F523

View File

@@ -0,0 +1,24 @@
def test_division():
a = 9 / 3
assert "No ZeroDivisionError were raised" # [assert-on-string-literal]
def test_division():
a = 9 / 3
assert a == 3
try:
assert "bad" # [assert-on-string-literal]
except:
assert "bad again" # [assert-on-string-literal]
a = 12
assert f"hello {a}" # [assert-on-string-literal]
assert f"{a}" # [assert-on-string-literal]
assert f"" # [assert-on-string-literal]
assert "" # [assert-on-string-literal]
assert b"hello" # [assert-on-string-literal]
assert "", b"hi" # [assert-on-string-literal]
assert "WhyNotHere?", "HereIsOk" # [assert-on-string-literal]
assert 12, "ok here"

View File

@@ -0,0 +1,14 @@
try:
1 / 0
except ZeroDivisionError or ValueError as e: # [binary-op-exception]
pass
try:
raise ValueError
except ZeroDivisionError and ValueError as e: # [binary-op-exception]
print(e)
try:
pass
except (ValueError, Exception, IOError):
pass

View File

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

View File

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

Binary file not shown.

View File

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

View File

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

View File

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

View File

@@ -46,3 +46,8 @@ def f(x: Union[("str", "int"), float]) -> None:
def f() -> None:
x: Optional[str]
x = Optional[str]
x = Union[str, int]
x = Union["str", "int"]
x: Union[str, int]
x: Union["str", "int"]

View File

@@ -1,9 +1,9 @@
from io import open
with open("f.txt") as f:
print(f.read())
import io
with io.open("f.txt", mode="r", buffering=-1, **kwargs) as f:
print(f.read())
from io import open
with open("f.txt") as f:
print(f.read())

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

@@ -73,3 +73,13 @@ print("%s \N{snowman}" % (a,))
print("%(foo)s \N{snowman}" % {"foo": 1})
print(("foo %s " "bar %s") % (x, y))
# Single-value expressions
print('Hello %s' % "World")
print('Hello %s' % f"World")
print('Hello %s (%s)' % bar)
print('Hello %s (%s)' % bar.baz)
print('Hello %s (%s)' % bar['bop'])
print('Hello %(arg)s' % bar)
print('Hello %(arg)s' % bar.baz)
print('Hello %(arg)s' % bar['bop'])

View File

@@ -1,6 +1,4 @@
# OK
"%s" % unknown_type
b"%s" % (b"bytestring",)
"%*s" % (5, "hi")
@@ -57,3 +55,9 @@ pytest.param('"%8s" % (None,)', id="unsafe width-string conversion"),
"""
% (x,)
)
'Hello %s' % bar
'Hello %s' % bar.baz
'Hello %s' % bar['bop']

View File

@@ -86,3 +86,14 @@ async def c():
async def c():
return "{}".format(1 + await 3)
def d(osname, version, release):
return"{}-{}.{}".format(osname, version, release)
def e():
yield"{}".format(1)
assert"{}".format(1)

View File

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

View File

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

View File

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

View File

@@ -2,12 +2,24 @@
# noqa # comment
print() # noqa
print() # noqa # comment
print() # noqa # comment
print() # noqa comment
print() # noqa comment
print(a) # noqa
print(a) # noqa # comment
print(a) # noqa # comment
print(a) # noqa comment
print(a) # noqa comment
# noqa: E501, F821
# noqa: E501, F821 # comment
print() # noqa: E501, F821
print() # noqa: E501, F821 # comment
print() # noqa: E501, F821 # comment
print() # noqa: E501, F821 comment
print() # noqa: E501, F821 comment
print(a) # noqa: E501, F821
print(a) # noqa: E501, F821 # comment
print(a) # noqa: E501, F821 # comment
print(a) # noqa: E501, F821 comment
print(a) # noqa: E501, F821 comment

View File

@@ -45,3 +45,10 @@ def still_good():
return process()
except MyException:
logger.exception("process failed")
def good_noexcept():
try:
pass
return process()
finally:
logger.exception("process failed")

View File

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

View File

@@ -9,7 +9,7 @@ use ruff_python_ast::source_code::Locator;
use ruff_python_ast::types::Range;
use crate::linter::FixTable;
use crate::registry::AsRule;
use crate::registry::{AsRule, Rule};
pub mod helpers;
@@ -28,7 +28,7 @@ fn apply_fixes<'a>(
locator: &'a Locator<'a>,
) -> (String, FixTable) {
let mut output = String::with_capacity(locator.len());
let mut last_pos: Location = Location::new(1, 0);
let mut last_pos: Option<Location> = None;
let mut applied: BTreeSet<&Fix> = BTreeSet::default();
let mut fixed = FxHashMap::default();
@@ -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.
@@ -50,25 +50,25 @@ fn apply_fixes<'a>(
// Best-effort approach: if this fix overlaps with a fix we've already applied,
// skip it.
if last_pos > fix.location {
if last_pos.map_or(false, |last_pos| last_pos >= fix.location) {
continue;
}
// Add all contents from `last_pos` to `fix.location`.
let slice = locator.slice(Range::new(last_pos, fix.location));
let slice = locator.slice(Range::new(last_pos.unwrap_or_default(), fix.location));
output.push_str(slice);
// Add the patch itself.
output.push_str(&fix.content);
// Track that the fix was applied.
last_pos = fix.end_location;
last_pos = Some(fix.end_location);
applied.insert(fix);
*fixed.entry(rule).or_default() += 1;
}
// Add the remaining content.
let slice = locator.skip(last_pos);
let slice = locator.skip(last_pos.unwrap_or_default());
output.push_str(slice);
(output, fixed)
@@ -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;
@@ -101,14 +113,14 @@ mod tests {
use ruff_python_ast::source_code::Locator;
use crate::autofix::{apply_fix, apply_fixes};
use crate::rules::pycodestyle::rules::NoNewLineAtEndOfFile;
use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile;
fn create_diagnostics(fixes: impl IntoIterator<Item = Fix>) -> Vec<Diagnostic> {
fixes
.into_iter()
.map(|fix| Diagnostic {
// The choice of rule here is arbitrary.
kind: NoNewLineAtEndOfFile.into(),
kind: MissingNewlineAtEndOfFile.into(),
location: fix.location,
end_location: fix.end_location,
fix: Some(fix),
@@ -182,7 +194,7 @@ class A:
fn apply_two_removals() {
let locator = Locator::new(
r#"
class A(object, object):
class A(object, object, object):
...
"#
.trim(),
@@ -190,13 +202,13 @@ class A(object, object):
let diagnostics = create_diagnostics([
Fix {
content: String::new(),
location: Location::new(1, 7),
location: Location::new(1, 8),
end_location: Location::new(1, 16),
},
Fix {
content: String::new(),
location: Location::new(1, 16),
end_location: Location::new(1, 23),
location: Location::new(1, 22),
end_location: Location::new(1, 30),
},
]);
let (contents, fixed) = apply_fixes(diagnostics.iter(), &locator);
@@ -204,7 +216,7 @@ class A(object, object):
assert_eq!(
contents,
r#"
class A:
class A(object):
...
"#
.trim()

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

@@ -165,10 +165,10 @@ pub fn check_logical_lines(
}
}
#[cfg(feature = "logical_lines")]
let should_fix = autofix.into() && settings.rules.should_fix(&Rule::MissingWhitespace);
#[cfg(debug_assertions)]
let should_fix = autofix.into() && settings.rules.should_fix(Rule::MissingWhitespace);
#[cfg(not(feature = "logical_lines"))]
#[cfg(not(debug_assertions))]
let should_fix = false;
for diagnostic in missing_whitespace(&line.text, start_loc.row(), should_fix) {
@@ -179,11 +179,11 @@ pub fn check_logical_lines(
}
if line.flags.contains(TokenFlags::BRACKET) {
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
let should_fix =
autofix.into() && settings.rules.should_fix(&Rule::WhitespaceBeforeParameters);
autofix.into() && settings.rules.should_fix(Rule::WhitespaceBeforeParameters);
#[cfg(not(feature = "logical_lines"))]
#[cfg(not(debug_assertions))]
let should_fix = false;
for diagnostic in whitespace_before_parameters(&line.tokens, should_fix) {

View File

@@ -24,7 +24,7 @@ pub fn check_noqa(
settings: &Settings,
autofix: flags::Autofix,
) -> Vec<usize> {
let enforce_noqa = settings.rules.enabled(&Rule::UnusedNOQA);
let enforce_noqa = settings.rules.enabled(Rule::UnusedNOQA);
// Whether the file is exempted from all checks.
let mut file_exempted = false;
@@ -143,30 +143,26 @@ pub fn check_noqa(
match directive {
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();
let start_char = lines[row][..start_byte].chars().count();
let end_char =
start_char + lines[row][start_byte..end_byte].chars().count();
let mut diagnostic = Diagnostic::new(
UnusedNOQA { codes: None },
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
Range::new(
Location::new(row + 1, start_char),
Location::new(row + 1, end_char),
),
);
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
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),
));
}
diagnostic.amend(delete_noqa(
row,
lines[row],
leading_spaces,
start_byte,
end_byte,
trailing_spaces,
));
}
diagnostics.push(diagnostic);
}
@@ -188,7 +184,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);
@@ -207,8 +203,9 @@ pub fn check_noqa(
&& unknown_codes.is_empty()
&& unmatched_codes.is_empty())
{
let start = lines[row][..start_byte].chars().count();
let end = start + lines[row][start_byte..end_byte].chars().count();
let start_char = lines[row][..start_byte].chars().count();
let end_char =
start_char + lines[row][start_byte..end_byte].chars().count();
let mut diagnostic = Diagnostic::new(
UnusedNOQA {
@@ -227,32 +224,26 @@ pub fn check_noqa(
.collect(),
}),
},
Range::new(Location::new(row + 1, start), Location::new(row + 1, end)),
Range::new(
Location::new(row + 1, start_char),
Location::new(row + 1, end_char),
),
);
if autofix.into() && settings.rules.should_fix(diagnostic.kind.rule()) {
if valid_codes.is_empty() {
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),
));
}
diagnostic.amend(delete_noqa(
row,
lines[row],
leading_spaces,
start_byte,
end_byte,
trailing_spaces,
));
} else {
diagnostic.amend(Fix::replacement(
format!("# noqa: {}", valid_codes.join(", ")),
Location::new(row + 1, start),
Location::new(row + 1, end),
Location::new(row + 1, start_char),
Location::new(row + 1, end_char),
));
}
}
@@ -267,3 +258,42 @@ pub fn check_noqa(
ignored_diagnostics.sort_unstable();
ignored_diagnostics
}
/// Generate a [`Fix`] to delete a `noqa` directive.
fn delete_noqa(
row: usize,
line: &str,
leading_spaces: usize,
start_byte: usize,
end_byte: usize,
trailing_spaces: usize,
) -> Fix {
if start_byte - leading_spaces == 0 && end_byte == line.len() {
// Ex) `# noqa`
Fix::deletion(Location::new(row + 1, 0), Location::new(row + 2, 0))
} else if end_byte == line.len() {
// Ex) `x = 1 # noqa`
let start_char = line[..start_byte].chars().count();
let end_char = start_char + line[start_byte..end_byte].chars().count();
Fix::deletion(
Location::new(row + 1, start_char - leading_spaces),
Location::new(row + 1, end_char + trailing_spaces),
)
} else if line[end_byte..].trim_start().starts_with('#') {
// Ex) `x = 1 # noqa # type: ignore`
let start_char = line[..start_byte].chars().count();
let end_char = start_char + line[start_byte..end_byte].chars().count();
Fix::deletion(
Location::new(row + 1, start_char),
Location::new(row + 1, end_char + trailing_spaces),
)
} else {
// Ex) `x = 1 # noqa here`
let start_char = line[..start_byte].chars().count();
let end_char = start_char + line[start_byte..end_byte].chars().count();
Fix::deletion(
Location::new(row + 1, start_char + 1 + 1),
Location::new(row + 1, end_char + trailing_spaces),
)
}
}

View File

@@ -12,8 +12,8 @@ use crate::rules::flake8_executable::rules::{
shebang_missing, shebang_newline, shebang_not_executable, shebang_python, shebang_whitespace,
};
use crate::rules::pycodestyle::rules::{
doc_line_too_long, indentation_contains_tabs, line_too_long, mixed_spaces_and_tabs,
no_newline_at_end_of_file, trailing_whitespace,
doc_line_too_long, line_too_long, mixed_spaces_and_tabs, no_newline_at_end_of_file,
tab_indentation, trailing_whitespace,
};
use crate::rules::pygrep_hooks::rules::{blanket_noqa, blanket_type_ignore};
use crate::rules::pylint;
@@ -32,28 +32,28 @@ pub fn check_physical_lines(
let mut diagnostics: Vec<Diagnostic> = vec![];
let mut has_any_shebang = false;
let enforce_blanket_noqa = settings.rules.enabled(&Rule::BlanketNOQA);
let enforce_shebang_not_executable = settings.rules.enabled(&Rule::ShebangNotExecutable);
let enforce_shebang_missing = settings.rules.enabled(&Rule::ShebangMissingExecutableFile);
let enforce_shebang_whitespace = settings.rules.enabled(&Rule::ShebangWhitespace);
let enforce_shebang_newline = settings.rules.enabled(&Rule::ShebangNewline);
let enforce_shebang_python = settings.rules.enabled(&Rule::ShebangPython);
let enforce_blanket_type_ignore = settings.rules.enabled(&Rule::BlanketTypeIgnore);
let enforce_doc_line_too_long = settings.rules.enabled(&Rule::DocLineTooLong);
let enforce_line_too_long = settings.rules.enabled(&Rule::LineTooLong);
let enforce_no_newline_at_end_of_file = settings.rules.enabled(&Rule::NoNewLineAtEndOfFile);
let enforce_unnecessary_coding_comment = settings.rules.enabled(&Rule::UTF8EncodingDeclaration);
let enforce_mixed_spaces_and_tabs = settings.rules.enabled(&Rule::MixedSpacesAndTabs);
let enforce_bidirectional_unicode = settings.rules.enabled(&Rule::BidirectionalUnicode);
let enforce_trailing_whitespace = settings.rules.enabled(&Rule::TrailingWhitespace);
let enforce_blanket_noqa = settings.rules.enabled(Rule::BlanketNOQA);
let enforce_shebang_not_executable = settings.rules.enabled(Rule::ShebangNotExecutable);
let enforce_shebang_missing = settings.rules.enabled(Rule::ShebangMissingExecutableFile);
let enforce_shebang_whitespace = settings.rules.enabled(Rule::ShebangLeadingWhitespace);
let enforce_shebang_newline = settings.rules.enabled(Rule::ShebangNotFirstLine);
let enforce_shebang_python = settings.rules.enabled(Rule::ShebangMissingPython);
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::MissingNewlineAtEndOfFile);
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::BlankLineWithWhitespace);
let enforce_tab_indentation = settings.rules.enabled(Rule::TabIndentation);
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::ShebangLeadingWhitespace);
let mut commented_lines_iter = commented_lines.iter().peekable();
let mut doc_lines_iter = doc_lines.iter().peekable();
@@ -154,8 +154,8 @@ pub fn check_physical_lines(
}
}
if enforce_indentation_contains_tabs {
if let Some(diagnostic) = indentation_contains_tabs(index, line) {
if enforce_tab_indentation {
if let Some(diagnostic) = tab_indentation(index, line) {
diagnostics.push(diagnostic);
}
}
@@ -165,7 +165,7 @@ pub fn check_physical_lines(
if let Some(diagnostic) = no_newline_at_end_of_file(
locator,
stylist,
autofix.into() && settings.rules.should_fix(&Rule::NoNewLineAtEndOfFile),
autofix.into() && settings.rules.should_fix(Rule::MissingNewlineAtEndOfFile),
) {
diagnostics.push(diagnostic);
}

View File

@@ -8,7 +8,7 @@ use crate::registry::{AsRule, Rule};
use crate::rules::ruff::rules::Context;
use crate::rules::{
eradicate, flake8_commas, flake8_implicit_str_concat, flake8_pyi, flake8_quotes, pycodestyle,
pyupgrade, ruff,
pylint, pyupgrade, ruff,
};
use crate::settings::{flags, Settings};
use ruff_diagnostics::Diagnostic;
@@ -23,41 +23,43 @@ pub fn check_tokens(
) -> Vec<Diagnostic> {
let mut diagnostics: Vec<Diagnostic> = vec![];
let enforce_ambiguous_unicode_character = settings
.rules
.enabled(&Rule::AmbiguousUnicodeCharacterString)
|| settings
.rules
.enabled(&Rule::AmbiguousUnicodeCharacterDocstring)
|| settings
.rules
.enabled(&Rule::AmbiguousUnicodeCharacterComment);
let enforce_quotes = settings.rules.enabled(&Rule::BadQuotesInlineString)
|| settings.rules.enabled(&Rule::BadQuotesMultilineString)
|| settings.rules.enabled(&Rule::BadQuotesDocstring)
|| settings.rules.enabled(&Rule::AvoidableEscapedQuote);
let enforce_commented_out_code = settings.rules.enabled(&Rule::CommentedOutCode);
let enforce_compound_statements = settings
.rules
.enabled(&Rule::MultipleStatementsOnOneLineColon)
|| settings
.rules
.enabled(&Rule::MultipleStatementsOnOneLineSemicolon)
|| settings.rules.enabled(&Rule::UselessSemicolon);
let enforce_invalid_escape_sequence = settings.rules.enabled(&Rule::InvalidEscapeSequence);
let enforce_implicit_string_concatenation = settings
.rules
.enabled(&Rule::SingleLineImplicitStringConcatenation)
|| settings
.rules
.enabled(&Rule::MultiLineImplicitStringConcatenation);
let enforce_trailing_comma = settings.rules.enabled(&Rule::TrailingCommaMissing)
|| settings
.rules
.enabled(&Rule::TrailingCommaOnBareTupleProhibited)
|| settings.rules.enabled(&Rule::TrailingCommaProhibited);
let enforce_extraneous_parenthesis = settings.rules.enabled(&Rule::ExtraneousParentheses);
let enforce_type_comment_in_stub = settings.rules.enabled(&Rule::TypeCommentInStub);
let enforce_ambiguous_unicode_character = settings.rules.any_enabled(&[
Rule::AmbiguousUnicodeCharacterString,
Rule::AmbiguousUnicodeCharacterDocstring,
Rule::AmbiguousUnicodeCharacterComment,
]);
let enforce_invalid_string_character = settings.rules.any_enabled(&[
Rule::InvalidCharacterBackspace,
Rule::InvalidCharacterSub,
Rule::InvalidCharacterEsc,
Rule::InvalidCharacterNul,
Rule::InvalidCharacterZeroWidthSpace,
]);
let enforce_quotes = settings.rules.any_enabled(&[
Rule::BadQuotesInlineString,
Rule::BadQuotesMultilineString,
Rule::BadQuotesDocstring,
Rule::AvoidableEscapedQuote,
]);
let enforce_commented_out_code = settings.rules.enabled(Rule::CommentedOutCode);
let enforce_compound_statements = settings.rules.any_enabled(&[
Rule::MultipleStatementsOnOneLineColon,
Rule::MultipleStatementsOnOneLineSemicolon,
Rule::UselessSemicolon,
]);
let enforce_invalid_escape_sequence = settings.rules.enabled(Rule::InvalidEscapeSequence);
let enforce_implicit_string_concatenation = settings.rules.any_enabled(&[
Rule::SingleLineImplicitStringConcatenation,
Rule::MultiLineImplicitStringConcatenation,
]);
let enforce_trailing_comma = settings.rules.any_enabled(&[
Rule::MissingTrailingComma,
Rule::TrailingCommaOnBareTuple,
Rule::ProhibitedTrailingComma,
]);
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

@@ -26,67 +26,67 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
Some(match (linter, code) {
// pycodestyle errors
(Pycodestyle, "E101") => Rule::MixedSpacesAndTabs,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E111") => Rule::IndentationWithInvalidMultiple,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E112") => Rule::NoIndentedBlock,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E113") => Rule::UnexpectedIndentation,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E114") => Rule::IndentationWithInvalidMultipleComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E115") => Rule::NoIndentedBlockComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E116") => Rule::UnexpectedIndentationComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E117") => Rule::OverIndented,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E201") => Rule::WhitespaceAfterOpenBracket,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E202") => Rule::WhitespaceBeforeCloseBracket,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E203") => Rule::WhitespaceBeforePunctuation,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E211") => Rule::WhitespaceBeforeParameters,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E221") => Rule::MultipleSpacesBeforeOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E222") => Rule::MultipleSpacesAfterOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E223") => Rule::TabBeforeOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E224") => Rule::TabAfterOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E225") => Rule::MissingWhitespaceAroundOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E226") => Rule::MissingWhitespaceAroundArithmeticOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E227") => Rule::MissingWhitespaceAroundBitwiseOrShiftOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E228") => Rule::MissingWhitespaceAroundModuloOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E231") => Rule::MissingWhitespace,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E251") => Rule::UnexpectedSpacesAroundKeywordParameterEquals,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E252") => Rule::MissingWhitespaceAroundParameterEquals,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E261") => Rule::TooFewSpacesBeforeInlineComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E262") => Rule::NoSpaceAfterInlineComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E265") => Rule::NoSpaceAfterBlockComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E266") => Rule::MultipleLeadingHashesForBlockComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E271") => Rule::MultipleSpacesAfterKeyword,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E272") => Rule::MultipleSpacesBeforeKeyword,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E273") => Rule::TabAfterKeyword,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E274") => Rule::TabBeforeKeyword,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
(Pycodestyle, "E275") => Rule::MissingWhitespaceAfterKeyword,
(Pycodestyle, "E401") => Rule::MultipleImportsOnOneLine,
(Pycodestyle, "E402") => Rule::ModuleImportNotAtTopOfFile,
@@ -108,20 +108,20 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pycodestyle, "E999") => Rule::SyntaxError,
// pycodestyle warnings
(Pycodestyle, "W191") => Rule::IndentationContainsTabs,
(Pycodestyle, "W191") => Rule::TabIndentation,
(Pycodestyle, "W291") => Rule::TrailingWhitespace,
(Pycodestyle, "W292") => Rule::NoNewLineAtEndOfFile,
(Pycodestyle, "W293") => Rule::BlankLineContainsWhitespace,
(Pycodestyle, "W292") => Rule::MissingNewlineAtEndOfFile,
(Pycodestyle, "W293") => Rule::BlankLineWithWhitespace,
(Pycodestyle, "W505") => Rule::DocLineTooLong,
(Pycodestyle, "W605") => Rule::InvalidEscapeSequence,
// pyflakes
(Pyflakes, "401") => Rule::UnusedImport,
(Pyflakes, "402") => Rule::ImportShadowedByLoopVar,
(Pyflakes, "403") => Rule::ImportStar,
(Pyflakes, "403") => Rule::UndefinedLocalWithImportStar,
(Pyflakes, "404") => Rule::LateFutureImport,
(Pyflakes, "405") => Rule::ImportStarUsage,
(Pyflakes, "406") => Rule::ImportStarNotPermitted,
(Pyflakes, "405") => Rule::UndefinedLocalWithImportStarUsage,
(Pyflakes, "406") => Rule::UndefinedLocalWithNestedImportStarUsage,
(Pyflakes, "407") => Rule::FutureFeatureNotDefined,
(Pyflakes, "501") => Rule::PercentFormatInvalidFormat,
(Pyflakes, "502") => Rule::PercentFormatExpectedMapping,
@@ -141,7 +141,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pyflakes, "601") => Rule::MultiValueRepeatedKeyLiteral,
(Pyflakes, "602") => Rule::MultiValueRepeatedKeyVariable,
(Pyflakes, "621") => Rule::ExpressionsInStarAssignment,
(Pyflakes, "622") => Rule::TwoStarredExpressions,
(Pyflakes, "622") => Rule::MultipleStarredExpressions,
(Pyflakes, "631") => Rule::AssertTuple,
(Pyflakes, "632") => Rule::IsLiteral,
(Pyflakes, "633") => Rule::InvalidPrintSyntax,
@@ -166,11 +166,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pylint, "C3002") => Rule::UnnecessaryDirectLambdaCall,
(Pylint, "E0100") => Rule::YieldInInit,
(Pylint, "E0101") => Rule::ReturnInInit,
(Pylint, "E0116") => Rule::ContinueInFinally,
(Pylint, "E0117") => Rule::NonlocalWithoutBinding,
(Pylint, "E0118") => Rule::UsedPriorGlobalDeclaration,
(Pylint, "E0118") => Rule::UsePriorToGlobalDeclaration,
(Pylint, "E0604") => Rule::InvalidAllObject,
(Pylint, "E0605") => Rule::InvalidAllFormat,
(Pylint, "W1508") => Rule::InvalidEnvvarDefault,
(Pylint, "E1142") => Rule::AwaitOutsideAsync,
(Pylint, "E1205") => Rule::LoggingTooManyArgs,
(Pylint, "E1206") => Rule::LoggingTooFewArgs,
@@ -178,20 +178,29 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pylint, "E1310") => Rule::BadStrStripCall,
(Pylint, "E1507") => Rule::InvalidEnvvarValue,
(Pylint, "E2502") => Rule::BidirectionalUnicode,
(Pylint, "E2510") => Rule::InvalidCharacterBackspace,
(Pylint, "E2512") => Rule::InvalidCharacterSub,
(Pylint, "E2513") => Rule::InvalidCharacterEsc,
(Pylint, "E2514") => Rule::InvalidCharacterNul,
(Pylint, "E2515") => Rule::InvalidCharacterZeroWidthSpace,
(Pylint, "R0133") => Rule::ComparisonOfConstant,
(Pylint, "R0206") => Rule::PropertyWithParameters,
(Pylint, "R0402") => Rule::ConsiderUsingFromImport,
(Pylint, "R0402") => Rule::ManualFromImport,
(Pylint, "R0911") => Rule::TooManyReturnStatements,
(Pylint, "R0912") => Rule::TooManyBranches,
(Pylint, "R0913") => Rule::TooManyArguments,
(Pylint, "R0915") => Rule::TooManyStatements,
(Pylint, "R1701") => Rule::ConsiderMergingIsinstance,
(Pylint, "R1722") => Rule::ConsiderUsingSysExit,
(Pylint, "R1701") => Rule::RepeatedIsinstanceCalls,
(Pylint, "R1711") => Rule::UselessReturn,
(Pylint, "R1722") => Rule::SysExitAlias,
(Pylint, "R2004") => Rule::MagicValueComparison,
(Pylint, "R5501") => Rule::CollapsibleElseIf,
(Pylint, "W0120") => Rule::UselessElseOnLoop,
(Pylint, "W0129") => Rule::AssertOnStringLiteral,
(Pylint, "W0602") => Rule::GlobalVariableNotAssigned,
(Pylint, "W0603") => Rule::GlobalStatement,
(Pylint, "W0711") => Rule::BinaryOpException,
(Pylint, "W1508") => Rule::InvalidEnvvarDefault,
(Pylint, "W2901") => Rule::RedefinedLoopName,
// flake8-builtins
@@ -206,7 +215,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Bugbear, "005") => Rule::StripWithMultiCharacters,
(Flake8Bugbear, "006") => Rule::MutableArgumentDefault,
(Flake8Bugbear, "007") => Rule::UnusedLoopControlVariable,
(Flake8Bugbear, "008") => Rule::FunctionCallArgumentDefault,
(Flake8Bugbear, "008") => Rule::FunctionCallInDefaultArgument,
(Flake8Bugbear, "009") => Rule::GetAttrWithConstant,
(Flake8Bugbear, "010") => Rule::SetAttrWithConstant,
(Flake8Bugbear, "011") => Rule::AssertFalse,
@@ -226,6 +235,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,
@@ -279,8 +289,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8ImplicitStrConcat, "003") => Rule::ExplicitStringConcatenation,
// flake8-print
(Flake8Print, "1") => Rule::PrintFound,
(Flake8Print, "3") => Rule::PPrintFound,
(Flake8Print, "1") => Rule::Print,
(Flake8Print, "3") => Rule::PPrint,
// flake8-quotes
(Flake8Quotes, "000") => Rule::BadQuotesInlineString,
@@ -294,7 +304,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Annotations, "003") => Rule::MissingTypeKwargs,
(Flake8Annotations, "101") => Rule::MissingTypeSelf,
(Flake8Annotations, "102") => Rule::MissingTypeCls,
(Flake8Annotations, "201") => Rule::MissingReturnTypePublicFunction,
(Flake8Annotations, "201") => Rule::MissingReturnTypeUndocumentedPublicFunction,
(Flake8Annotations, "202") => Rule::MissingReturnTypePrivateFunction,
(Flake8Annotations, "204") => Rule::MissingReturnTypeSpecialMethod,
(Flake8Annotations, "205") => Rule::MissingReturnTypeStaticMethod,
@@ -302,16 +312,16 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Annotations, "401") => Rule::AnyType,
// flake8-2020
(Flake82020, "101") => Rule::SysVersionSlice3Referenced,
(Flake82020, "102") => Rule::SysVersion2Referenced,
(Flake82020, "101") => Rule::SysVersionSlice3,
(Flake82020, "102") => Rule::SysVersion2,
(Flake82020, "103") => Rule::SysVersionCmpStr3,
(Flake82020, "201") => Rule::SysVersionInfo0Eq3Referenced,
(Flake82020, "202") => Rule::SixPY3Referenced,
(Flake82020, "201") => Rule::SysVersionInfo0Eq3,
(Flake82020, "202") => Rule::SixPY3,
(Flake82020, "203") => Rule::SysVersionInfo1CmpInt,
(Flake82020, "204") => Rule::SysVersionInfoMinorCmpInt,
(Flake82020, "301") => Rule::SysVersion0Referenced,
(Flake82020, "301") => Rule::SysVersion0,
(Flake82020, "302") => Rule::SysVersionCmpStr10,
(Flake82020, "303") => Rule::SysVersionSlice1Referenced,
(Flake82020, "303") => Rule::SysVersionSlice1,
// flake8-simplify
(Flake8Simplify, "101") => Rule::DuplicateIsinstanceCall,
@@ -319,16 +329,15 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Simplify, "103") => Rule::NeedlessBool,
(Flake8Simplify, "105") => Rule::UseContextlibSuppress,
(Flake8Simplify, "107") => Rule::ReturnInTryExceptFinally,
(Flake8Simplify, "108") => Rule::UseTernaryOperator,
(Flake8Simplify, "108") => Rule::IfElseBlockInsteadOfIfExp,
(Flake8Simplify, "109") => Rule::CompareWithTuple,
(Flake8Simplify, "110") => Rule::ReimplementedBuiltin,
// (Flake8Simplify, "111") => Rule::ReimplementedBuiltin,
(Flake8Simplify, "112") => Rule::UseCapitalEnvironmentVariables,
(Flake8Simplify, "112") => Rule::UncapitalizedEnvironmentVariables,
(Flake8Simplify, "114") => Rule::IfWithSameArms,
(Flake8Simplify, "115") => Rule::OpenFileWithContextHandler,
(Flake8Simplify, "116") => Rule::ManualDictLookup,
(Flake8Simplify, "116") => Rule::IfElseBlockInsteadOfDictLookup,
(Flake8Simplify, "117") => Rule::MultipleWithStatements,
(Flake8Simplify, "118") => Rule::KeyInDict,
(Flake8Simplify, "118") => Rule::InDictKeys,
(Flake8Simplify, "201") => Rule::NegateEqualOp,
(Flake8Simplify, "202") => Rule::NegateNotEqualOp,
(Flake8Simplify, "208") => Rule::DoubleNegation,
@@ -340,15 +349,15 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Simplify, "222") => Rule::ExprOrTrue,
(Flake8Simplify, "223") => Rule::ExprAndFalse,
(Flake8Simplify, "300") => Rule::YodaConditions,
(Flake8Simplify, "401") => Rule::DictGetWithDefault,
(Flake8Simplify, "401") => Rule::IfElseBlockInsteadOfDictGet,
// pyupgrade
(Pyupgrade, "001") => Rule::UselessMetaclassType,
(Pyupgrade, "003") => Rule::TypeOfPrimitive,
(Pyupgrade, "004") => Rule::UselessObjectInheritance,
(Pyupgrade, "005") => Rule::DeprecatedUnittestAlias,
(Pyupgrade, "006") => Rule::DeprecatedCollectionType,
(Pyupgrade, "007") => Rule::TypingUnion,
(Pyupgrade, "006") => Rule::NonPEP585Annotation,
(Pyupgrade, "007") => Rule::NonPEP604Annotation,
(Pyupgrade, "008") => Rule::SuperCallWithParameters,
(Pyupgrade, "009") => Rule::UTF8EncodingDeclaration,
(Pyupgrade, "010") => Rule::UnnecessaryFutureImport,
@@ -363,32 +372,32 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pyupgrade, "020") => Rule::OpenAlias,
(Pyupgrade, "021") => Rule::ReplaceUniversalNewlines,
(Pyupgrade, "022") => Rule::ReplaceStdoutStderr,
(Pyupgrade, "023") => Rule::RewriteCElementTree,
(Pyupgrade, "023") => Rule::DeprecatedCElementTree,
(Pyupgrade, "024") => Rule::OSErrorAlias,
(Pyupgrade, "025") => Rule::RewriteUnicodeLiteral,
(Pyupgrade, "026") => Rule::RewriteMockImport,
(Pyupgrade, "027") => Rule::RewriteListComprehension,
(Pyupgrade, "028") => Rule::RewriteYieldFrom,
(Pyupgrade, "025") => Rule::UnicodeKindPrefix,
(Pyupgrade, "026") => Rule::DeprecatedMockImport,
(Pyupgrade, "027") => Rule::UnpackedListComprehension,
(Pyupgrade, "028") => Rule::YieldInForLoop,
(Pyupgrade, "029") => Rule::UnnecessaryBuiltinImport,
(Pyupgrade, "030") => Rule::FormatLiterals,
(Pyupgrade, "031") => Rule::PrintfStringFormatting,
(Pyupgrade, "032") => Rule::FString,
(Pyupgrade, "033") => Rule::FunctoolsCache,
(Pyupgrade, "033") => Rule::LRUCacheWithMaxsizeNone,
(Pyupgrade, "034") => Rule::ExtraneousParentheses,
(Pyupgrade, "035") => Rule::DeprecatedImport,
(Pyupgrade, "036") => Rule::OutdatedVersionBlock,
(Pyupgrade, "037") => Rule::QuotedAnnotation,
(Pyupgrade, "038") => Rule::IsinstanceWithTuple,
(Pyupgrade, "038") => Rule::NonPEP604Isinstance,
// pydocstyle
(Pydocstyle, "100") => Rule::PublicModule,
(Pydocstyle, "101") => Rule::PublicClass,
(Pydocstyle, "102") => Rule::PublicMethod,
(Pydocstyle, "103") => Rule::PublicFunction,
(Pydocstyle, "104") => Rule::PublicPackage,
(Pydocstyle, "105") => Rule::MagicMethod,
(Pydocstyle, "106") => Rule::PublicNestedClass,
(Pydocstyle, "107") => Rule::PublicInit,
(Pydocstyle, "100") => Rule::UndocumentedPublicModule,
(Pydocstyle, "101") => Rule::UndocumentedPublicClass,
(Pydocstyle, "102") => Rule::UndocumentedPublicMethod,
(Pydocstyle, "103") => Rule::UndocumentedPublicFunction,
(Pydocstyle, "104") => Rule::UndocumentedPublicPackage,
(Pydocstyle, "105") => Rule::UndocumentedMagicMethod,
(Pydocstyle, "106") => Rule::UndocumentedPublicNestedClass,
(Pydocstyle, "107") => Rule::UndocumentedPublicInit,
(Pydocstyle, "200") => Rule::FitsOnOneLine,
(Pydocstyle, "201") => Rule::NoBlankLineBeforeFunction,
(Pydocstyle, "202") => Rule::NoBlankLineAfterFunction,
@@ -396,11 +405,11 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pydocstyle, "204") => Rule::OneBlankLineAfterClass,
(Pydocstyle, "205") => Rule::BlankLineAfterSummary,
(Pydocstyle, "206") => Rule::IndentWithSpaces,
(Pydocstyle, "207") => Rule::NoUnderIndentation,
(Pydocstyle, "208") => Rule::NoOverIndentation,
(Pydocstyle, "207") => Rule::UnderIndentation,
(Pydocstyle, "208") => Rule::OverIndentation,
(Pydocstyle, "209") => Rule::NewLineAfterLastParagraph,
(Pydocstyle, "210") => Rule::NoSurroundingWhitespace,
(Pydocstyle, "211") => Rule::NoBlankLineBeforeClass,
(Pydocstyle, "210") => Rule::SurroundingWhitespace,
(Pydocstyle, "211") => Rule::BlankLineBeforeClass,
(Pydocstyle, "212") => Rule::MultiLineSummaryFirstLine,
(Pydocstyle, "213") => Rule::MultiLineSummarySecondLine,
(Pydocstyle, "214") => Rule::SectionNotOverIndented,
@@ -417,9 +426,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Pydocstyle, "407") => Rule::DashedUnderlineAfterSection,
(Pydocstyle, "408") => Rule::SectionUnderlineAfterName,
(Pydocstyle, "409") => Rule::SectionUnderlineMatchesSectionLength,
(Pydocstyle, "410") => Rule::BlankLineAfterSection,
(Pydocstyle, "411") => Rule::BlankLineBeforeSection,
(Pydocstyle, "412") => Rule::NoBlankLinesBetweenHeaderAndContent,
(Pydocstyle, "410") => Rule::NoBlankLineAfterSection,
(Pydocstyle, "411") => Rule::NoBlankLineBeforeSection,
(Pydocstyle, "412") => Rule::BlankLinesBetweenHeaderAndContent,
(Pydocstyle, "413") => Rule::BlankLineAfterLastSection,
(Pydocstyle, "414") => Rule::EmptyDocstringSection,
(Pydocstyle, "415") => Rule::EndsInPunctuation,
@@ -454,6 +463,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Eradicate, "001") => Rule::CommentedOutCode,
// flake8-bandit
(Flake8Bandit, "001") => Rule::DeniedFunctionCall,
(Flake8Bandit, "101") => Rule::Assert,
(Flake8Bandit, "102") => Rule::ExecBuiltin,
(Flake8Bandit, "103") => Rule::BadFilePermissions,
@@ -501,24 +511,24 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8Datetimez, "012") => Rule::CallDateFromtimestamp,
// pygrep-hooks
(PygrepHooks, "001") => Rule::NoEval,
(PygrepHooks, "001") => Rule::Eval,
(PygrepHooks, "002") => Rule::DeprecatedLogWarn,
(PygrepHooks, "003") => Rule::BlanketTypeIgnore,
(PygrepHooks, "004") => Rule::BlanketNOQA,
// pandas-vet
(PandasVet, "002") => Rule::UseOfInplaceArgument,
(PandasVet, "003") => Rule::UseOfDotIsNull,
(PandasVet, "004") => Rule::UseOfDotNotNull,
(PandasVet, "007") => Rule::UseOfDotIx,
(PandasVet, "008") => Rule::UseOfDotAt,
(PandasVet, "009") => Rule::UseOfDotIat,
(PandasVet, "010") => Rule::UseOfDotPivotOrUnstack,
(PandasVet, "011") => Rule::UseOfDotValues,
(PandasVet, "012") => Rule::UseOfDotReadTable,
(PandasVet, "013") => Rule::UseOfDotStack,
(PandasVet, "015") => Rule::UseOfPdMerge,
(PandasVet, "901") => Rule::DfIsABadVariableName,
(PandasVet, "002") => Rule::PandasUseOfInplaceArgument,
(PandasVet, "003") => Rule::PandasUseOfDotIsNull,
(PandasVet, "004") => Rule::PandasUseOfDotNotNull,
(PandasVet, "007") => Rule::PandasUseOfDotIx,
(PandasVet, "008") => Rule::PandasUseOfDotAt,
(PandasVet, "009") => Rule::PandasUseOfDotIat,
(PandasVet, "010") => Rule::PandasUseOfDotPivotOrUnstack,
(PandasVet, "011") => Rule::PandasUseOfDotValues,
(PandasVet, "012") => Rule::PandasUseOfDotReadTable,
(PandasVet, "013") => Rule::PandasUseOfDotStack,
(PandasVet, "015") => Rule::PandasUseOfPdMerge,
(PandasVet, "901") => Rule::PandasDfVariableName,
// flake8-errmsg
(Flake8ErrMsg, "101") => Rule::RawStringInException,
@@ -526,58 +536,58 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Flake8ErrMsg, "103") => Rule::DotFormatInException,
// flake8-pyi
(Flake8Pyi, "001") => Rule::PrefixTypeParams,
(Flake8Pyi, "001") => Rule::UnprefixedTypeParam,
(Flake8Pyi, "006") => Rule::BadVersionInfoComparison,
(Flake8Pyi, "007") => Rule::UnrecognizedPlatformCheck,
(Flake8Pyi, "008") => Rule::UnrecognizedPlatformName,
(Flake8Pyi, "009") => Rule::PassStatementStubBody,
(Flake8Pyi, "010") => Rule::NonEmptyStubBody,
(Flake8Pyi, "011") => Rule::TypedArgumentSimpleDefaults,
(Flake8Pyi, "014") => Rule::ArgumentSimpleDefaults,
(Flake8Pyi, "011") => Rule::TypedArgumentDefaultInStub,
(Flake8Pyi, "014") => Rule::ArgumentDefaultInStub,
(Flake8Pyi, "021") => Rule::DocstringInStub,
(Flake8Pyi, "033") => Rule::TypeCommentInStub,
// flake8-pytest-style
(Flake8PytestStyle, "001") => Rule::IncorrectFixtureParenthesesStyle,
(Flake8PytestStyle, "002") => Rule::FixturePositionalArgs,
(Flake8PytestStyle, "003") => Rule::ExtraneousScopeFunction,
(Flake8PytestStyle, "004") => Rule::MissingFixtureNameUnderscore,
(Flake8PytestStyle, "005") => Rule::IncorrectFixtureNameUnderscore,
(Flake8PytestStyle, "006") => Rule::ParametrizeNamesWrongType,
(Flake8PytestStyle, "007") => Rule::ParametrizeValuesWrongType,
(Flake8PytestStyle, "008") => Rule::PatchWithLambda,
(Flake8PytestStyle, "009") => Rule::UnittestAssertion,
(Flake8PytestStyle, "010") => Rule::RaisesWithoutException,
(Flake8PytestStyle, "011") => Rule::RaisesTooBroad,
(Flake8PytestStyle, "012") => Rule::RaisesWithMultipleStatements,
(Flake8PytestStyle, "013") => Rule::IncorrectPytestImport,
(Flake8PytestStyle, "015") => Rule::AssertAlwaysFalse,
(Flake8PytestStyle, "016") => Rule::FailWithoutMessage,
(Flake8PytestStyle, "017") => Rule::AssertInExcept,
(Flake8PytestStyle, "018") => Rule::CompositeAssertion,
(Flake8PytestStyle, "019") => Rule::FixtureParamWithoutValue,
(Flake8PytestStyle, "020") => Rule::DeprecatedYieldFixture,
(Flake8PytestStyle, "021") => Rule::FixtureFinalizerCallback,
(Flake8PytestStyle, "022") => Rule::UselessYieldFixture,
(Flake8PytestStyle, "023") => Rule::IncorrectMarkParenthesesStyle,
(Flake8PytestStyle, "024") => Rule::UnnecessaryAsyncioMarkOnFixture,
(Flake8PytestStyle, "025") => Rule::ErroneousUseFixturesOnFixture,
(Flake8PytestStyle, "026") => Rule::UseFixturesWithoutParameters,
(Flake8PytestStyle, "001") => Rule::PytestFixtureIncorrectParenthesesStyle,
(Flake8PytestStyle, "002") => Rule::PytestFixturePositionalArgs,
(Flake8PytestStyle, "003") => Rule::PytestExtraneousScopeFunction,
(Flake8PytestStyle, "004") => Rule::PytestMissingFixtureNameUnderscore,
(Flake8PytestStyle, "005") => Rule::PytestIncorrectFixtureNameUnderscore,
(Flake8PytestStyle, "006") => Rule::PytestParametrizeNamesWrongType,
(Flake8PytestStyle, "007") => Rule::PytestParametrizeValuesWrongType,
(Flake8PytestStyle, "008") => Rule::PytestPatchWithLambda,
(Flake8PytestStyle, "009") => Rule::PytestUnittestAssertion,
(Flake8PytestStyle, "010") => Rule::PytestRaisesWithoutException,
(Flake8PytestStyle, "011") => Rule::PytestRaisesTooBroad,
(Flake8PytestStyle, "012") => Rule::PytestRaisesWithMultipleStatements,
(Flake8PytestStyle, "013") => Rule::PytestIncorrectPytestImport,
(Flake8PytestStyle, "015") => Rule::PytestAssertAlwaysFalse,
(Flake8PytestStyle, "016") => Rule::PytestFailWithoutMessage,
(Flake8PytestStyle, "017") => Rule::PytestAssertInExcept,
(Flake8PytestStyle, "018") => Rule::PytestCompositeAssertion,
(Flake8PytestStyle, "019") => Rule::PytestFixtureParamWithoutValue,
(Flake8PytestStyle, "020") => Rule::PytestDeprecatedYieldFixture,
(Flake8PytestStyle, "021") => Rule::PytestFixtureFinalizerCallback,
(Flake8PytestStyle, "022") => Rule::PytestUselessYieldFixture,
(Flake8PytestStyle, "023") => Rule::PytestIncorrectMarkParenthesesStyle,
(Flake8PytestStyle, "024") => Rule::PytestUnnecessaryAsyncioMarkOnFixture,
(Flake8PytestStyle, "025") => Rule::PytestErroneousUseFixturesOnFixture,
(Flake8PytestStyle, "026") => Rule::PytestUseFixturesWithoutParameters,
// flake8-pie
(Flake8Pie, "790") => Rule::UnnecessaryPass,
(Flake8Pie, "794") => Rule::DupeClassFieldDefinitions,
(Flake8Pie, "796") => Rule::PreferUniqueEnums,
(Flake8Pie, "794") => Rule::DuplicateClassFieldDefinition,
(Flake8Pie, "796") => Rule::NonUniqueEnums,
(Flake8Pie, "800") => Rule::UnnecessarySpread,
(Flake8Pie, "802") => Rule::UnnecessaryComprehensionAnyAll,
(Flake8Pie, "804") => Rule::UnnecessaryDictKwargs,
(Flake8Pie, "807") => Rule::PreferListBuiltin,
(Flake8Pie, "810") => Rule::SingleStartsEndsWith,
(Flake8Pie, "807") => Rule::ReimplementedListBuiltin,
(Flake8Pie, "810") => Rule::MultipleStartsEndsWith,
// flake8-commas
(Flake8Commas, "812") => Rule::TrailingCommaMissing,
(Flake8Commas, "818") => Rule::TrailingCommaOnBareTupleProhibited,
(Flake8Commas, "819") => Rule::TrailingCommaProhibited,
(Flake8Commas, "812") => Rule::MissingTrailingComma,
(Flake8Commas, "818") => Rule::TrailingCommaOnBareTuple,
(Flake8Commas, "819") => Rule::ProhibitedTrailingComma,
// flake8-no-pep420
(Flake8NoPep420, "001") => Rule::ImplicitNamespacePackage,
@@ -585,9 +595,9 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
// flake8-executable
(Flake8Executable, "001") => Rule::ShebangNotExecutable,
(Flake8Executable, "002") => Rule::ShebangMissingExecutableFile,
(Flake8Executable, "003") => Rule::ShebangPython,
(Flake8Executable, "004") => Rule::ShebangWhitespace,
(Flake8Executable, "005") => Rule::ShebangNewline,
(Flake8Executable, "003") => Rule::ShebangMissingPython,
(Flake8Executable, "004") => Rule::ShebangLeadingWhitespace,
(Flake8Executable, "005") => Rule::ShebangNotFirstLine,
// flake8-type-checking
(Flake8TypeChecking, "001") => Rule::TypingOnlyFirstPartyImport,
@@ -599,7 +609,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
// tryceratops
(Tryceratops, "002") => Rule::RaiseVanillaClass,
(Tryceratops, "003") => Rule::RaiseVanillaArgs,
(Tryceratops, "004") => Rule::PreferTypeError,
(Tryceratops, "004") => Rule::TypeCheckWithoutTypeError,
(Tryceratops, "200") => Rule::ReraiseNoCause,
(Tryceratops, "201") => Rule::VerboseRaise,
(Tryceratops, "300") => Rule::TryConsiderElse,
@@ -658,17 +668,18 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<Rule> {
(Ruff, "001") => Rule::AmbiguousUnicodeCharacterString,
(Ruff, "002") => Rule::AmbiguousUnicodeCharacterDocstring,
(Ruff, "003") => Rule::AmbiguousUnicodeCharacterComment,
(Ruff, "005") => Rule::UnpackInsteadOfConcatenatingToCollectionLiteral,
(Ruff, "005") => Rule::CollectionLiteralConcatenation,
(Ruff, "006") => Rule::AsyncioDanglingTask,
(Ruff, "007") => Rule::PairwiseOverZipped,
(Ruff, "100") => Rule::UnusedNOQA,
// flake8-django
(Flake8Django, "001") => Rule::NullableModelStringField,
(Flake8Django, "003") => Rule::LocalsInRenderFunction,
(Flake8Django, "006") => Rule::ExcludeWithModelForm,
(Flake8Django, "007") => Rule::AllWithModelForm,
(Flake8Django, "008") => Rule::ModelWithoutDunderStr,
(Flake8Django, "013") => Rule::NonLeadingReceiverDecorator,
(Flake8Django, "001") => Rule::DjangoNullableModelStringField,
(Flake8Django, "003") => Rule::DjangoLocalsInRenderFunction,
(Flake8Django, "006") => Rule::DjangoExcludeWithModelForm,
(Flake8Django, "007") => Rule::DjangoAllWithModelForm,
(Flake8Django, "008") => Rule::DjangoModelWithoutDunderStr,
(Flake8Django, "013") => Rule::DjangoNonLeadingReceiverDecorator,
_ => return None,
})

View File

@@ -1,6 +1,7 @@
use anyhow::{bail, Result};
use libcst_native::{
Call, Comparison, Expr, Expression, Import, ImportFrom, Module, SmallStatement, Statement,
Attribute, Call, Comparison, Dict, Expr, Expression, Import, ImportFrom, Module, SimpleString,
SmallStatement, Statement,
};
pub fn match_module(module_text: &str) -> Result<Module> {
@@ -70,3 +71,31 @@ pub fn match_comparison<'a, 'b>(
bail!("Expected Expression::Comparison")
}
}
pub fn match_dict<'a, 'b>(expression: &'a mut Expression<'b>) -> Result<&'a mut Dict<'b>> {
if let Expression::Dict(dict) = expression {
Ok(dict)
} else {
bail!("Expected Expression::Dict")
}
}
pub fn match_attribute<'a, 'b>(
expression: &'a mut Expression<'b>,
) -> Result<&'a mut Attribute<'b>> {
if let Expression::Attribute(attribute) = expression {
Ok(attribute)
} else {
bail!("Expected Expression::Attribute")
}
}
pub fn match_simple_string<'a, 'b>(
expression: &'a mut Expression<'b>,
) -> Result<&'a mut SimpleString<'b>> {
if let Expression::SimpleString(simple_string) = expression {
Ok(simple_string)
} else {
bail!("Expected Expression::SimpleString")
}
}

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

@@ -2,15 +2,18 @@
use rustpython_parser::ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
use ruff_python_ast::visibility::{Modifier, VisibleScope};
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
/// Extract a docstring from a function or class body.
pub fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
let stmt = suite.first()?;
// Require the docstring to be a standalone expression.
let StmtKind::Expr { value } = &stmt.node else {
return None;
};
// Only match strings.
if !matches!(
&value.node,
ExprKind::Constant {

View File

@@ -4,9 +4,8 @@ use anyhow::{anyhow, Result};
use globset::GlobMatcher;
use log::debug;
use path_absolutize::{path_dedot, Absolutize};
use rustc_hash::FxHashSet;
use crate::registry::Rule;
use crate::registry::RuleSet;
/// Extract the absolute path and basename (as strings) from a Path.
pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
@@ -22,33 +21,34 @@ 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, RuleSet)],
) -> RuleSet {
let (file_path, file_basename) = extract_path_names(path).expect("Unable to parse filename");
pattern_code_pairs
.iter()
.filter_map(|(absolute, basename, codes)| {
.filter_map(|(absolute, basename, rules)| {
if basename.is_match(file_basename) {
debug!(
"Adding per-file ignores for {:?} due to basename match on {:?}: {:?}",
path,
basename.glob().regex(),
codes
rules
);
return Some(codes.iter());
}
if absolute.is_match(file_path) {
Some(rules)
} else if absolute.is_match(file_path) {
debug!(
"Adding per-file ignores for {:?} due to absolute match on {:?}: {:?}",
path,
absolute.glob().regex(),
codes
rules
);
return Some(codes.iter());
Some(rules)
} else {
None
}
None
})
.flatten()
.collect()

View File

@@ -0,0 +1,7 @@
//! Utils for reading and writing jupyter notebooks
mod notebook;
mod schema;
pub use notebook::*;
pub use schema::*;

View File

@@ -0,0 +1,283 @@
use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::iter;
use std::path::Path;
use serde::Serialize;
use serde_json::error::Category;
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::types::Range;
use crate::jupyter::{CellType, JupyterNotebook, SourceValue};
use crate::rules::pycodestyle::rules::SyntaxError;
use crate::IOError;
pub const JUPYTER_NOTEBOOK_EXT: &str = "ipynb";
/// Jupyter Notebook indexing table
///
/// When we lint a jupyter notebook, we have to translate the row/column based on
/// [`crate::message::Location`]
/// to jupyter notebook cell/row/column.
#[derive(Debug, Eq, PartialEq)]
pub struct JupyterIndex {
/// Enter a row (1-based), get back the cell (1-based)
pub row_to_cell: Vec<u32>,
/// Enter a row (1-based), get back the cell (1-based)
pub row_to_row_in_cell: Vec<u32>,
}
/// Return `true` if the [`Path`] appears to be that of a jupyter notebook file (`.ipynb`).
pub fn is_jupyter_notebook(path: &Path) -> bool {
path.extension()
.map_or(false, |ext| ext == JUPYTER_NOTEBOOK_EXT)
// For now this is feature gated here, the long term solution depends on
// https://github.com/charliermarsh/ruff/issues/3410
&& cfg!(feature = "jupyter_notebook")
}
impl JupyterNotebook {
/// See also the black implementation
/// <https://github.com/psf/black/blob/69ca0a4c7a365c5f5eea519a90980bab72cab764/src/black/__init__.py#L1017-L1046>
pub fn read(path: &Path) -> Result<Self, Box<Diagnostic>> {
let reader = BufReader::new(File::open(path).map_err(|err| {
Diagnostic::new(
IOError {
message: format!("{err}"),
},
Range::default(),
)
})?);
let notebook: JupyterNotebook = match serde_json::from_reader(reader) {
Ok(notebook) => notebook,
Err(err) => {
// Translate the error into a diagnostic
return Err(Box::new({
match err.classify() {
Category::Io => Diagnostic::new(
IOError {
message: format!("{err}"),
},
Range::default(),
),
Category::Syntax | Category::Eof => {
// Maybe someone saved the python sources (those with the `# %%` separator)
// as jupyter notebook instead. Let's help them.
let contents = std::fs::read_to_string(path).map_err(|err| {
Diagnostic::new(
IOError {
message: format!("{err}"),
},
Range::default(),
)
})?;
// Check if tokenizing was successful and the file is non-empty
if (ruff_rustpython::tokenize(&contents))
.last()
.map_or(true, Result::is_err)
{
Diagnostic::new(
SyntaxError {
message: format!(
"A Jupyter Notebook (.{JUPYTER_NOTEBOOK_EXT}) must internally be JSON, \
but this file isn't valid JSON: {err}"
),
},
Range::default(),
)
} else {
Diagnostic::new(
SyntaxError {
message: format!(
"Expected a Jupyter Notebook (.{JUPYTER_NOTEBOOK_EXT} extension), \
which must be internally stored as JSON, \
but found a Python source file: {err}"
),
},
Range::default(),
)
}
}
Category::Data => {
// We could try to read the schema version here but if this fails it's
// a bug anyway
Diagnostic::new(
SyntaxError {
message: format!(
"This file does not match the schema expected of Jupyter Notebooks: {err}"
),
},
Range::default(),
)
}
}
}));
}
};
// v4 is what everybody uses
if notebook.nbformat != 4 {
// bail because we should have already failed at the json schema stage
return Err(Box::new(Diagnostic::new(
SyntaxError {
message: format!(
"Expected Jupyter Notebook format 4, found {}",
notebook.nbformat
),
},
Range::default(),
)));
}
Ok(notebook)
}
/// Concatenates all cells into a single virtual file and builds an index that maps the content
/// to notebook cell locations
pub fn index(&self) -> (String, JupyterIndex) {
let mut jupyter_index = JupyterIndex {
// Enter a line number (1-based), get back the cell (1-based)
// 0 index is just padding
row_to_cell: vec![0],
// Enter a line number (1-based), get back the row number in the cell (1-based)
// 0 index is just padding
row_to_row_in_cell: vec![0],
};
let size_hint = self
.cells
.iter()
.filter(|cell| cell.cell_type == CellType::Code)
.count();
let mut contents = Vec::with_capacity(size_hint);
for (pos, cell) in self
.cells
.iter()
.enumerate()
.filter(|(_pos, cell)| cell.cell_type == CellType::Code)
{
let cell_contents = match &cell.source {
SourceValue::String(string) => {
// TODO(konstin): is or isn't there a trailing newline per cell?
// i've only seen these as array and never as string
let line_count = u32::try_from(string.lines().count()).unwrap();
jupyter_index.row_to_cell.extend(
iter::repeat(u32::try_from(pos + 1).unwrap()).take(line_count as usize),
);
jupyter_index.row_to_row_in_cell.extend(1..=line_count);
string.clone()
}
SourceValue::StringArray(string_array) => {
jupyter_index.row_to_cell.extend(
iter::repeat(u32::try_from(pos + 1).unwrap()).take(string_array.len()),
);
jupyter_index
.row_to_row_in_cell
.extend(1..=u32::try_from(string_array.len()).unwrap());
// lines already end in a newline character
string_array.join("")
}
};
contents.push(cell_contents);
}
// The last line doesn't end in a newline character
(contents.join("\n"), jupyter_index)
}
/// Write back with an indent of 1, just like black
pub fn write(&self, path: &Path) -> anyhow::Result<()> {
let mut writer = BufWriter::new(File::create(path)?);
// https://github.com/psf/black/blob/69ca0a4c7a365c5f5eea519a90980bab72cab764/src/black/__init__.py#LL1041
let formatter = serde_json::ser::PrettyFormatter::with_indent(b" ");
let mut ser = serde_json::Serializer::with_formatter(&mut writer, formatter);
self.serialize(&mut ser)?;
Ok(())
}
}
#[cfg(test)]
mod test {
use std::path::Path;
#[cfg(feature = "jupyter_notebook")]
use crate::jupyter::is_jupyter_notebook;
use crate::jupyter::{JupyterIndex, JupyterNotebook};
#[test]
fn test_valid() {
let path = Path::new("resources/test/fixtures/jupyter/valid.ipynb");
assert!(JupyterNotebook::read(path).is_ok());
}
#[test]
fn test_r() {
// We can load this, it will be filtered out later
let path = Path::new("resources/test/fixtures/jupyter/R.ipynb");
assert!(JupyterNotebook::read(path).is_ok());
}
#[test]
fn test_invalid() {
let path = Path::new("resources/test/fixtures/jupyter/invalid_extension.ipynb");
assert_eq!(
JupyterNotebook::read(path).unwrap_err().kind.body,
"SyntaxError: Expected a Jupyter Notebook (.ipynb extension), \
which must be internally stored as JSON, \
but found a Python source file: \
expected value at line 1 column 1"
);
let path = Path::new("resources/test/fixtures/jupyter/not_json.ipynb");
assert_eq!(
JupyterNotebook::read(path).unwrap_err().kind.body,
"SyntaxError: A Jupyter Notebook (.ipynb) must internally be JSON, \
but this file isn't valid JSON: \
expected value at line 1 column 1"
);
let path = Path::new("resources/test/fixtures/jupyter/wrong_schema.ipynb");
assert_eq!(
JupyterNotebook::read(path).unwrap_err().kind.body,
"SyntaxError: This file does not match the schema expected of Jupyter Notebooks: \
missing field `cells` at line 1 column 2"
);
}
#[test]
#[cfg(feature = "jupyter_notebook")]
fn inclusions() {
let path = Path::new("foo/bar/baz");
assert!(!is_jupyter_notebook(path));
let path = Path::new("foo/bar/baz.ipynb");
assert!(is_jupyter_notebook(path));
}
#[test]
fn test_concat_notebook() {
let path = Path::new("resources/test/fixtures/jupyter/valid.ipynb");
let notebook = JupyterNotebook::read(path).unwrap();
let (contents, index) = notebook.index();
assert_eq!(
contents,
r#"def unused_variable():
x = 1
y = 2
print(f"cell one: {y}")
unused_variable()
def mutable_argument(z=set()):
print(f"cell two: {z}")
mutable_argument()
"#
);
assert_eq!(
index,
JupyterIndex {
row_to_cell: vec![0, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3],
row_to_row_in_cell: vec![0, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4],
}
);
}
}

View File

@@ -0,0 +1,256 @@
//! The JSON schema of a Jupyter Notebook, entrypoint is [`JupyterNotebook`]
//!
//! Generated by <https://app.quicktype.io/> from
//! <https://github.com/jupyter/nbformat/blob/16b53251aabf472ad9406ddb1f78b0421c014eeb/nbformat/v4/nbformat.v4.schema.json>
//! Jupyter Notebook v4.5 JSON schema.
//!
//! The following changes were made to the generated version: `Cell::id` is optional because it
//! wasn't required <v4.5, `#[serde(deny_unknown_fields)]` was added where the schema had
//! `"additionalProperties": false` and `#[serde(flatten)] pub other: BTreeMap<String, Value>`
//! for `"additionalProperties": true` as preparation for round-trip support.
use std::collections::{BTreeMap, HashMap};
use serde::{Deserialize, Serialize};
use serde_json::Value;
/// The root of the JSON of a Jupyter Notebook
///
/// Generated by <https://app.quicktype.io/> from
/// <https://github.com/jupyter/nbformat/blob/16b53251aabf472ad9406ddb1f78b0421c014eeb/nbformat/v4/nbformat.v4.schema.json>
/// Jupyter Notebook v4.5 JSON schema.
#[derive(Debug, Serialize, Deserialize)]
pub struct JupyterNotebook {
/// Array of cells of the current notebook.
pub cells: Vec<Cell>,
/// Notebook root-level metadata.
pub metadata: JupyterNotebookMetadata,
/// Notebook format (major number). Incremented between backwards incompatible changes to the
/// notebook format.
pub nbformat: i64,
/// Notebook format (minor number). Incremented for backward compatible changes to the
/// notebook format.
pub nbformat_minor: i64,
}
/// Notebook raw nbconvert cell.
///
/// Notebook markdown cell.
///
/// Notebook code cell.
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Cell {
pub attachments: Option<HashMap<String, HashMap<String, SourceValue>>>,
/// String identifying the type of cell.
pub cell_type: CellType,
/// Technically, id isn't required (it's not even present) in schema v4.0 through v4.4, but
/// it's required in v4.5. Main issue is that pycharm creates notebooks without an id
/// <https://youtrack.jetbrains.com/issue/PY-59438/Jupyter-notebooks-created-with-PyCharm-are-missing-the-id-field-in-cells-in-the-.ipynb-json>
pub id: Option<String>,
/// Cell-level metadata.
pub metadata: CellMetadata,
pub source: SourceValue,
/// The code cell's prompt number. Will be null if the cell has not been run.
pub execution_count: Option<i64>,
/// Execution, display, or stream outputs.
pub outputs: Option<Vec<Output>>,
}
/// Cell-level metadata.
#[derive(Debug, Serialize, Deserialize)]
pub struct CellMetadata {
/// Raw cell metadata format for nbconvert.
pub format: Option<String>,
/// Official Jupyter Metadata for Raw Cells
///
/// Official Jupyter Metadata for Markdown Cells
///
/// Official Jupyter Metadata for Code Cells
pub jupyter: Option<HashMap<String, Option<Value>>>,
pub name: Option<String>,
pub tags: Option<Vec<String>>,
/// Whether the cell's output is collapsed/expanded.
pub collapsed: Option<bool>,
/// Execution time for the code in the cell. This tracks time at which messages are received
/// from iopub or shell channels
pub execution: Option<Execution>,
/// Whether the cell's output is scrolled, unscrolled, or autoscrolled.
pub scrolled: Option<ScrolledUnion>,
/// Custom added: round-trip support
#[serde(flatten)]
pub other: BTreeMap<String, Value>,
}
/// Execution time for the code in the cell. This tracks time at which messages are received
/// from iopub or shell channels
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Execution {
/// header.date (in ISO 8601 format) of iopub channel's execute_input message. It indicates
/// the time at which the kernel broadcasts an execute_input message to connected frontends
#[serde(rename = "iopub.execute_input")]
pub iopub_execute_input: Option<String>,
/// header.date (in ISO 8601 format) of iopub channel's kernel status message when the status
/// is 'busy'
#[serde(rename = "iopub.status.busy")]
pub iopub_status_busy: Option<String>,
/// header.date (in ISO 8601 format) of iopub channel's kernel status message when the status
/// is 'idle'. It indicates the time at which kernel finished processing the associated
/// request
#[serde(rename = "iopub.status.idle")]
pub iopub_status_idle: Option<String>,
/// header.date (in ISO 8601 format) of the shell channel's execute_reply message. It
/// indicates the time at which the execute_reply message was created
#[serde(rename = "shell.execute_reply")]
pub shell_execute_reply: Option<String>,
}
/// Result of executing a code cell.
///
/// Data displayed as a result of code cell execution.
///
/// Stream output from a code cell.
///
/// Output of an error that occurred during code cell execution.
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Output {
pub data: Option<HashMap<String, SourceValue>>,
/// A result's prompt number.
pub execution_count: Option<i64>,
pub metadata: Option<HashMap<String, Option<Value>>>,
/// Type of cell output.
pub output_type: OutputType,
/// The name of the stream (stdout, stderr).
pub name: Option<String>,
/// The stream's text output, represented as an array of strings.
pub text: Option<TextUnion>,
/// The name of the error.
pub ename: Option<String>,
/// The value, or message, of the error.
pub evalue: Option<String>,
/// The error's traceback, represented as an array of strings.
pub traceback: Option<Vec<String>>,
}
/// Notebook root-level metadata.
#[derive(Debug, Serialize, Deserialize)]
pub struct JupyterNotebookMetadata {
/// The author(s) of the notebook document
pub authors: Option<Vec<Option<Value>>>,
/// Kernel information.
pub kernelspec: Option<Kernelspec>,
/// Kernel information.
pub language_info: Option<LanguageInfo>,
/// Original notebook format (major number) before converting the notebook between versions.
/// This should never be written to a file.
pub orig_nbformat: Option<i64>,
/// The title of the notebook document
pub title: Option<String>,
/// Custom added: round-trip support
#[serde(flatten)]
pub other: BTreeMap<String, Value>,
}
/// Kernel information.
#[derive(Debug, Serialize, Deserialize)]
pub struct Kernelspec {
/// Name to display in UI.
pub display_name: String,
/// Name of the kernel specification.
pub name: String,
/// Custom added: round-trip support
#[serde(flatten)]
pub other: BTreeMap<String, Value>,
}
/// Kernel information.
#[derive(Debug, Serialize, Deserialize)]
pub struct LanguageInfo {
/// The codemirror mode to use for code in this language.
pub codemirror_mode: Option<CodemirrorMode>,
/// The file extension for files in this language.
pub file_extension: Option<String>,
/// The mimetype corresponding to files in this language.
pub mimetype: Option<String>,
/// The programming language which this kernel runs.
pub name: String,
/// The pygments lexer to use for code in this language.
pub pygments_lexer: Option<String>,
/// Custom added: round-trip support
#[serde(flatten)]
pub other: BTreeMap<String, Value>,
}
/// mimetype output (e.g. text/plain), represented as either an array of strings or a
/// string.
///
/// Contents of the cell, represented as an array of lines.
///
/// The stream's text output, represented as an array of strings.
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SourceValue {
String(String),
StringArray(Vec<String>),
}
/// Whether the cell's output is scrolled, unscrolled, or autoscrolled.
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ScrolledUnion {
Bool(bool),
Enum(ScrolledEnum),
}
/// mimetype output (e.g. text/plain), represented as either an array of strings or a
/// string.
///
/// Contents of the cell, represented as an array of lines.
///
/// The stream's text output, represented as an array of strings.
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum TextUnion {
String(String),
StringArray(Vec<String>),
}
/// The codemirror mode to use for code in this language.
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum CodemirrorMode {
AnythingMap(HashMap<String, Option<Value>>),
String(String),
}
/// String identifying the type of cell.
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub enum CellType {
#[serde(rename = "code")]
Code,
#[serde(rename = "markdown")]
Markdown,
#[serde(rename = "raw")]
Raw,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum ScrolledEnum {
#[serde(rename = "auto")]
Auto,
}
/// Type of cell output.
#[derive(Debug, Serialize, Deserialize)]
pub enum OutputType {
#[serde(rename = "display_data")]
DisplayData,
#[serde(rename = "error")]
Error,
#[serde(rename = "execute_result")]
ExecuteResult,
#[serde(rename = "stream")]
Stream,
}

View File

@@ -20,6 +20,7 @@ mod docstrings;
pub mod fix;
pub mod flake8_to_ruff;
pub mod fs;
pub mod jupyter;
mod lex;
pub mod linter;
pub mod logging;

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,

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

@@ -8,7 +8,7 @@ use log::warn;
use nohash_hasher::IntMap;
use once_cell::sync::Lazy;
use regex::Regex;
use rustc_hash::{FxHashMap, FxHashSet};
use rustc_hash::FxHashMap;
use rustpython_parser::ast::Location;
use ruff_diagnostics::Diagnostic;
@@ -17,7 +17,7 @@ use ruff_python_ast::source_code::{LineEnding, Locator};
use ruff_python_ast::types::Range;
use crate::codes::NoqaCode;
use crate::registry::{AsRule, Rule};
use crate::registry::{AsRule, Rule, RuleSet};
use crate::rule_redirects::get_redirect_target;
static NOQA_LINE_REGEX: Lazy<Regex> = Lazy::new(|| {
@@ -121,7 +121,7 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
/// Returns `true` if the string list of `codes` includes `code` (or an alias
/// thereof).
pub fn includes(needle: &Rule, haystack: &[&str]) -> bool {
pub fn includes(needle: Rule, haystack: &[&str]) -> bool {
let needle = needle.noqa_code();
haystack
.iter()
@@ -130,7 +130,7 @@ pub fn includes(needle: &Rule, haystack: &[&str]) -> bool {
/// Returns `true` if the given [`Rule`] is ignored at the specified `lineno`.
pub fn rule_is_ignored(
code: &Rule,
code: Rule,
lineno: usize,
noqa_line_for: &IntMap<usize, usize>,
locator: &Locator,
@@ -174,7 +174,7 @@ fn add_noqa_inner(
line_ending: &LineEnding,
) -> (usize, String) {
// Map of line number to set of (non-ignored) diagnostic codes that are triggered on that line.
let mut matches_by_line: FxHashMap<usize, FxHashSet<&Rule>> = FxHashMap::default();
let mut matches_by_line: FxHashMap<usize, RuleSet> = FxHashMap::default();
// Whether the file is exempted from all checks.
let mut file_exempted = false;
@@ -280,7 +280,7 @@ fn add_noqa_inner(
output.push_str(" # noqa: ");
// Add codes.
push_codes(&mut output, rules.iter().map(|r| r.noqa_code()));
push_codes(&mut output, rules.iter().map(|rule| rule.noqa_code()));
output.push_str(line_ending);
count += 1;
}

View File

@@ -1,5 +1,7 @@
//! Registry of all [`Rule`] implementations.
mod rule_set;
use strum_macros::{AsRefStr, EnumIter};
use ruff_diagnostics::Violation;
@@ -7,71 +9,72 @@ use ruff_macros::RuleNamespace;
use crate::codes::{self, RuleCodePrefix};
use crate::rules;
pub use rule_set::{RuleSet, RuleSetIterator};
ruff_macros::register_rules!(
// pycodestyle errors
rules::pycodestyle::rules::MixedSpacesAndTabs,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::IndentationWithInvalidMultiple,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::NoIndentedBlock,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::UnexpectedIndentation,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::IndentationWithInvalidMultipleComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::NoIndentedBlockComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::UnexpectedIndentationComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::OverIndented,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::WhitespaceAfterOpenBracket,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::WhitespaceBeforeCloseBracket,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::WhitespaceBeforePunctuation,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MultipleSpacesBeforeOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MultipleSpacesAfterOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::TabBeforeOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::TabAfterOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::TooFewSpacesBeforeInlineComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::NoSpaceAfterInlineComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::NoSpaceAfterBlockComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MultipleLeadingHashesForBlockComment,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MultipleSpacesAfterKeyword,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MissingWhitespace,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MissingWhitespaceAfterKeyword,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MultipleSpacesBeforeKeyword,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MissingWhitespaceAroundOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MissingWhitespaceAroundArithmeticOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MissingWhitespaceAroundBitwiseOrShiftOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MissingWhitespaceAroundModuloOperator,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::TabAfterKeyword,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::UnexpectedSpacesAroundKeywordParameterEquals,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::MissingWhitespaceAroundParameterEquals,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::WhitespaceBeforeParameters,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
rules::pycodestyle::rules::TabBeforeKeyword,
rules::pycodestyle::rules::MultipleImportsOnOneLine,
rules::pycodestyle::rules::ModuleImportNotAtTopOfFile,
@@ -92,19 +95,19 @@ ruff_macros::register_rules!(
rules::pycodestyle::rules::IOError,
rules::pycodestyle::rules::SyntaxError,
// pycodestyle warnings
rules::pycodestyle::rules::IndentationContainsTabs,
rules::pycodestyle::rules::TabIndentation,
rules::pycodestyle::rules::TrailingWhitespace,
rules::pycodestyle::rules::NoNewLineAtEndOfFile,
rules::pycodestyle::rules::BlankLineContainsWhitespace,
rules::pycodestyle::rules::MissingNewlineAtEndOfFile,
rules::pycodestyle::rules::BlankLineWithWhitespace,
rules::pycodestyle::rules::DocLineTooLong,
rules::pycodestyle::rules::InvalidEscapeSequence,
// pyflakes
rules::pyflakes::rules::UnusedImport,
rules::pyflakes::rules::ImportShadowedByLoopVar,
rules::pyflakes::rules::ImportStar,
rules::pyflakes::rules::UndefinedLocalWithImportStar,
rules::pyflakes::rules::LateFutureImport,
rules::pyflakes::rules::ImportStarUsage,
rules::pyflakes::rules::ImportStarNotPermitted,
rules::pyflakes::rules::UndefinedLocalWithImportStarUsage,
rules::pyflakes::rules::UndefinedLocalWithNestedImportStarUsage,
rules::pyflakes::rules::FutureFeatureNotDefined,
rules::pyflakes::rules::PercentFormatInvalidFormat,
rules::pyflakes::rules::PercentFormatExpectedMapping,
@@ -124,7 +127,7 @@ ruff_macros::register_rules!(
rules::pyflakes::rules::MultiValueRepeatedKeyLiteral,
rules::pyflakes::rules::MultiValueRepeatedKeyVariable,
rules::pyflakes::rules::ExpressionsInStarAssignment,
rules::pyflakes::rules::TwoStarredExpressions,
rules::pyflakes::rules::MultipleStarredExpressions,
rules::pyflakes::rules::AssertTuple,
rules::pyflakes::rules::IsLiteral,
rules::pyflakes::rules::InvalidPrintSyntax,
@@ -143,6 +146,8 @@ ruff_macros::register_rules!(
rules::pyflakes::rules::UnusedAnnotation,
rules::pyflakes::rules::RaiseNotImplemented,
// pylint
rules::pylint::rules::AssertOnStringLiteral,
rules::pylint::rules::UselessReturn,
rules::pylint::rules::YieldInInit,
rules::pylint::rules::InvalidAllObject,
rules::pylint::rules::InvalidAllFormat,
@@ -150,20 +155,27 @@ ruff_macros::register_rules!(
rules::pylint::rules::InvalidEnvvarValue,
rules::pylint::rules::BadStringFormatType,
rules::pylint::rules::BidirectionalUnicode,
rules::pylint::rules::BinaryOpException,
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,
rules::pylint::rules::UsedPriorGlobalDeclaration,
rules::pylint::rules::UsePriorToGlobalDeclaration,
rules::pylint::rules::AwaitOutsideAsync,
rules::pylint::rules::PropertyWithParameters,
rules::pylint::rules::ReturnInInit,
rules::pylint::rules::ConsiderUsingFromImport,
rules::pylint::rules::ManualFromImport,
rules::pylint::rules::CompareToEmptyString,
rules::pylint::rules::ComparisonOfConstant,
rules::pylint::rules::ConsiderMergingIsinstance,
rules::pylint::rules::ConsiderUsingSysExit,
rules::pylint::rules::RepeatedIsinstanceCalls,
rules::pylint::rules::SysExitAlias,
rules::pylint::rules::MagicValueComparison,
rules::pylint::rules::UselessElseOnLoop,
rules::pylint::rules::GlobalStatement,
@@ -185,8 +197,9 @@ 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::FunctionCallInDefaultArgument,
rules::flake8_bugbear::rules::GetAttrWithConstant,
rules::flake8_bugbear::rules::SetAttrWithConstant,
rules::flake8_bugbear::rules::AssertFalse,
@@ -251,8 +264,8 @@ ruff_macros::register_rules!(
rules::flake8_implicit_str_concat::rules::MultiLineImplicitStringConcatenation,
rules::flake8_implicit_str_concat::rules::ExplicitStringConcatenation,
// flake8-print
rules::flake8_print::rules::PrintFound,
rules::flake8_print::rules::PPrintFound,
rules::flake8_print::rules::Print,
rules::flake8_print::rules::PPrint,
// flake8-quotes
rules::flake8_quotes::rules::BadQuotesInlineString,
rules::flake8_quotes::rules::BadQuotesMultilineString,
@@ -264,38 +277,38 @@ ruff_macros::register_rules!(
rules::flake8_annotations::rules::MissingTypeKwargs,
rules::flake8_annotations::rules::MissingTypeSelf,
rules::flake8_annotations::rules::MissingTypeCls,
rules::flake8_annotations::rules::MissingReturnTypePublicFunction,
rules::flake8_annotations::rules::MissingReturnTypeUndocumentedPublicFunction,
rules::flake8_annotations::rules::MissingReturnTypePrivateFunction,
rules::flake8_annotations::rules::MissingReturnTypeSpecialMethod,
rules::flake8_annotations::rules::MissingReturnTypeStaticMethod,
rules::flake8_annotations::rules::MissingReturnTypeClassMethod,
rules::flake8_annotations::rules::AnyType,
// flake8-2020
rules::flake8_2020::rules::SysVersionSlice3Referenced,
rules::flake8_2020::rules::SysVersion2Referenced,
rules::flake8_2020::rules::SysVersionSlice3,
rules::flake8_2020::rules::SysVersion2,
rules::flake8_2020::rules::SysVersionCmpStr3,
rules::flake8_2020::rules::SysVersionInfo0Eq3Referenced,
rules::flake8_2020::rules::SixPY3Referenced,
rules::flake8_2020::rules::SysVersionInfo0Eq3,
rules::flake8_2020::rules::SixPY3,
rules::flake8_2020::rules::SysVersionInfo1CmpInt,
rules::flake8_2020::rules::SysVersionInfoMinorCmpInt,
rules::flake8_2020::rules::SysVersion0Referenced,
rules::flake8_2020::rules::SysVersion0,
rules::flake8_2020::rules::SysVersionCmpStr10,
rules::flake8_2020::rules::SysVersionSlice1Referenced,
rules::flake8_2020::rules::SysVersionSlice1,
// flake8-simplify
rules::flake8_simplify::rules::ManualDictLookup,
rules::flake8_simplify::rules::IfElseBlockInsteadOfDictLookup,
rules::flake8_simplify::rules::DuplicateIsinstanceCall,
rules::flake8_simplify::rules::CollapsibleIf,
rules::flake8_simplify::rules::NeedlessBool,
rules::flake8_simplify::rules::UseContextlibSuppress,
rules::flake8_simplify::rules::ReturnInTryExceptFinally,
rules::flake8_simplify::rules::UseTernaryOperator,
rules::flake8_simplify::rules::IfElseBlockInsteadOfIfExp,
rules::flake8_simplify::rules::CompareWithTuple,
rules::flake8_simplify::rules::ReimplementedBuiltin,
rules::flake8_simplify::rules::UseCapitalEnvironmentVariables,
rules::flake8_simplify::rules::UncapitalizedEnvironmentVariables,
rules::flake8_simplify::rules::IfWithSameArms,
rules::flake8_simplify::rules::OpenFileWithContextHandler,
rules::flake8_simplify::rules::MultipleWithStatements,
rules::flake8_simplify::rules::KeyInDict,
rules::flake8_simplify::rules::InDictKeys,
rules::flake8_simplify::rules::NegateEqualOp,
rules::flake8_simplify::rules::NegateNotEqualOp,
rules::flake8_simplify::rules::DoubleNegation,
@@ -307,14 +320,14 @@ ruff_macros::register_rules!(
rules::flake8_simplify::rules::ExprOrTrue,
rules::flake8_simplify::rules::ExprAndFalse,
rules::flake8_simplify::rules::YodaConditions,
rules::flake8_simplify::rules::DictGetWithDefault,
rules::flake8_simplify::rules::IfElseBlockInsteadOfDictGet,
// pyupgrade
rules::pyupgrade::rules::UselessMetaclassType,
rules::pyupgrade::rules::TypeOfPrimitive,
rules::pyupgrade::rules::UselessObjectInheritance,
rules::pyupgrade::rules::DeprecatedUnittestAlias,
rules::pyupgrade::rules::DeprecatedCollectionType,
rules::pyupgrade::rules::TypingUnion,
rules::pyupgrade::rules::NonPEP585Annotation,
rules::pyupgrade::rules::NonPEP604Annotation,
rules::pyupgrade::rules::SuperCallWithParameters,
rules::pyupgrade::rules::UTF8EncodingDeclaration,
rules::pyupgrade::rules::UnnecessaryFutureImport,
@@ -329,31 +342,31 @@ ruff_macros::register_rules!(
rules::pyupgrade::rules::OpenAlias,
rules::pyupgrade::rules::ReplaceUniversalNewlines,
rules::pyupgrade::rules::ReplaceStdoutStderr,
rules::pyupgrade::rules::RewriteCElementTree,
rules::pyupgrade::rules::DeprecatedCElementTree,
rules::pyupgrade::rules::OSErrorAlias,
rules::pyupgrade::rules::RewriteUnicodeLiteral,
rules::pyupgrade::rules::RewriteMockImport,
rules::pyupgrade::rules::RewriteListComprehension,
rules::pyupgrade::rules::RewriteYieldFrom,
rules::pyupgrade::rules::UnicodeKindPrefix,
rules::pyupgrade::rules::DeprecatedMockImport,
rules::pyupgrade::rules::UnpackedListComprehension,
rules::pyupgrade::rules::YieldInForLoop,
rules::pyupgrade::rules::UnnecessaryBuiltinImport,
rules::pyupgrade::rules::FormatLiterals,
rules::pyupgrade::rules::PrintfStringFormatting,
rules::pyupgrade::rules::FString,
rules::pyupgrade::rules::FunctoolsCache,
rules::pyupgrade::rules::LRUCacheWithMaxsizeNone,
rules::pyupgrade::rules::ExtraneousParentheses,
rules::pyupgrade::rules::DeprecatedImport,
rules::pyupgrade::rules::OutdatedVersionBlock,
rules::pyupgrade::rules::QuotedAnnotation,
rules::pyupgrade::rules::IsinstanceWithTuple,
rules::pyupgrade::rules::NonPEP604Isinstance,
// pydocstyle
rules::pydocstyle::rules::PublicModule,
rules::pydocstyle::rules::PublicClass,
rules::pydocstyle::rules::PublicMethod,
rules::pydocstyle::rules::PublicFunction,
rules::pydocstyle::rules::PublicPackage,
rules::pydocstyle::rules::MagicMethod,
rules::pydocstyle::rules::PublicNestedClass,
rules::pydocstyle::rules::PublicInit,
rules::pydocstyle::rules::UndocumentedPublicModule,
rules::pydocstyle::rules::UndocumentedPublicClass,
rules::pydocstyle::rules::UndocumentedPublicMethod,
rules::pydocstyle::rules::UndocumentedPublicFunction,
rules::pydocstyle::rules::UndocumentedPublicPackage,
rules::pydocstyle::rules::UndocumentedMagicMethod,
rules::pydocstyle::rules::UndocumentedPublicNestedClass,
rules::pydocstyle::rules::UndocumentedPublicInit,
rules::pydocstyle::rules::FitsOnOneLine,
rules::pydocstyle::rules::NoBlankLineBeforeFunction,
rules::pydocstyle::rules::NoBlankLineAfterFunction,
@@ -361,11 +374,11 @@ ruff_macros::register_rules!(
rules::pydocstyle::rules::OneBlankLineAfterClass,
rules::pydocstyle::rules::BlankLineAfterSummary,
rules::pydocstyle::rules::IndentWithSpaces,
rules::pydocstyle::rules::NoUnderIndentation,
rules::pydocstyle::rules::NoOverIndentation,
rules::pydocstyle::rules::UnderIndentation,
rules::pydocstyle::rules::OverIndentation,
rules::pydocstyle::rules::NewLineAfterLastParagraph,
rules::pydocstyle::rules::NoSurroundingWhitespace,
rules::pydocstyle::rules::NoBlankLineBeforeClass,
rules::pydocstyle::rules::SurroundingWhitespace,
rules::pydocstyle::rules::BlankLineBeforeClass,
rules::pydocstyle::rules::MultiLineSummaryFirstLine,
rules::pydocstyle::rules::MultiLineSummarySecondLine,
rules::pydocstyle::rules::SectionNotOverIndented,
@@ -382,9 +395,9 @@ ruff_macros::register_rules!(
rules::pydocstyle::rules::DashedUnderlineAfterSection,
rules::pydocstyle::rules::SectionUnderlineAfterName,
rules::pydocstyle::rules::SectionUnderlineMatchesSectionLength,
rules::pydocstyle::rules::BlankLineAfterSection,
rules::pydocstyle::rules::BlankLineBeforeSection,
rules::pydocstyle::rules::NoBlankLinesBetweenHeaderAndContent,
rules::pydocstyle::rules::NoBlankLineAfterSection,
rules::pydocstyle::rules::NoBlankLineBeforeSection,
rules::pydocstyle::rules::BlankLinesBetweenHeaderAndContent,
rules::pydocstyle::rules::BlankLineAfterLastSection,
rules::pydocstyle::rules::EmptyDocstringSection,
rules::pydocstyle::rules::EndsInPunctuation,
@@ -416,6 +429,7 @@ ruff_macros::register_rules!(
rules::eradicate::rules::CommentedOutCode,
// flake8-bandit
rules::flake8_bandit::rules::Assert,
rules::flake8_bandit::rules::DeniedFunctionCall,
rules::flake8_bandit::rules::ExecBuiltin,
rules::flake8_bandit::rules::BadFilePermissions,
rules::flake8_bandit::rules::HardcodedBindAllInterfaces,
@@ -457,85 +471,85 @@ ruff_macros::register_rules!(
rules::flake8_datetimez::rules::CallDateToday,
rules::flake8_datetimez::rules::CallDateFromtimestamp,
// pygrep-hooks
rules::pygrep_hooks::rules::NoEval,
rules::pygrep_hooks::rules::Eval,
rules::pygrep_hooks::rules::DeprecatedLogWarn,
rules::pygrep_hooks::rules::BlanketTypeIgnore,
rules::pygrep_hooks::rules::BlanketNOQA,
// pandas-vet
rules::pandas_vet::rules::UseOfInplaceArgument,
rules::pandas_vet::rules::UseOfDotIsNull,
rules::pandas_vet::rules::UseOfDotNotNull,
rules::pandas_vet::rules::UseOfDotIx,
rules::pandas_vet::rules::UseOfDotAt,
rules::pandas_vet::rules::UseOfDotIat,
rules::pandas_vet::rules::UseOfDotPivotOrUnstack,
rules::pandas_vet::rules::UseOfDotValues,
rules::pandas_vet::rules::UseOfDotReadTable,
rules::pandas_vet::rules::UseOfDotStack,
rules::pandas_vet::rules::UseOfPdMerge,
rules::pandas_vet::rules::DfIsABadVariableName,
rules::pandas_vet::rules::PandasUseOfInplaceArgument,
rules::pandas_vet::rules::PandasUseOfDotIsNull,
rules::pandas_vet::rules::PandasUseOfDotNotNull,
rules::pandas_vet::rules::PandasUseOfDotIx,
rules::pandas_vet::rules::PandasUseOfDotAt,
rules::pandas_vet::rules::PandasUseOfDotIat,
rules::pandas_vet::rules::PandasUseOfDotPivotOrUnstack,
rules::pandas_vet::rules::PandasUseOfDotValues,
rules::pandas_vet::rules::PandasUseOfDotReadTable,
rules::pandas_vet::rules::PandasUseOfDotStack,
rules::pandas_vet::rules::PandasUseOfPdMerge,
rules::pandas_vet::rules::PandasDfVariableName,
// flake8-errmsg
rules::flake8_errmsg::rules::RawStringInException,
rules::flake8_errmsg::rules::FStringInException,
rules::flake8_errmsg::rules::DotFormatInException,
// flake8-pyi
rules::flake8_pyi::rules::PrefixTypeParams,
rules::flake8_pyi::rules::UnprefixedTypeParam,
rules::flake8_pyi::rules::BadVersionInfoComparison,
rules::flake8_pyi::rules::UnrecognizedPlatformCheck,
rules::flake8_pyi::rules::UnrecognizedPlatformName,
rules::flake8_pyi::rules::PassStatementStubBody,
rules::flake8_pyi::rules::NonEmptyStubBody,
rules::flake8_pyi::rules::DocstringInStub,
rules::flake8_pyi::rules::TypedArgumentSimpleDefaults,
rules::flake8_pyi::rules::ArgumentSimpleDefaults,
rules::flake8_pyi::rules::TypedArgumentDefaultInStub,
rules::flake8_pyi::rules::ArgumentDefaultInStub,
rules::flake8_pyi::rules::TypeCommentInStub,
// flake8-pytest-style
rules::flake8_pytest_style::rules::IncorrectFixtureParenthesesStyle,
rules::flake8_pytest_style::rules::FixturePositionalArgs,
rules::flake8_pytest_style::rules::ExtraneousScopeFunction,
rules::flake8_pytest_style::rules::MissingFixtureNameUnderscore,
rules::flake8_pytest_style::rules::IncorrectFixtureNameUnderscore,
rules::flake8_pytest_style::rules::ParametrizeNamesWrongType,
rules::flake8_pytest_style::rules::ParametrizeValuesWrongType,
rules::flake8_pytest_style::rules::PatchWithLambda,
rules::flake8_pytest_style::rules::UnittestAssertion,
rules::flake8_pytest_style::rules::RaisesWithoutException,
rules::flake8_pytest_style::rules::RaisesTooBroad,
rules::flake8_pytest_style::rules::RaisesWithMultipleStatements,
rules::flake8_pytest_style::rules::IncorrectPytestImport,
rules::flake8_pytest_style::rules::AssertAlwaysFalse,
rules::flake8_pytest_style::rules::FailWithoutMessage,
rules::flake8_pytest_style::rules::AssertInExcept,
rules::flake8_pytest_style::rules::CompositeAssertion,
rules::flake8_pytest_style::rules::FixtureParamWithoutValue,
rules::flake8_pytest_style::rules::DeprecatedYieldFixture,
rules::flake8_pytest_style::rules::FixtureFinalizerCallback,
rules::flake8_pytest_style::rules::UselessYieldFixture,
rules::flake8_pytest_style::rules::IncorrectMarkParenthesesStyle,
rules::flake8_pytest_style::rules::UnnecessaryAsyncioMarkOnFixture,
rules::flake8_pytest_style::rules::ErroneousUseFixturesOnFixture,
rules::flake8_pytest_style::rules::UseFixturesWithoutParameters,
rules::flake8_pytest_style::rules::PytestFixtureIncorrectParenthesesStyle,
rules::flake8_pytest_style::rules::PytestFixturePositionalArgs,
rules::flake8_pytest_style::rules::PytestExtraneousScopeFunction,
rules::flake8_pytest_style::rules::PytestMissingFixtureNameUnderscore,
rules::flake8_pytest_style::rules::PytestIncorrectFixtureNameUnderscore,
rules::flake8_pytest_style::rules::PytestParametrizeNamesWrongType,
rules::flake8_pytest_style::rules::PytestParametrizeValuesWrongType,
rules::flake8_pytest_style::rules::PytestPatchWithLambda,
rules::flake8_pytest_style::rules::PytestUnittestAssertion,
rules::flake8_pytest_style::rules::PytestRaisesWithoutException,
rules::flake8_pytest_style::rules::PytestRaisesTooBroad,
rules::flake8_pytest_style::rules::PytestRaisesWithMultipleStatements,
rules::flake8_pytest_style::rules::PytestIncorrectPytestImport,
rules::flake8_pytest_style::rules::PytestAssertAlwaysFalse,
rules::flake8_pytest_style::rules::PytestFailWithoutMessage,
rules::flake8_pytest_style::rules::PytestAssertInExcept,
rules::flake8_pytest_style::rules::PytestCompositeAssertion,
rules::flake8_pytest_style::rules::PytestFixtureParamWithoutValue,
rules::flake8_pytest_style::rules::PytestDeprecatedYieldFixture,
rules::flake8_pytest_style::rules::PytestFixtureFinalizerCallback,
rules::flake8_pytest_style::rules::PytestUselessYieldFixture,
rules::flake8_pytest_style::rules::PytestIncorrectMarkParenthesesStyle,
rules::flake8_pytest_style::rules::PytestUnnecessaryAsyncioMarkOnFixture,
rules::flake8_pytest_style::rules::PytestErroneousUseFixturesOnFixture,
rules::flake8_pytest_style::rules::PytestUseFixturesWithoutParameters,
// flake8-pie
rules::flake8_pie::rules::UnnecessaryPass,
rules::flake8_pie::rules::DupeClassFieldDefinitions,
rules::flake8_pie::rules::PreferUniqueEnums,
rules::flake8_pie::rules::DuplicateClassFieldDefinition,
rules::flake8_pie::rules::NonUniqueEnums,
rules::flake8_pie::rules::UnnecessarySpread,
rules::flake8_pie::rules::UnnecessaryDictKwargs,
rules::flake8_pie::rules::PreferListBuiltin,
rules::flake8_pie::rules::SingleStartsEndsWith,
rules::flake8_pie::rules::ReimplementedListBuiltin,
rules::flake8_pie::rules::MultipleStartsEndsWith,
rules::flake8_pie::rules::UnnecessaryComprehensionAnyAll,
// flake8-commas
rules::flake8_commas::rules::TrailingCommaMissing,
rules::flake8_commas::rules::TrailingCommaOnBareTupleProhibited,
rules::flake8_commas::rules::TrailingCommaProhibited,
rules::flake8_commas::rules::MissingTrailingComma,
rules::flake8_commas::rules::TrailingCommaOnBareTuple,
rules::flake8_commas::rules::ProhibitedTrailingComma,
// flake8-no-pep420
rules::flake8_no_pep420::rules::ImplicitNamespacePackage,
// flake8-executable
rules::flake8_executable::rules::ShebangNotExecutable,
rules::flake8_executable::rules::ShebangMissingExecutableFile,
rules::flake8_executable::rules::ShebangPython,
rules::flake8_executable::rules::ShebangWhitespace,
rules::flake8_executable::rules::ShebangNewline,
rules::flake8_executable::rules::ShebangMissingPython,
rules::flake8_executable::rules::ShebangLeadingWhitespace,
rules::flake8_executable::rules::ShebangNotFirstLine,
// flake8-type-checking
rules::flake8_type_checking::rules::TypingOnlyFirstPartyImport,
rules::flake8_type_checking::rules::TypingOnlyThirdPartyImport,
@@ -545,7 +559,7 @@ ruff_macros::register_rules!(
// tryceratops
rules::tryceratops::rules::RaiseVanillaClass,
rules::tryceratops::rules::RaiseVanillaArgs,
rules::tryceratops::rules::PreferTypeError,
rules::tryceratops::rules::TypeCheckWithoutTypeError,
rules::tryceratops::rules::ReraiseNoCause,
rules::tryceratops::rules::VerboseRaise,
rules::tryceratops::rules::TryConsiderElse,
@@ -598,18 +612,23 @@ ruff_macros::register_rules!(
rules::ruff::rules::AmbiguousUnicodeCharacterString,
rules::ruff::rules::AmbiguousUnicodeCharacterDocstring,
rules::ruff::rules::AmbiguousUnicodeCharacterComment,
rules::ruff::rules::UnpackInsteadOfConcatenatingToCollectionLiteral,
rules::ruff::rules::CollectionLiteralConcatenation,
rules::ruff::rules::AsyncioDanglingTask,
rules::ruff::rules::UnusedNOQA,
rules::ruff::rules::PairwiseOverZipped,
// flake8-django
rules::flake8_django::rules::NullableModelStringField,
rules::flake8_django::rules::LocalsInRenderFunction,
rules::flake8_django::rules::ExcludeWithModelForm,
rules::flake8_django::rules::AllWithModelForm,
rules::flake8_django::rules::ModelWithoutDunderStr,
rules::flake8_django::rules::NonLeadingReceiverDecorator,
rules::flake8_django::rules::DjangoNullableModelStringField,
rules::flake8_django::rules::DjangoLocalsInRenderFunction,
rules::flake8_django::rules::DjangoExcludeWithModelForm,
rules::flake8_django::rules::DjangoAllWithModelForm,
rules::flake8_django::rules::DjangoModelWithoutDunderStr,
rules::flake8_django::rules::DjangoNonLeadingReceiverDecorator,
);
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)?;
@@ -828,17 +847,17 @@ impl Rule {
| Rule::DocLineTooLong
| Rule::LineTooLong
| Rule::MixedSpacesAndTabs
| Rule::NoNewLineAtEndOfFile
| Rule::MissingNewlineAtEndOfFile
| Rule::UTF8EncodingDeclaration
| Rule::ShebangMissingExecutableFile
| Rule::ShebangNotExecutable
| Rule::ShebangNewline
| Rule::ShebangNotFirstLine
| Rule::BidirectionalUnicode
| Rule::ShebangPython
| Rule::ShebangWhitespace
| Rule::ShebangMissingPython
| Rule::ShebangLeadingWhitespace
| Rule::TrailingWhitespace
| Rule::IndentationContainsTabs
| Rule::BlankLineContainsWhitespace => LintSource::PhysicalLines,
| Rule::TabIndentation
| Rule::BlankLineWithWhitespace => LintSource::PhysicalLines,
Rule::AmbiguousUnicodeCharacterComment
| Rule::AmbiguousUnicodeCharacterDocstring
| Rule::AmbiguousUnicodeCharacterString
@@ -848,20 +867,25 @@ impl Rule {
| Rule::BadQuotesMultilineString
| Rule::CommentedOutCode
| Rule::MultiLineImplicitStringConcatenation
| Rule::InvalidCharacterBackspace
| Rule::InvalidCharacterSub
| Rule::InvalidCharacterEsc
| Rule::InvalidCharacterNul
| Rule::InvalidCharacterZeroWidthSpace
| Rule::ExtraneousParentheses
| Rule::InvalidEscapeSequence
| Rule::SingleLineImplicitStringConcatenation
| Rule::TrailingCommaMissing
| Rule::TrailingCommaOnBareTupleProhibited
| Rule::MissingTrailingComma
| Rule::TrailingCommaOnBareTuple
| Rule::MultipleStatementsOnOneLineColon
| Rule::UselessSemicolon
| Rule::MultipleStatementsOnOneLineSemicolon
| Rule::TrailingCommaProhibited
| Rule::ProhibitedTrailingComma
| Rule::TypeCommentInStub => LintSource::Tokens,
Rule::IOError => LintSource::Io,
Rule::UnsortedImports | Rule::MissingRequiredImport => LintSource::Imports,
Rule::ImplicitNamespacePackage | Rule::InvalidModuleName => LintSource::Filesystem,
#[cfg(feature = "logical_lines")]
#[cfg(debug_assertions)]
Rule::IndentationWithInvalidMultiple
| Rule::IndentationWithInvalidMultipleComment
| Rule::MissingWhitespace
@@ -901,7 +925,7 @@ impl Rule {
/// Pairs of checks that shouldn't be enabled together.
pub const INCOMPATIBLE_CODES: &[(Rule, Rule, &str); 2] = &[
(
Rule::NoBlankLineBeforeClass,
Rule::BlankLineBeforeClass,
Rule::OneBlankLineBeforeClass,
"`one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are \
incompatible. Ignoring `one-blank-line-before-class`.",
@@ -916,6 +940,7 @@ pub const INCOMPATIBLE_CODES: &[(Rule, Rule, &str); 2] = &[
#[cfg(test)]
mod tests {
use std::mem::size_of;
use strum::IntoEnumIterator;
use super::{Linter, Rule, RuleNamespace};
@@ -961,4 +986,9 @@ mod tests {
assert_eq!(code, format!("{}{rest}", linter.common_prefix()));
}
}
#[test]
fn rule_size() {
assert_eq!(2, size_of::<Rule>());
}
}

View File

@@ -0,0 +1,365 @@
use crate::registry::Rule;
use ruff_macros::CacheKey;
use std::fmt::{Debug, Formatter};
use std::iter::FusedIterator;
/// A set of [`Rule`]s.
///
/// Uses a bitset where a bit of one signals that the Rule with that [u16] is in this set.
#[derive(Clone, Default, CacheKey, PartialEq, Eq)]
pub struct RuleSet([u64; 9]);
impl RuleSet {
const EMPTY: [u64; 9] = [0; 9];
// 64 fits into a u16 without truncation
#[allow(clippy::cast_possible_truncation)]
const SLICE_BITS: u16 = u64::BITS as u16;
/// Returns an empty rule set.
pub const fn empty() -> Self {
Self(Self::EMPTY)
}
pub fn clear(&mut self) {
self.0 = Self::EMPTY;
}
#[inline]
pub const fn from_rule(rule: Rule) -> Self {
let rule = rule as u16;
let index = (rule / Self::SLICE_BITS) as usize;
debug_assert!(
index < Self::EMPTY.len(),
"Rule index out of bounds. Increase the size of the bitset array."
);
// The bit-position of this specific rule in the slice
let shift = rule % Self::SLICE_BITS;
// Set the index for that rule to 1
let mask = 1 << shift;
let mut bits = Self::EMPTY;
bits[index] = mask;
Self(bits)
}
#[inline]
pub const fn from_rules(rules: &[Rule]) -> Self {
let mut set = RuleSet::empty();
let mut i = 0;
// Uses a while because for loops are not allowed in const functions.
while i < rules.len() {
set = set.union(&RuleSet::from_rule(rules[i]));
i += 1;
}
set
}
/// Returns the union of the two rule sets `self` and `other`
///
/// ## Examples
///
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// let set_1 = RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::AnyType]);
/// let set_2 = RuleSet::from_rules(&[
/// Rule::BadQuotesInlineString,
/// Rule::BooleanPositionalValueInFunctionCall,
/// ]);
///
/// let union = set_1.union(&set_2);
///
/// assert!(union.contains(Rule::AmbiguousFunctionName));
/// assert!(union.contains(Rule::AnyType));
/// assert!(union.contains(Rule::BadQuotesInlineString));
/// assert!(union.contains(Rule::BooleanPositionalValueInFunctionCall));
/// ```
#[must_use]
pub const fn union(mut self, other: &Self) -> Self {
let mut i = 0;
while i < self.0.len() {
self.0[i] |= other.0[i];
i += 1;
}
self
}
/// Returns `self` without any of the rules contained in `other`.
///
/// ## Examples
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// let set_1 = RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::AnyType]);
/// let set_2 = RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::Debugger]);
///
/// let subtract = set_1.subtract(&set_2);
///
/// assert!(subtract.contains(Rule::AnyType));
/// assert!(!subtract.contains(Rule::AmbiguousFunctionName));
/// ```
#[must_use]
pub const fn subtract(mut self, other: &Self) -> Self {
let mut i = 0;
while i < self.0.len() {
self.0[i] ^= other.0[i];
i += 1;
}
self
}
/// Returns true if `self` and `other` contain at least one common rule.
///
/// ## Examples
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// let set_1 = RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::AnyType]);
///
/// assert!(set_1.intersects(&RuleSet::from_rules(&[
/// Rule::AnyType,
/// Rule::BadQuotesInlineString
/// ])));
///
/// assert!(!set_1.intersects(&RuleSet::from_rules(&[
/// Rule::BooleanPositionalValueInFunctionCall,
/// Rule::BadQuotesInlineString
/// ])));
/// ```
pub const fn intersects(&self, other: &Self) -> bool {
let mut i = 0;
while i < self.0.len() {
if self.0[i] & other.0[i] != 0 {
return true;
}
i += 1;
}
false
}
/// Returns `true` if this set contains no rules, `false` otherwise.
///
/// ## Examples
///
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// assert!(RuleSet::empty().is_empty());
/// assert!(
/// !RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::BadQuotesInlineString])
/// .is_empty()
/// );
/// ```
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the number of rules in this set.
///
/// ## Examples
///
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// assert_eq!(RuleSet::empty().len(), 0);
/// assert_eq!(
/// RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::BadQuotesInlineString]).len(),
/// 2
/// );
pub const fn len(&self) -> usize {
let mut len: u32 = 0;
let mut i = 0;
while i < self.0.len() {
len += self.0[i].count_ones();
i += 1;
}
len as usize
}
/// Inserts `rule` into the set.
///
/// ## Examples
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// let mut set = RuleSet::empty();
///
/// assert!(!set.contains(Rule::AnyType));
///
/// set.insert(Rule::AnyType);
///
/// assert!(set.contains(Rule::AnyType));
/// ```
pub fn insert(&mut self, rule: Rule) {
let set = std::mem::take(self);
*self = set.union(&RuleSet::from_rule(rule));
}
/// Removes `rule` from the set.
///
/// ## Examples
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// let mut set = RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::AnyType]);
///
/// set.remove(Rule::AmbiguousFunctionName);
///
/// assert!(set.contains(Rule::AnyType));
/// assert!(!set.contains(Rule::AmbiguousFunctionName));
/// ```
pub fn remove(&mut self, rule: Rule) {
let set = std::mem::take(self);
*self = set.subtract(&RuleSet::from_rule(rule));
}
/// Returns `true` if `rule` is in this set.
///
/// ## Examples
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// let set = RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::AnyType]);
///
/// assert!(set.contains(Rule::AmbiguousFunctionName));
/// assert!(!set.contains(Rule::BreakOutsideLoop));
/// ```
pub const fn contains(&self, rule: Rule) -> bool {
let rule = rule as u16;
let index = rule as usize / Self::SLICE_BITS as usize;
let shift = rule % Self::SLICE_BITS;
let mask = 1 << shift;
self.0[index] & mask != 0
}
/// Returns an iterator over the rules in this set.
///
/// ## Examples
///
/// ```rust
/// # use ruff::registry::{Rule, RuleSet};
/// let set = RuleSet::from_rules(&[Rule::AmbiguousFunctionName, Rule::AnyType]);
///
/// let iter: Vec<_> = set.iter().collect();
///
/// assert_eq!(iter, vec![Rule::AmbiguousFunctionName, Rule::AnyType]);
/// ```
pub fn iter(&self) -> RuleSetIterator {
RuleSetIterator {
set: self.clone(),
index: 0,
}
}
}
impl Debug for RuleSet {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_set().entries(self.iter()).finish()
}
}
impl FromIterator<Rule> for RuleSet {
fn from_iter<T: IntoIterator<Item = Rule>>(iter: T) -> Self {
let mut set = RuleSet::empty();
for rule in iter {
set.insert(rule);
}
set
}
}
impl Extend<Rule> for RuleSet {
fn extend<T: IntoIterator<Item = Rule>>(&mut self, iter: T) {
let set = std::mem::take(self);
*self = set.union(&RuleSet::from_iter(iter));
}
}
impl IntoIterator for RuleSet {
type Item = Rule;
type IntoIter = RuleSetIterator;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl IntoIterator for &RuleSet {
type Item = Rule;
type IntoIter = RuleSetIterator;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
pub struct RuleSetIterator {
set: RuleSet,
index: u16,
}
impl Iterator for RuleSetIterator {
type Item = Rule;
fn next(&mut self) -> Option<Self::Item> {
loop {
let slice = self.set.0.get_mut(self.index as usize)?;
// `trailing_zeros` is guaranteed to return a value in [0;64]
#[allow(clippy::cast_possible_truncation)]
let bit = slice.trailing_zeros() as u16;
if bit < RuleSet::SLICE_BITS {
*slice ^= 1 << bit;
let rule_value = self.index * RuleSet::SLICE_BITS + bit;
// SAFETY: RuleSet guarantees that only valid rules are stored in the set.
#[allow(unsafe_code)]
return Some(unsafe { std::mem::transmute(rule_value) });
}
self.index += 1;
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.set.len();
(len, Some(len))
}
}
impl ExactSizeIterator for RuleSetIterator {}
impl FusedIterator for RuleSetIterator {}
#[cfg(test)]
mod tests {
use crate::registry::{Rule, RuleSet};
use strum::IntoEnumIterator;
/// Tests that the set can contain all rules
#[test]
fn test_all_rules() {
for rule in Rule::iter() {
let set = RuleSet::from_rule(rule);
assert!(set.contains(rule));
}
let all_rules_set: RuleSet = Rule::iter().collect();
let all_rules: Vec<_> = all_rules_set.iter().collect();
let expected_rules: Vec<_> = Rule::iter().collect();
assert_eq!(all_rules, expected_rules);
}
}

View File

@@ -14,6 +14,7 @@ use ruff_python_stdlib::path::is_python_file;
use rustc_hash::FxHashSet;
use crate::fs;
use crate::jupyter::is_jupyter_notebook;
use crate::settings::configuration::Configuration;
use crate::settings::pyproject::settings_toml;
use crate::settings::{pyproject, AllSettings, Settings};
@@ -128,7 +129,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)?;
@@ -193,15 +194,15 @@ fn match_exclusion(file_path: &str, file_basename: &str, exclusion: &globset::Gl
exclusion.is_match(file_path) || exclusion.is_match(file_basename)
}
/// Return `true` if the [`DirEntry`] appears to be that of a Python file.
/// Return `true` if the [`DirEntry`] appears to be that of a Python file or jupyter notebook.
pub fn is_python_entry(entry: &DirEntry) -> bool {
is_python_file(entry.path())
(is_python_file(entry.path()) || is_jupyter_notebook(entry.path()))
&& !entry
.file_type()
.map_or(false, |file_type| file_type.is_dir())
}
/// Find all Python (`.py` and `.pyi` files) in a set of paths.
/// Find all Python (`.py`, `.pyi` and `.ipynb` files) in a set of paths.
pub fn python_files_in_path(
paths: &[PathBuf],
pyproject_strategy: &PyprojectDiscovery,
@@ -260,38 +261,6 @@ pub fn python_files_in_path(
std::sync::Mutex::new(vec![]);
walker.run(|| {
Box::new(|result| {
// Search for the `pyproject.toml` file in this directory, before we visit any
// of its contents.
if pyproject_strategy.is_hierarchical() {
if let Ok(entry) = &result {
if entry
.file_type()
.map_or(false, |file_type| file_type.is_dir())
{
match settings_toml(entry.path()) {
Ok(Some(pyproject)) => match resolve_scoped_settings(
&pyproject,
&Relativity::Parent,
processor,
) {
Ok((root, settings)) => {
resolver.write().unwrap().add(root, settings);
}
Err(err) => {
*error.lock().unwrap() = Err(err);
return WalkState::Quit;
}
},
Ok(None) => {}
Err(err) => {
*error.lock().unwrap() = Err(err);
return WalkState::Quit;
}
}
}
}
}
// Respect our own exclusion behavior.
if let Ok(entry) = &result {
if entry.depth() > 0 {
@@ -324,6 +293,38 @@ pub fn python_files_in_path(
}
}
// Search for the `pyproject.toml` file in this directory, before we visit any
// of its contents.
if pyproject_strategy.is_hierarchical() {
if let Ok(entry) = &result {
if entry
.file_type()
.map_or(false, |file_type| file_type.is_dir())
{
match settings_toml(entry.path()) {
Ok(Some(pyproject)) => match resolve_scoped_settings(
&pyproject,
&Relativity::Parent,
processor,
) {
Ok((root, settings)) => {
resolver.write().unwrap().add(root, settings);
}
Err(err) => {
*error.lock().unwrap() = Err(err);
return WalkState::Quit;
}
},
Ok(None) => {}
Err(err) => {
*error.lock().unwrap() = Err(err);
return WalkState::Quit;
}
}
}
}
}
if result.as_ref().map_or(true, |entry| {
// Accept all files that are passed-in directly.
(entry.depth() == 0 && entry.file_type().map_or(false, |ft| ft.is_file()))

View File

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

View File

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

View File

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

View File

@@ -13,16 +13,16 @@ mod tests {
use crate::settings;
use crate::test::test_path;
#[test_case(Rule::SysVersionSlice3Referenced, Path::new("YTT101.py"); "YTT101")]
#[test_case(Rule::SysVersion2Referenced, Path::new("YTT102.py"); "YTT102")]
#[test_case(Rule::SysVersionSlice3, Path::new("YTT101.py"); "YTT101")]
#[test_case(Rule::SysVersion2, Path::new("YTT102.py"); "YTT102")]
#[test_case(Rule::SysVersionCmpStr3, Path::new("YTT103.py"); "YTT103")]
#[test_case(Rule::SysVersionInfo0Eq3Referenced, Path::new("YTT201.py"); "YTT201")]
#[test_case(Rule::SixPY3Referenced, Path::new("YTT202.py"); "YTT202")]
#[test_case(Rule::SysVersionInfo0Eq3, Path::new("YTT201.py"); "YTT201")]
#[test_case(Rule::SixPY3, Path::new("YTT202.py"); "YTT202")]
#[test_case(Rule::SysVersionInfo1CmpInt, Path::new("YTT203.py"); "YTT203")]
#[test_case(Rule::SysVersionInfoMinorCmpInt, Path::new("YTT204.py"); "YTT204")]
#[test_case(Rule::SysVersion0Referenced, Path::new("YTT301.py"); "YTT301")]
#[test_case(Rule::SysVersion0, Path::new("YTT301.py"); "YTT301")]
#[test_case(Rule::SysVersionCmpStr10, Path::new("YTT302.py"); "YTT302")]
#[test_case(Rule::SysVersionSlice1Referenced, Path::new("YTT303.py"); "YTT303")]
#[test_case(Rule::SysVersionSlice1, Path::new("YTT303.py"); "YTT303")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(

View File

@@ -9,9 +9,9 @@ use crate::checkers::ast::Checker;
use crate::registry::Rule;
#[violation]
pub struct SysVersionSlice3Referenced;
pub struct SysVersionSlice3;
impl Violation for SysVersionSlice3Referenced {
impl Violation for SysVersionSlice3 {
#[derive_message_formats]
fn message(&self) -> String {
format!("`sys.version[:3]` referenced (python3.10), use `sys.version_info`")
@@ -19,9 +19,9 @@ impl Violation for SysVersionSlice3Referenced {
}
#[violation]
pub struct SysVersion2Referenced;
pub struct SysVersion2;
impl Violation for SysVersion2Referenced {
impl Violation for SysVersion2 {
#[derive_message_formats]
fn message(&self) -> String {
format!("`sys.version[2]` referenced (python3.10), use `sys.version_info`")
@@ -39,9 +39,9 @@ impl Violation for SysVersionCmpStr3 {
}
#[violation]
pub struct SysVersionInfo0Eq3Referenced;
pub struct SysVersionInfo0Eq3;
impl Violation for SysVersionInfo0Eq3Referenced {
impl Violation for SysVersionInfo0Eq3 {
#[derive_message_formats]
fn message(&self) -> String {
format!("`sys.version_info[0] == 3` referenced (python4), use `>=`")
@@ -49,9 +49,9 @@ impl Violation for SysVersionInfo0Eq3Referenced {
}
#[violation]
pub struct SixPY3Referenced;
pub struct SixPY3;
impl Violation for SixPY3Referenced {
impl Violation for SixPY3 {
#[derive_message_formats]
fn message(&self) -> String {
format!("`six.PY3` referenced (python4), use `not six.PY2`")
@@ -85,9 +85,9 @@ impl Violation for SysVersionInfoMinorCmpInt {
}
#[violation]
pub struct SysVersion0Referenced;
pub struct SysVersion0;
impl Violation for SysVersion0Referenced {
impl Violation for SysVersion0 {
#[derive_message_formats]
fn message(&self) -> String {
format!("`sys.version[0]` referenced (python10), use `sys.version_info`")
@@ -105,9 +105,9 @@ impl Violation for SysVersionCmpStr10 {
}
#[violation]
pub struct SysVersionSlice1Referenced;
pub struct SysVersionSlice1;
impl Violation for SysVersionSlice1Referenced {
impl Violation for SysVersionSlice1 {
#[derive_message_formats]
fn message(&self) -> String {
format!("`sys.version[:1]` referenced (python10), use `sys.version_info`")
@@ -137,25 +137,17 @@ pub fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
} = &upper.node
{
if *i == BigInt::from(1)
&& checker
.settings
.rules
.enabled(&Rule::SysVersionSlice1Referenced)
&& checker.settings.rules.enabled(Rule::SysVersionSlice1)
{
checker.diagnostics.push(Diagnostic::new(
SysVersionSlice1Referenced,
Range::from(value),
));
checker
.diagnostics
.push(Diagnostic::new(SysVersionSlice1, Range::from(value)));
} else if *i == BigInt::from(3)
&& checker
.settings
.rules
.enabled(&Rule::SysVersionSlice3Referenced)
&& checker.settings.rules.enabled(Rule::SysVersionSlice3)
{
checker.diagnostics.push(Diagnostic::new(
SysVersionSlice3Referenced,
Range::from(value),
));
checker
.diagnostics
.push(Diagnostic::new(SysVersionSlice3, Range::from(value)));
}
}
}
@@ -164,18 +156,15 @@ pub fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
value: Constant::Int(i),
..
} => {
if *i == BigInt::from(2)
&& checker.settings.rules.enabled(&Rule::SysVersion2Referenced)
if *i == BigInt::from(2) && checker.settings.rules.enabled(Rule::SysVersion2) {
checker
.diagnostics
.push(Diagnostic::new(SysVersion2, Range::from(value)));
} else if *i == BigInt::from(0) && checker.settings.rules.enabled(Rule::SysVersion0)
{
checker
.diagnostics
.push(Diagnostic::new(SysVersion2Referenced, Range::from(value)));
} else if *i == BigInt::from(0)
&& checker.settings.rules.enabled(&Rule::SysVersion0Referenced)
{
checker
.diagnostics
.push(Diagnostic::new(SysVersion0Referenced, Range::from(value)));
.push(Diagnostic::new(SysVersion0, Range::from(value)));
}
}
@@ -207,15 +196,11 @@ pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &
) = (ops, comparators)
{
if *n == BigInt::from(3)
&& checker
.settings
.rules
.enabled(&Rule::SysVersionInfo0Eq3Referenced)
&& checker.settings.rules.enabled(Rule::SysVersionInfo0Eq3)
{
checker.diagnostics.push(Diagnostic::new(
SysVersionInfo0Eq3Referenced,
Range::from(left),
));
checker
.diagnostics
.push(Diagnostic::new(SysVersionInfo0Eq3, Range::from(left)));
}
}
} else if *i == BigInt::from(1) {
@@ -231,7 +216,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 +244,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 +271,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)));
@@ -309,6 +294,6 @@ pub fn name_or_attribute(checker: &mut Checker, expr: &Expr) {
{
checker
.diagnostics
.push(Diagnostic::new(SixPY3Referenced, Range::from(expr)));
.push(Diagnostic::new(SixPY3, Range::from(expr)));
}
}

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_2020/mod.rs
expression: diagnostics
---
- kind:
name: SysVersionSlice3Referenced
name: SysVersionSlice3
body: "`sys.version[:3]` referenced (python3.10), use `sys.version_info`"
suggestion: ~
fixable: false
@@ -16,7 +16,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: SysVersionSlice3Referenced
name: SysVersionSlice3
body: "`sys.version[:3]` referenced (python3.10), use `sys.version_info`"
suggestion: ~
fixable: false
@@ -29,7 +29,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: SysVersionSlice3Referenced
name: SysVersionSlice3
body: "`sys.version[:3]` referenced (python3.10), use `sys.version_info`"
suggestion: ~
fixable: false

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_2020/mod.rs
expression: diagnostics
---
- kind:
name: SysVersion2Referenced
name: SysVersion2
body: "`sys.version[2]` referenced (python3.10), use `sys.version_info`"
suggestion: ~
fixable: false
@@ -16,7 +16,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: SysVersion2Referenced
name: SysVersion2
body: "`sys.version[2]` referenced (python3.10), use `sys.version_info`"
suggestion: ~
fixable: false

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_2020/mod.rs
expression: diagnostics
---
- kind:
name: SysVersionInfo0Eq3Referenced
name: SysVersionInfo0Eq3
body: "`sys.version_info[0] == 3` referenced (python4), use `>=`"
suggestion: ~
fixable: false
@@ -16,7 +16,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: SysVersionInfo0Eq3Referenced
name: SysVersionInfo0Eq3
body: "`sys.version_info[0] == 3` referenced (python4), use `>=`"
suggestion: ~
fixable: false
@@ -29,7 +29,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: SysVersionInfo0Eq3Referenced
name: SysVersionInfo0Eq3
body: "`sys.version_info[0] == 3` referenced (python4), use `>=`"
suggestion: ~
fixable: false
@@ -42,7 +42,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: SysVersionInfo0Eq3Referenced
name: SysVersionInfo0Eq3
body: "`sys.version_info[0] == 3` referenced (python4), use `>=`"
suggestion: ~
fixable: false

View File

@@ -3,7 +3,7 @@ source: crates/ruff/src/rules/flake8_2020/mod.rs
expression: diagnostics
---
- kind:
name: SixPY3Referenced
name: SixPY3
body: "`six.PY3` referenced (python4), use `not six.PY2`"
suggestion: ~
fixable: false
@@ -16,7 +16,7 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
name: SixPY3Referenced
name: SixPY3
body: "`six.PY3` referenced (python4), use `not six.PY2`"
suggestion: ~
fixable: false

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