Compare commits

...

41 Commits

Author SHA1 Message Date
Charlie Marsh
84300e00ff Bump version to 0.0.229 2023-01-21 13:18:06 -05:00
Charlie Marsh
fbee95a668 Avoid flagging redefined imports as unused in same-scope (#2065)
This is effectively a revert of #1173, to favor false-negatives over false-positives in the same-scope case.

Closes #2044.
2023-01-21 12:50:21 -05:00
Simon Brugman
afcf5c0ee0 feat: plugin scaffold for tryceratops with TRY300 (#2055)
Renamed to TRY to avoid conflicts, as proposed in https://github.com/guilatrova/tryceratops/pull/55

https://github.com/guilatrova/tryceratops/blob/main/docs/violations/TC300.md

See: #2056
2023-01-21 11:25:10 -05:00
Maksudul Haque
0c30768288 [flake8-builtins] Add builtins-ignorelist Option (#2061)
Closes #2053.
2023-01-21 11:09:04 -05:00
Colin Delahunty
80295f335b Pyupgrade: Printf string formatting (#1803) 2023-01-21 09:37:22 -05:00
Charlie Marsh
465943adf7 Revert "Upgrade to toml v0.5.11" (#2058)
This _did_ fix https://github.com/charliermarsh/ruff/issues/1894, but was a little premature. `toml` doesn't actually depend on `toml-edit` yet, and `v0.5.11` was mostly about deprecations AFAICT. So upgrading might solve that issue, but could introduce other incompatibilities, and I'd like to minimize churn. I expect that `toml` will have a new release soon, so we can revert this revert.

Reverts charliermarsh/ruff#2040.
2023-01-21 07:54:56 -05:00
Charlie Marsh
38eed292e4 Avoid removing comments in RUF005 (#2057)
Closes #2054.
2023-01-21 07:37:25 -05:00
Harutaka Kawamura
883e650a35 Fix S101 range to only highlight assert (#2052)
Fix:

```
resources/test/fixtures/flake8_bandit/S101.py:2:1: S101 Use of `assert` detected
  |
2 | assert True
  | ^^^^^^^^^^^ S101
  |

resources/test/fixtures/flake8_bandit/S101.py:8:5: S101 Use of `assert` detected
  |
8 |     assert x == 1
  |     ^^^^^^^^^^^^^ S101
  |

resources/test/fixtures/flake8_bandit/S101.py:11:5: S101 Use of `assert` detected
   |
11 |     assert x == 2
   |     ^^^^^^^^^^^^^ S101
   |

Found 3 error(s).
```

to:

```
resources/test/fixtures/flake8_bandit/S101.py:2:1: S101 Use of `assert` detected
  |
2 | assert True
  | ^^^^^^ S101
  |

resources/test/fixtures/flake8_bandit/S101.py:8:5: S101 Use of `assert` detected
  |
8 |     assert x == 1
  |     ^^^^^^ S101
  |

resources/test/fixtures/flake8_bandit/S101.py:11:5: S101 Use of `assert` detected
   |
11 |     assert x == 2
   |     ^^^^^^ S101
   |
```
2023-01-21 07:15:00 -05:00
Harutaka Kawamura
eb1b5e5454 De-duplicate SIM102 (#2050)
The idea is the same as #1867. Avoids emitting `SIM102` twice for the following code:

```python
if a:
    if b:
        if c:
            d
```

```
resources/test/fixtures/flake8_simplify/SIM102.py:1:1: SIM102 Use a single `if` statement instead of nested `if` statements
resources/test/fixtures/flake8_simplify/SIM102.py:2:5: SIM102 Use a single `if` statement instead of nested `if` statements
```
2023-01-20 23:38:52 -05:00
Charlie Marsh
8e558a3458 Add scaffolding for flake8-type-checking extension (#2048)
This PR adds the scaffolding files for `flake8-type-checking`, along with the simplest rule (`empty-type-checking-block`), just as an example to get us started.

See: #1785.
2023-01-20 22:41:36 -05:00
Martin Fischer
4e4643aa5d refactor: Decouple Rule from linter prefixes
543865c96b introduced
RuleCode::origin() -> RuleOrigin generation via a macro, while that
signature now has been renamed to Rule::origin() -> Linter we actually
want to get rid of it since rules and linters shouldn't be this tightly
coupled (since one rule can exist in multiple linters).

Another disadvantage of the previous approach was that the prefixes
had to be defined in ruff_macros/src/prefixes.rs, which was easy to
miss when defining new linters in src/*, case in point
INP001 => violations::ImplicitNamespacePackage has in the meantime been
added without ruff_macros/src/prefixes.rs being updated accordingly
which resulted in `ruff --explain INP001` mistakenly reporting that the
rule belongs to isort (since INP001 starts with the isort prefix "I").
The derive proc macro introduced in this commit requires every variant
to have at least one #[prefix = "..."], eliminating such mistakes.
2023-01-20 20:25:57 -05:00
Martin Fischer
b19258a243 refactor: Rename RuleCodePrefix to RuleSelector
More accurate since the enum also encompasses:

* ALL (which isn't a prefix at all)

* fully-qualified rule codes (which aren't prefixes unless you say
  they're a prefix to the empty string but that's not intuitive)
2023-01-20 20:25:57 -05:00
Martin Fischer
7fc42f8f85 refactor: Rename RuleOrigin to Linter
"origin" was accurate since ruff rules are currently always modeled
after one origin (except the Ruff-specific rules).

Since we however want to introduce a many-to-many mapping between codes
and rules, the term "origin" no longer makes much sense. Rules usually
don't have multiple origins but one linter implements a rule first and
then others implement it later (often inspired from another linter).
But we don't actually care much about where a rule originates from when
mapping multiple rule codes to one rule implementation, so renaming
RuleOrigin to Linter is less confusing with the many-to-many system.
2023-01-20 20:25:57 -05:00
Dmitry Dygalo
babe1eb7be perf: Reduce allocations (#2045)
I found a few places where some allocations could be avoided.
2023-01-20 20:06:48 -05:00
Simon Brugman
608b2191aa [flake8-executable] EXE003-005 (#2023)
Tracking issue: https://github.com/charliermarsh/ruff/issues/2024

Implementation for EXE003, EXE004 and EXE005 of `flake8-executable` 
(shebang should contain "python", not have whitespace before, and should be on the first line)

Please take in mind that this is my first rust contribution.

The remaining EXE-rules are a combination of shebang (`lines.rs`), file permissions (`fs.rs`) and if-conditions (`ast.rs`). I was not able to find other rules that have interactions/dependencies in them. Any advice on how this can be best implemented would be very welcome.

For autofixing `EXE005`, I had in mind to _move_  the shebang line to the top op the file. This could be achieved by a combination of `Fix::insert` and `Fix::delete` (multiple fixes per diagnostic), or by implementing a dedicated `Fix::move`, or perhaps in other ways. For now I've left it out, but keen on hearing what you think would be most consistent with the package, and pointer where to start (if at all).

---
If you care about another testimonial:
`ruff` not only helps staying on top of the many excellent flake8 plugins and other Python code quality tools that are available, it also applies them at baffling speed.
(Planning to implement it soon for github.com/pandas-profiling/pandas-profiling (as largest contributor) and github.com/ing-bank/popmon.)
2023-01-20 18:19:07 -05:00
Eric Roberts
3939c2dbf7 Add support for pycodestyle E101 (#2038)
Rule described here: https://www.flake8rules.com/rules/E101.html

I tried to follow contributing guidelines closely, I've never worked with Rust before. Stumbled across Ruff a few days ago and would like to use it in our project, but we use a bunch of flake8 rules that are not yet implemented in ruff, so I decided to give it a go.
2023-01-20 17:24:58 -05:00
Charlie Marsh
20a9252e92 Upgrade to toml v0.5.11 (#2040)
In #1680, we moved over to `toml_edit`. But it looks like `toml` now uses `toml_edit`, and has implemented some improvements (e.g., this closes #1894).
2023-01-20 17:20:45 -05:00
Hugo van Kemenade
a0e3347e43 README: --force-exclude is already set (#2042)
Re: https://github.com/charliermarsh/ruff-pre-commit/issues/19 / https://github.com/charliermarsh/ruff-pre-commit/pull/20

This is now always set, no need to include it in the README example.
2023-01-20 17:20:22 -05:00
Charlie Marsh
9e704a7c63 Only fix true-false returns for return-bool-condition-directly (#2037)
Closes #2035.
2023-01-20 13:17:19 -05:00
Zeddicus414
c9da98e0b7 Fix D404 NoThisPrefix not working with whitespace. (#2036)
D404 should trigger for """ This is a docstring."""

Add a few tests to ensure the fix worked.
2023-01-20 13:01:31 -05:00
Charlie Marsh
5377d24507 Bump version to 0.0.228 2023-01-20 09:58:56 -05:00
Florian Best
db8e4500ee fix(pydocstyle): Avoid trimming docstring if starts with leading quote (#2027)
Fixes: #2017

looks like the other way round is also possible to break:

```""" "foo"""`
2023-01-20 09:57:48 -05:00
Aarni Koskela
bd2de5624e Move readme dev details to CONTRIBUTING.md and fix contradictions (#2030)
Following up on #2018/#2019 discussion, this moves the readme's development-related bits to `CONTRIBUTING.md` to avoid duplication, and fixes up the commands accordingly 😄
2023-01-20 09:23:28 -05:00
Aarni Koskela
3a81f893cc Bump terminfo to remove a whole bunch of unnecessary dependencies (#2022)
See 6281c6b8f7

```
$ cargo update -p terminfo
    Updating crates.io index
    Removing cfg-if v0.1.10
    Removing dirs v2.0.2
    Removing getrandom v0.1.16
    Removing phf v0.8.0
    Updating phf_codegen v0.8.0 -> v0.11.1
    Updating phf_generator v0.8.0 -> v0.11.1
    Removing phf_shared v0.8.0
    Removing rand v0.7.3
    Removing rand_chacha v0.2.2
    Removing rand_core v0.5.1
    Removing rand_hc v0.2.0
    Removing rand_pcg v0.2.1
    Updating terminfo v0.7.3 -> v0.7.5
    Removing wasi v0.9.0+wasi-snapshot-preview1
```
2023-01-20 09:09:02 -05:00
Charlie Marsh
fd6dc2a343 Use platform-appropriate newline character for LibCST embedding (#2028)
Closes #2026.
2023-01-20 09:08:04 -05:00
Martin Fischer
8693236f9e Make CI test add_*.py scripts 2023-01-20 08:09:54 -05:00
Martin Fischer
44e2b6208a fix: Update add_rule.py to create new files for rules 2023-01-20 08:09:54 -05:00
Martin Fischer
16c81f75c2 fix: Update add_rule.py to account for 16e79c8d 2023-01-20 08:09:54 -05:00
Martin Fischer
e1d6ac3265 fix: Update add_plugin.py to account for 9dc66b5a 2023-01-20 08:09:54 -05:00
Martin Fischer
3aec1100f5 fix: Update add_plugin.py to account for b78b6f27 2023-01-20 08:09:54 -05:00
Martin Fischer
c00df647e1 fix: Update add_rule.py to account for 81996f1bc 2023-01-20 08:09:54 -05:00
Martin Fischer
f012877be1 Add scripts/pyproject.toml to use ruff for ruff :) 2023-01-20 08:09:54 -05:00
Martin Fischer
ff6defc988 refactor: Introduce get_indent helper for scripts 2023-01-20 08:09:54 -05:00
Martin Fischer
67ca50e9f2 refactor: Reduce code duplication in scripts/ 2023-01-20 08:09:54 -05:00
Martin Fischer
6cc160bc2b Mark scripts/add_*.py as executable 2023-01-20 08:09:54 -05:00
Ville Skyttä
4bdf506d80 Grammar fixes (#2014) 2023-01-20 07:44:23 -05:00
Charlie Marsh
4af2353ef9 Avoid trimming docstring if ends in trailing quote (#2025)
Closes #2017.
2023-01-20 07:41:58 -05:00
Ville Skyttä
6072edf5bf Note .astimezone() in call-datetime-strptime-without-zone message (#2015) 2023-01-20 07:40:34 -05:00
Martin Fischer
4061eeeb32 Update CI to use MSRV for cargo test and build
As per Cargo.toml our minimal supported Rust version is 1.65.0, so we
should be using that version in our CI for cargo test and cargo build.

This was apparently accidentally changed in
79ca66ace5.
2023-01-20 07:39:40 -05:00
Aarni Koskela
bea6deb0c3 Port pydocstyle code 401 (ImperativeMood) (#1999)
This adds support for pydocstyle code D401 using the `imperative` crate.
2023-01-20 07:18:27 -05:00
Colin Delahunty
81db00a3c4 Pyupgrade: Extraneous parenthesis (#1926) 2023-01-20 00:04:07 -05:00
147 changed files with 4439 additions and 1126 deletions

View File

@@ -70,7 +70,7 @@ jobs:
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2022-11-01
toolchain: 1.65.0
override: true
- uses: Swatinem/rust-cache@v1
- run: cargo install cargo-insta
@@ -84,6 +84,21 @@ jobs:
# Setting RUSTDOCFLAGS because `cargo doc --check` isn't yet implemented (https://github.com/rust-lang/cargo/issues/10025).
- run: RUSTDOCFLAGS="-D warnings" cargo doc --all --no-deps
scripts:
name: "test scripts"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
override: true
- uses: Swatinem/rust-cache@v1
- run: ./scripts/add_rule.py --name DoTheThing --code PLC999 --linter pylint
- run: cargo check
- run: ./scripts/add_plugin.py test --url https://pypi.org/project/-test/0.1.0/
- run: cargo check
# TODO(charlie): Re-enable the `wasm-pack` tests.
# See: https://github.com/charliermarsh/ruff/issues/1425
# wasm-pack-test:
@@ -122,7 +137,7 @@ jobs:
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2022-11-01
toolchain: 1.65.0
override: true
- uses: Swatinem/rust-cache@v1
- uses: actions/setup-python@v4

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.227
rev: v0.0.229
hooks:
- id: ruff

View File

@@ -37,13 +37,16 @@ After cloning the repository, run Ruff locally with:
cargo run resources/test/fixtures --no-cache
```
Prior to opening a pull request, ensure that your code has been auto-formatted, and that it passes
both the lint and test validation checks:
Prior to opening a pull request, ensure that your code has been auto-formatted,
and that it passes both the lint and test validation checks.
For rustfmt and Clippy, we use [nightly Rust][nightly], as it is stricter than stable Rust.
(However, tests and builds use stable Rust.)
```shell
cargo +nightly fmt --all # Auto-formatting...
cargo +nightly clippy --all # Linting...
cargo +nightly test --all # Testing...
cargo +nightly clippy --fix --workspace --all-targets --all-features -- -W clippy::pedantic # Linting...
cargo test --all # Testing...
```
These checks will run on GitHub Actions when you open your Pull Request, but running them locally
@@ -66,7 +69,7 @@ At a high level, the steps involved in adding a new lint rule are as follows:
6. Update the generated files (documentation and generated code).
To define the violation, start by creating a dedicated file for your rule under the appropriate
rule origin (e.g., `src/rules/flake8_bugbear/rules/abstract_base_class.rs`). That file should
rule linter (e.g., `src/rules/flake8_bugbear/rules/abstract_base_class.rs`). That file should
contain a struct defined via `define_violation!`, along with a function that creates the violation
based on any required inputs. (Many of the existing examples live in `src/violations.rs`, but we're
looking to place new rules in their own files.)
@@ -78,7 +81,7 @@ collecting diagnostics as it goes.
If you need to inspect the AST, you can run `cargo +nightly dev print-ast` with a Python file. Grep
for the `Check::new` invocations to understand how other, similar rules are implemented.
To add a test fixture, create a file under `resources/test/fixtures/[origin]`, named to match
To add a test fixture, create a file under `resources/test/fixtures/[linter]`, named to match
the code you defined earlier (e.g., `resources/test/fixtures/pycodestyle/E402.py`). This file should
contain a variety of violations and non-violations designed to evaluate and demonstrate the behavior
of your lint rule.
@@ -87,7 +90,7 @@ Run `cargo +nightly dev generate-all` to generate the code for your new fixture.
locally with (e.g.) `cargo run resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402`.
Once you're satisfied with the output, codify the behavior as a snapshot test by adding a new
`test_case` macro in the relevant `src/[origin]/mod.rs` file. Then, run `cargo test --all`.
`test_case` macro in the relevant `src/[linter]/mod.rs` file. Then, run `cargo test --all`.
Your test will fail, but you'll be prompted to follow-up with `cargo insta review`. Accept the
generated snapshot, then commit the snapshot file alongside the rest of your changes.
@@ -127,3 +130,5 @@ them to [PyPI](https://pypi.org/project/ruff/).
Ruff follows the [semver](https://semver.org/) versioning standard. However, as pre-1.0 software,
even patch releases may contain [non-backwards-compatible changes](https://semver.org/#spec-item-4).
[nightly]: https://rust-lang.github.io/rustup/concepts/channels.html#working-with-nightly-rust

332
Cargo.lock generated
View File

@@ -14,7 +14,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom 0.2.8",
"getrandom",
"once_cell",
"version_check",
]
@@ -197,12 +197,6 @@ version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -413,7 +407,7 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"wasm-bindgen",
]
@@ -439,7 +433,7 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
]
[[package]]
@@ -484,7 +478,7 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"crossbeam-utils",
]
@@ -494,7 +488,7 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
@@ -506,7 +500,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
@@ -518,7 +512,7 @@ version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
]
[[package]]
@@ -592,16 +586,6 @@ dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
dependencies = [
"cfg-if 0.1.10",
"dirs-sys",
]
[[package]]
name = "dirs"
version = "4.0.0"
@@ -617,7 +601,7 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"dirs-sys-next",
]
@@ -721,7 +705,7 @@ version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"libc",
"redox_syscall",
"windows-sys",
@@ -735,7 +719,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.227"
version = "0.0.229"
dependencies = [
"anyhow",
"clap 4.0.32",
@@ -786,24 +770,13 @@ dependencies = [
"libc",
]
[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"js-sys",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
@@ -926,6 +899,16 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "imperative"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f92123bf2fe0d9f1b5df1964727b970ca3b2d0203d47cf97fb1f36d856b6398"
dependencies = [
"phf 0.11.1",
"rust-stemmers",
]
[[package]]
name = "indexmap"
version = "1.9.2"
@@ -976,7 +959,7 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
]
[[package]]
@@ -1186,7 +1169,7 @@ version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
]
[[package]]
@@ -1265,7 +1248,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
"cfg-if",
"libc",
"static_assertions",
]
@@ -1419,7 +1402,7 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
@@ -1493,15 +1476,6 @@ dependencies = [
"indexmap",
]
[[package]]
name = "phf"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_shared 0.8.0",
]
[[package]]
name = "phf"
version = "0.10.1"
@@ -1512,13 +1486,12 @@ dependencies = [
]
[[package]]
name = "phf_codegen"
version = "0.8.0"
name = "phf"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c"
dependencies = [
"phf_generator 0.8.0",
"phf_shared 0.8.0",
"phf_shared 0.11.1",
]
[[package]]
@@ -1532,13 +1505,13 @@ dependencies = [
]
[[package]]
name = "phf_generator"
version = "0.8.0"
name = "phf_codegen"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770"
dependencies = [
"phf_shared 0.8.0",
"rand 0.7.3",
"phf_generator 0.11.1",
"phf_shared 0.11.1",
]
[[package]]
@@ -1548,16 +1521,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
dependencies = [
"phf_shared 0.10.0",
"rand 0.8.5",
"rand",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
name = "phf_generator"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf"
dependencies = [
"siphasher",
"phf_shared 0.11.1",
"rand",
]
[[package]]
@@ -1569,6 +1543,15 @@ dependencies = [
"siphasher",
]
[[package]]
name = "phf_shared"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676"
dependencies = [
"siphasher",
]
[[package]]
name = "pico-args"
version = "0.4.2"
@@ -1724,20 +1707,6 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom 0.1.16",
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc",
"rand_pcg",
]
[[package]]
name = "rand"
version = "0.8.5"
@@ -1745,18 +1714,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core 0.5.1",
"rand_chacha",
"rand_core",
]
[[package]]
@@ -1766,16 +1725,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.4",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom 0.1.16",
"rand_core",
]
[[package]]
@@ -1784,25 +1734,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.8",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
dependencies = [
"rand_core 0.5.1",
"getrandom",
]
[[package]]
@@ -1842,7 +1774,7 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom 0.2.8",
"getrandom",
"redox_syscall",
"thiserror",
]
@@ -1906,23 +1838,24 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.227"
version = "0.0.229"
dependencies = [
"anyhow",
"bitflags",
"cfg-if 1.0.0",
"cfg-if",
"chrono",
"clap 4.0.32",
"colored",
"console_error_panic_hook",
"console_log",
"criterion",
"dirs 4.0.0",
"dirs",
"fern",
"getrandom 0.2.8",
"getrandom",
"glob",
"globset",
"ignore",
"imperative",
"insta",
"itertools",
"js-sys",
@@ -1938,9 +1871,9 @@ dependencies = [
"ropey",
"ruff_macros",
"rustc-hash",
"rustpython-ast",
"rustpython-common",
"rustpython-parser",
"rustpython-ast 0.2.0 (git+https://github.com/RustPython/RustPython.git?rev=62aa942bf506ea3d41ed0503b947b84141fdaa3c)",
"rustpython-common 0.2.0 (git+https://github.com/RustPython/RustPython.git?rev=62aa942bf506ea3d41ed0503b947b84141fdaa3c)",
"rustpython-parser 0.2.0 (git+https://github.com/RustPython/RustPython.git?rev=62aa942bf506ea3d41ed0503b947b84141fdaa3c)",
"schemars",
"semver",
"serde",
@@ -1960,7 +1893,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.0.227"
version = "0.0.229"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1997,7 +1930,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.227"
version = "0.0.229"
dependencies = [
"anyhow",
"clap 4.0.32",
@@ -2006,9 +1939,9 @@ dependencies = [
"once_cell",
"ruff",
"ruff_cli",
"rustpython-ast",
"rustpython-common",
"rustpython-parser",
"rustpython-ast 0.2.0 (git+https://github.com/RustPython/RustPython.git?rev=ff90fe52eea578c8ebdd9d95e078cc041a5959fa)",
"rustpython-common 0.2.0 (git+https://github.com/RustPython/RustPython.git?rev=ff90fe52eea578c8ebdd9d95e078cc041a5959fa)",
"rustpython-parser 0.2.0 (git+https://github.com/RustPython/RustPython.git?rev=ff90fe52eea578c8ebdd9d95e078cc041a5959fa)",
"schemars",
"serde_json",
"strum",
@@ -2018,7 +1951,7 @@ dependencies = [
[[package]]
name = "ruff_macros"
version = "0.0.227"
version = "0.0.229"
dependencies = [
"once_cell",
"proc-macro2",
@@ -2027,6 +1960,16 @@ dependencies = [
"textwrap",
]
[[package]]
name = "rust-stemmers"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54"
dependencies = [
"serde",
"serde_derive",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
@@ -2059,24 +2002,34 @@ dependencies = [
"webpki",
]
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=62aa942bf506ea3d41ed0503b947b84141fdaa3c#62aa942bf506ea3d41ed0503b947b84141fdaa3c"
dependencies = [
"num-bigint",
"rustpython-common 0.2.0 (git+https://github.com/RustPython/RustPython.git?rev=62aa942bf506ea3d41ed0503b947b84141fdaa3c)",
"rustpython-compiler-core 0.2.0 (git+https://github.com/RustPython/RustPython.git?rev=62aa942bf506ea3d41ed0503b947b84141fdaa3c)",
]
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=ff90fe52eea578c8ebdd9d95e078cc041a5959fa#ff90fe52eea578c8ebdd9d95e078cc041a5959fa"
dependencies = [
"num-bigint",
"rustpython-common",
"rustpython-compiler-core",
"rustpython-common 0.2.0 (git+https://github.com/RustPython/RustPython.git?rev=ff90fe52eea578c8ebdd9d95e078cc041a5959fa)",
"rustpython-compiler-core 0.2.0 (git+https://github.com/RustPython/RustPython.git?rev=ff90fe52eea578c8ebdd9d95e078cc041a5959fa)",
]
[[package]]
name = "rustpython-common"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=ff90fe52eea578c8ebdd9d95e078cc041a5959fa#ff90fe52eea578c8ebdd9d95e078cc041a5959fa"
source = "git+https://github.com/RustPython/RustPython.git?rev=62aa942bf506ea3d41ed0503b947b84141fdaa3c#62aa942bf506ea3d41ed0503b947b84141fdaa3c"
dependencies = [
"ascii",
"bitflags",
"cfg-if 1.0.0",
"cfg-if",
"hexf-parse",
"itertools",
"lexical-parse-float",
@@ -2087,13 +2040,55 @@ dependencies = [
"num-traits",
"once_cell",
"radium",
"rand 0.8.5",
"rand",
"siphasher",
"unic-ucd-category",
"volatile",
"widestring",
]
[[package]]
name = "rustpython-common"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=ff90fe52eea578c8ebdd9d95e078cc041a5959fa#ff90fe52eea578c8ebdd9d95e078cc041a5959fa"
dependencies = [
"ascii",
"bitflags",
"cfg-if",
"hexf-parse",
"itertools",
"lexical-parse-float",
"libc",
"lock_api",
"num-bigint",
"num-complex",
"num-traits",
"once_cell",
"radium",
"rand",
"siphasher",
"unic-ucd-category",
"volatile",
"widestring",
]
[[package]]
name = "rustpython-compiler-core"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=62aa942bf506ea3d41ed0503b947b84141fdaa3c#62aa942bf506ea3d41ed0503b947b84141fdaa3c"
dependencies = [
"bincode",
"bitflags",
"bstr 0.2.17",
"itertools",
"lz4_flex",
"num-bigint",
"num-complex",
"num_enum",
"serde",
"thiserror",
]
[[package]]
name = "rustpython-compiler-core"
version = "0.2.0"
@@ -2111,6 +2106,31 @@ dependencies = [
"thiserror",
]
[[package]]
name = "rustpython-parser"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=62aa942bf506ea3d41ed0503b947b84141fdaa3c#62aa942bf506ea3d41ed0503b947b84141fdaa3c"
dependencies = [
"ahash",
"anyhow",
"itertools",
"lalrpop",
"lalrpop-util",
"log",
"num-bigint",
"num-traits",
"phf 0.10.1",
"phf_codegen 0.10.0",
"rustc-hash",
"rustpython-ast 0.2.0 (git+https://github.com/RustPython/RustPython.git?rev=62aa942bf506ea3d41ed0503b947b84141fdaa3c)",
"rustpython-compiler-core 0.2.0 (git+https://github.com/RustPython/RustPython.git?rev=62aa942bf506ea3d41ed0503b947b84141fdaa3c)",
"thiserror",
"tiny-keccak",
"unic-emoji-char",
"unic-ucd-ident",
"unicode_names2",
]
[[package]]
name = "rustpython-parser"
version = "0.2.0"
@@ -2127,8 +2147,8 @@ dependencies = [
"phf 0.10.1",
"phf_codegen 0.10.0",
"rustc-hash",
"rustpython-ast",
"rustpython-compiler-core",
"rustpython-ast 0.2.0 (git+https://github.com/RustPython/RustPython.git?rev=ff90fe52eea578c8ebdd9d95e078cc041a5959fa)",
"rustpython-compiler-core 0.2.0 (git+https://github.com/RustPython/RustPython.git?rev=ff90fe52eea578c8ebdd9d95e078cc041a5959fa)",
"thiserror",
"tiny-keccak",
"unic-emoji-char",
@@ -2274,7 +2294,7 @@ version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd1c7ddea665294d484c39fd0c0d2b7e35bbfe10035c5fe1854741a57f6880e1"
dependencies = [
"dirs 4.0.0",
"dirs",
]
[[package]]
@@ -2377,7 +2397,7 @@ version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"fastrand",
"libc",
"redox_syscall",
@@ -2417,15 +2437,15 @@ dependencies = [
[[package]]
name = "terminfo"
version = "0.7.3"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76971977e6121664ec1b960d1313aacfa75642adc93b9d4d53b247bd4cb1747e"
checksum = "da31aef70da0f6352dbcb462683eb4dd2bfad01cf3fc96cf204547b9a839a585"
dependencies = [
"dirs 2.0.2",
"dirs",
"fnv",
"nom",
"phf 0.8.0",
"phf_codegen 0.8.0",
"phf 0.11.1",
"phf_codegen 0.11.1",
]
[[package]]
@@ -2449,7 +2469,7 @@ version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e45b7bf6e19353ddd832745c8fcf77a17a93171df7151187f26623f2b75b5b26"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"proc-macro-error",
"proc-macro2",
"quote",
@@ -2589,7 +2609,7 @@ version = "1.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"static_assertions",
]
@@ -2793,12 +2813,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
@@ -2817,7 +2831,7 @@ version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"wasm-bindgen-macro",
]
@@ -2842,7 +2856,7 @@ version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",

View File

@@ -8,7 +8,7 @@ default-members = [".", "ruff_cli"]
[package]
name = "ruff"
version = "0.0.227"
version = "0.0.229"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"
@@ -35,6 +35,7 @@ fern = { version = "0.6.1" }
glob = { version = "0.3.0" }
globset = { version = "0.4.9" }
ignore = { version = "0.4.18" }
imperative = { version = "1.0.3" }
itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
log = { version = "0.4.17" }
@@ -46,11 +47,11 @@ once_cell = { version = "1.16.0" }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
regex = { version = "1.6.0" }
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
ruff_macros = { version = "0.0.227", path = "ruff_macros" }
ruff_macros = { version = "0.0.229", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "ff90fe52eea578c8ebdd9d95e078cc041a5959fa" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "ff90fe52eea578c8ebdd9d95e078cc041a5959fa" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "ff90fe52eea578c8ebdd9d95e078cc041a5959fa" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "62aa942bf506ea3d41ed0503b947b84141fdaa3c" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "62aa942bf506ea3d41ed0503b947b84141fdaa3c" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "62aa942bf506ea3d41ed0503b947b84141fdaa3c" }
schemars = { version = "0.8.11" }
semver = { version = "1.0.16" }
serde = { version = "1.0.147", features = ["derive"] }
@@ -59,9 +60,9 @@ smallvec = { version = "1.10.0" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
textwrap = { version = "0.16.0" }
thiserror = { version = "1.0" }
titlecase = { version = "2.2.1" }
toml_edit = { version = "0.17.1", features = ["easy"] }
thiserror = { version = "1.0" }
# https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support
# For (future) wasm-pack support

View File

@@ -138,15 +138,17 @@ developer of [Zulip](https://github.com/zulip/zulip):
1. [flake8-pie (PIE)](#flake8-pie-pie)
1. [flake8-commas (COM)](#flake8-commas-com)
1. [flake8-no-pep420 (INP)](#flake8-no-pep420-inp)
1. [flake8-executable (EXE)](#flake8-executable-exe)
1. [flake8-type-checking (TYP)](#flake8-type-checking-typ)
1. [tryceratops (TRY)](#tryceratops-try)
1. [Ruff-specific rules (RUF)](#ruff-specific-rules-ruf)<!-- End auto-generated table of contents. -->
1. [Editor Integrations](#editor-integrations)
1. [FAQ](#faq)
1. [Development](#development)
1. [Contributing](#contributing)
1. [Releases](#releases)
1. [Benchmarks](#benchmarks)
1. [Reference](#reference)
1. [License](#license)
1. [Contributing](#contributing)
## Installation and Usage
@@ -199,11 +201,9 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.227'
rev: 'v0.0.229'
hooks:
- id: ruff
# Respect `exclude` and `extend-exclude` settings.
args: ["--force-exclude"]
```
## Configuration
@@ -599,6 +599,7 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI
#### Error (E)
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| E101 | mixed-spaces-and-tabs | Indentation contains mixed spaces and tabs | |
| E401 | multiple-imports-on-one-line | Multiple imports on one line | |
| E402 | module-import-not-at-top-of-file | Module level import not at top of file | |
| E501 | line-too-long | Line too long ({length} > {limit} characters) | |
@@ -672,6 +673,7 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI.
| D300 | uses-triple-quotes | Use """triple double quotes""" | |
| D301 | uses-r-prefix-for-backslashed-content | Use r""" if any backslashes in a docstring | |
| D400 | ends-in-period | First line should end with a period | 🛠 |
| D401 | non-imperative-mood | First line of docstring should be in imperative mood: "{first_line}" | |
| D402 | no-signature | First line should not be the function's signature | |
| D403 | first-line-capitalized | First word of the first line should be properly capitalized | |
| D404 | no-this-prefix | First word of the docstring should not be "This" | |
@@ -726,8 +728,10 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
| UP028 | rewrite-yield-from | Replace `yield` over `for` loop with `yield from` | 🛠 |
| UP029 | unnecessary-builtin-import | Unnecessary builtin import: `{import}` | 🛠 |
| UP030 | format-literals | Use implicit references for positional format fields | 🛠 |
| UP031 | printf-string-formatting | Use format specifiers instead of percent format | 🛠 |
| UP032 | f-string | Use f-string instead of `format` call | 🛠 |
| UP033 | functools-cache | Use `@functools.cache` instead of `@functools.lru_cache(maxsize=None)` | 🛠 |
| UP034 | extraneous-parentheses | Avoid extraneous parentheses | 🛠 |
### pep8-naming (N)
@@ -1062,7 +1066,7 @@ For more, see [flake8-datetimez](https://pypi.org/project/flake8-datetimez/20.10
| DTZ004 | call-datetime-utcfromtimestamp | The use of `datetime.datetime.utcfromtimestamp()` is not allowed | |
| DTZ005 | call-datetime-now-without-tzinfo | The use of `datetime.datetime.now()` without `tz` argument is not allowed | |
| DTZ006 | call-datetime-fromtimestamp | The use of `datetime.datetime.fromtimestamp()` without `tz` argument is not allowed | |
| DTZ007 | call-datetime-strptime-without-zone | The use of `datetime.datetime.strptime()` without %z must be followed by `.replace(tzinfo=)` | |
| DTZ007 | call-datetime-strptime-without-zone | The use of `datetime.datetime.strptime()` without %z must be followed by `.replace(tzinfo=)` or `.astimezone()` | |
| DTZ011 | call-date-today | The use of `datetime.date.today()` is not allowed. | |
| DTZ012 | call-date-fromtimestamp | The use of `datetime.date.fromtimestamp()` is not allowed | |
@@ -1166,6 +1170,32 @@ For more, see [flake8-no-pep420](https://pypi.org/project/flake8-no-pep420/2.3.0
| ---- | ---- | ------- | --- |
| INP001 | implicit-namespace-package | File `{filename}` is part of an implicit namespace package. Add an `__init__.py`. | |
### flake8-executable (EXE)
For more, see [flake8-executable](https://pypi.org/project/flake8-executable/2.1.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| EXE003 | shebang-python | Shebang should contain "python" | |
| EXE004 | shebang-whitespace | Avoid whitespace before shebang | 🛠 |
| EXE005 | shebang-newline | Shebang should be at the beginning of the file | |
### flake8-type-checking (TYP)
For more, see [flake8-type-checking](https://pypi.org/project/flake8-type-checking/2.3.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| TYP005 | empty-type-checking-block | Found empty type-checking block | |
### tryceratops (TRY)
For more, see [tryceratops](https://pypi.org/project/tryceratops/1.1.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| TRY300 | try-consider-else | Consider `else` block | |
### Ruff-specific rules (RUF)
| Code | Name | Message | Fix |
@@ -1458,11 +1488,13 @@ natively, including:
- [`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-no-pep420`](https://pypi.org/project/flake8-no-pep420)
- [`flake8-pie`](https://pypi.org/project/flake8-pie/) ([#1543](https://github.com/charliermarsh/ruff/issues/1543))
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-pytest-style`](https://pypi.org/project/flake8-pytest-style/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-return`](https://pypi.org/project/flake8-return/)
- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) ([#998](https://github.com/charliermarsh/ruff/issues/998))
@@ -1526,11 +1558,13 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`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-no-pep420`](https://pypi.org/project/flake8-no-pep420)
- [`flake8-pie`](https://pypi.org/project/flake8-pie/) ([#1543](https://github.com/charliermarsh/ruff/issues/1543))
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-pytest-style`](https://pypi.org/project/flake8-pytest-style/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-return`](https://pypi.org/project/flake8-return/)
- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) ([#998](https://github.com/charliermarsh/ruff/issues/998))
@@ -1639,24 +1673,10 @@ matter how they're provided, which avoids accidental incompatibilities and simpl
Run `ruff /path/to/code.py --show-settings` to view the resolved settings for a given file.
## Development
## Contributing
Ruff is written in Rust (1.65.0). You'll need to install the [Rust toolchain](https://www.rust-lang.org/tools/install)
for development.
Assuming you have `cargo` installed, you can run:
```shell
cargo run resources/test/fixtures
```
For development, we use [nightly Rust](https://rust-lang.github.io/rustup/concepts/channels.html#working-with-nightly-rust):
```shell
cargo +nightly fmt
cargo +nightly clippy --fix --workspace --all-targets --all-features -- -W clippy::pedantic
cargo +nightly test --all
```
Contributions are welcome and hugely appreciated. To get started, check out the
[contributing guidelines](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTING.md).
## Releases
@@ -1979,7 +1999,7 @@ recommended to only use `extend-ignore` when extending a
**Default value**: `[]`
**Type**: `Vec<RuleCodePrefix>`
**Type**: `Vec<RuleSelector>`
**Example usage**:
@@ -2004,7 +2024,7 @@ recommended to only use `extend-select` when extending a
**Default value**: `[]`
**Type**: `Vec<RuleCodePrefix>`
**Type**: `Vec<RuleSelector>`
**Example usage**:
@@ -2079,7 +2099,7 @@ A list of rule codes or prefixes to consider autofixable.
**Default value**: `["A", "ANN", "ARG", "B", "BLE", "C", "D", "E", "ERA", "F", "FBT", "I", "ICN", "N", "PGH", "PLC", "PLE", "PLR", "PLW", "Q", "RET", "RUF", "S", "T", "TID", "UP", "W", "YTT"]`
**Type**: `Vec<RuleCodePrefix>`
**Type**: `Vec<RuleSelector>`
**Example usage**:
@@ -2151,7 +2171,7 @@ specific prefixes.
**Default value**: `[]`
**Type**: `Vec<RuleCodePrefix>`
**Type**: `Vec<RuleSelector>`
**Example usage**:
@@ -2229,7 +2249,7 @@ exclude, when considering any matching files.
**Default value**: `{}`
**Type**: `HashMap<String, Vec<RuleCodePrefix>>`
**Type**: `HashMap<String, Vec<RuleSelector>>`
**Example usage**:
@@ -2293,7 +2313,7 @@ specific prefixes.
**Default value**: `["E", "F"]`
**Type**: `Vec<RuleCodePrefix>`
**Type**: `Vec<RuleSelector>`
**Example usage**:
@@ -2437,7 +2457,7 @@ A list of rule codes or prefixes to consider non-autofix-able.
**Default value**: `[]`
**Type**: `Vec<RuleCodePrefix>`
**Type**: `Vec<RuleSelector>`
**Example usage**:
@@ -3456,8 +3476,3 @@ keep-runtime-typing = true
## License
MIT
## Contributing
Contributions are welcome and hugely appreciated. To get started, check out the
[contributing guidelines](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTING.md).

View File

@@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
fn main() {
let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
generate_origin_name_and_url(&out_dir);
generate_linter_name_and_url(&out_dir);
}
const RULES_SUBMODULE_DOC_PREFIX: &str = "//! Rules from ";
@@ -15,13 +15,13 @@ const RULES_SUBMODULE_DOC_PREFIX: &str = "//! Rules from ";
/// //! Rules from [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/).
///
/// This function extracts the link label and url from these comments and
/// generates the `name` and `url` functions for the `RuleOrigin` enum
/// generates the `name` and `url` functions for the `Linter` enum
/// accordingly, so that they can be used by `ruff_dev::generate_rules_table`.
fn generate_origin_name_and_url(out_dir: &Path) {
fn generate_linter_name_and_url(out_dir: &Path) {
println!("cargo:rerun-if-changed=src/rules/");
let mut name_match_arms: String = r#"RuleOrigin::Ruff => "Ruff-specific rules","#.into();
let mut url_match_arms: String = r#"RuleOrigin::Ruff => None,"#.into();
let mut name_match_arms: String = r#"Linter::Ruff => "Ruff-specific rules","#.into();
let mut url_match_arms: String = r#"Linter::Ruff => None,"#.into();
for file in fs::read_dir("src/rules/")
.unwrap()
@@ -62,14 +62,14 @@ fn generate_origin_name_and_url(out_dir: &Path) {
})
.collect::<String>();
name_match_arms.push_str(&format!(r#"RuleOrigin::{variant_name} => "{name}","#));
url_match_arms.push_str(&format!(r#"RuleOrigin::{variant_name} => Some("{url}"),"#));
name_match_arms.push_str(&format!(r#"Linter::{variant_name} => "{name}","#));
url_match_arms.push_str(&format!(r#"Linter::{variant_name} => Some("{url}"),"#));
}
write!(
BufWriter::new(fs::File::create(out_dir.join("origin.rs")).unwrap()),
BufWriter::new(fs::File::create(out_dir.join("linter.rs")).unwrap()),
"
impl RuleOrigin {{
impl Linter {{
pub fn name(&self) -> &'static str {{
match self {{ {name_match_arms} }}
}}

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.227"
version = "0.0.229"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.227"
version = "0.0.229"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.227"
version = "0.0.229"
edition = "2021"
[dependencies]

View File

@@ -7,7 +7,7 @@ build-backend = "maturin"
[project]
name = "ruff"
version = "0.0.227"
version = "0.0.229"
description = "An extremely fast Python linter, written in Rust."
authors = [
{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" },

View File

@@ -1,5 +1,6 @@
import some as sum
from some import other as int
from directory import new as dir
print = 1
copyright: 'annotation' = 2
@@ -7,6 +8,8 @@ copyright: 'annotation' = 2
float = object = 4
min, max = 5, 6
id = 4
def bytes():
pass

View File

@@ -5,5 +5,7 @@ def func1(str, /, type, *complex, Exception, **getattr):
async def func2(bytes):
pass
async def func3(id, dir):
pass
map([], lambda float: ...)

View File

@@ -1,8 +1,12 @@
class MyClass:
ImportError = 4
id = 5
dir = "/"
def __init__(self):
self.float = 5 # is fine
self.id = 10
self.dir = "."
def str(self):
pass

View File

@@ -383,7 +383,7 @@ s = (
s2 = (
'this'
'is a also a string'
'is also a string'
)
t = (

View File

@@ -0,0 +1,2 @@
#!/usr/bin/bash
print("hello world")

View File

@@ -0,0 +1 @@
#!/usr/bin/python

View File

@@ -0,0 +1,2 @@
print(" #!/usr/bin/python")
# shebang outside of comments should be ignored

View File

@@ -0,0 +1,2 @@
pass #!/usr/bin/env python

View File

@@ -0,0 +1,3 @@
# A python comment
#!/usr/bin/python

View File

@@ -0,0 +1,4 @@
print("code prior to shebang")
# A python comment
#!/usr/bin/python

View File

@@ -0,0 +1,6 @@
"""
With a docstring
print("commented out code")
"""
# A python comment
#!/usr/bin/python

View File

@@ -3,6 +3,12 @@ if a:
if b:
c
# SIM102
if a:
if b:
if c:
d
# SIM102
if a:
pass

View File

@@ -42,3 +42,11 @@ def f():
return "foo"
else:
return False
def f():
# SIM103 (but not fixable)
if a:
return False
else:
return True

View File

@@ -0,0 +1,25 @@
from typing import TYPE_CHECKING, List
if TYPE_CHECKING:
pass # TYP005
def example():
if TYPE_CHECKING:
pass # TYP005
return
class Test:
if TYPE_CHECKING:
pass # TYP005
x = 2
if TYPE_CHECKING:
if 2:
pass
if TYPE_CHECKING:
x: List

View File

@@ -0,0 +1,19 @@
def func_all_spaces():
# No error
print("spaces")
def func_tabs():
# No error
print("tabs")
def func_mixed_start_with_tab():
# E101
print("mixed starts with tab")
def func_mixed_start_with_space():
# E101
print("mixed starts with space")
def xyz():
# E101
print("xyz");

View File

@@ -606,3 +606,31 @@ def one_liner():
r"""Wrong.
"""
@expect('D200: One-line docstring should fit on one line with quotes '
'(found 3)')
@expect('D212: Multi-line docstring summary should start at the first line')
def one_liner():
"""Wrong."
"""
@expect('D200: One-line docstring should fit on one line with quotes '
'(found 3)')
@expect('D212: Multi-line docstring summary should start at the first line')
def one_liner():
"""
"Wrong."""
@expect('D404: First word of the docstring should not be "This"')
def starts_with_this():
"""This is a docstring."""
@expect('D404: First word of the docstring should not be "This"')
def starts_with_space_then_this():
""" This is a docstring that starts with a space.""" # noqa: D210

View File

@@ -0,0 +1,43 @@
# Bad examples
def bad_liouiwnlkjl():
"""Returns foo."""
def bad_sdgfsdg23245():
"""Constructor for a foo."""
def bad_sdgfsdg23245777():
"""
Constructor for a boa.
"""
def bad_run_something():
"""Runs something"""
pass
def multi_line():
"""Writes a logical line that
extends to two physical lines.
"""
# Good examples
def good_run_something():
"""Run away."""
def good_construct():
"""Construct a beautiful house."""
def good_multi_line():
"""Write a logical line that
extends to two physical lines.
"""

View File

@@ -1,51 +1,51 @@
if True:
import foo; x = 1
import foo; x = 1
import foo1; x = 1
import foo2; x = 1
if True:
import foo; \
import foo3; \
x = 1
if True:
import foo \
import foo4 \
; x = 1
if True:
x = 1; import foo
x = 1; import foo5
if True:
x = 1; \
import foo
import foo6
if True:
x = 1 \
; import foo
; import foo7
if True:
x = 1; import foo; x = 1
x = 1; import foo; x = 1
x = 1; import foo8; x = 1
x = 1; import foo9; x = 1
if True:
x = 1; \
import foo; \
import foo10; \
x = 1
if True:
x = 1 \
;import foo \
;import foo11 \
;x = 1
# Continuation, but not as the last content in the file.
x = 1; \
import foo
import foo12
# Continuation, followed by end-of-file. (Removing `import foo` would cause a syntax
# error.)
x = 1; \
import foo
import foo13

View File

@@ -12,6 +12,9 @@ per-file-ignores = { "__init__.py" = ["F401"] }
[tool.ruff.flake8-bugbear]
extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"]
[tool.ruff.flake8-builtins]
builtins-ignorelist = ["id", "dir"]
[tool.ruff.flake8-quotes]
inline-quotes = "single"
multiline-quotes = "double"

View File

@@ -0,0 +1,69 @@
a, b, x, y = 1, 2, 3, 4
# UP031
print('%s %s' % (a, b))
print('%s%s' % (a, b))
print("trivial" % ())
print("%s" % ("simple",))
print("%s" % ("%s" % ("nested",),))
print("%s%% percent" % (15,))
print("%f" % (15,))
print("%.f" % (15,))
print("%.3f" % (15,))
print("%3f" % (15,))
print("%-5f" % (5,))
print("%9f" % (5,))
print("%#o" % (123,))
print("brace {} %s" % (1,))
print(
"%s" % (
"trailing comma",
)
)
print("foo %s " % (x,))
print("%(k)s" % {"k": "v"})
print("%(k)s" % {
"k": "v",
"i": "j"
})
print("%(to_list)s" % {"to_list": []})
print("%(k)s" % {"k": "v", "i": 1, "j": []})
print("%(ab)s" % {"a" "b": 1})
print("%(a)s" % {"a" : 1})
print((
"foo %s "
"bar %s" % (x, y)
))
print(
"foo %(foo)s "
"bar %(bar)s" % {"foo": x, "bar": y}
)
print("%s \N{snowman}" % (a,))
print("%(foo)s \N{snowman}" % {"foo": 1})
print(("foo %s " "bar %s") % (x, y))

View File

@@ -0,0 +1,59 @@
# OK
"%s" % unknown_type
b"%s" % (b"bytestring",)
"%*s" % (5, "hi")
"%d" % (flt,)
"%c" % (some_string,)
"%4%" % ()
"%.2r" % (1.25)
i % 3
"%.*s" % (5, "hi")
"%i" % (flt,)
"%()s" % {"": "empty"}
"%s" % {"k": "v"}
"%(1)s" % {"1": "bar"}
"%(a)s" % {"a": 1, "a": 2}
pytest.param('"%8s" % (None,)', id="unsafe width-string conversion"),
"%()s" % {"": "bar"}
"%(1)s" % {1: 2, "1": 2}
"%(and)s" % {"and": 2}
# OK (arguably false negatives)
(
"foo %s "
"bar %s"
) % (x, y)
(
"foo %(foo)s "
"bar %(bar)s"
) % {"foo": x, "bar": y}
(
"""foo %s"""
% (x,)
)
(
"""
foo %s
"""
% (x,)
)

View File

@@ -0,0 +1,61 @@
# UP034
print(("foo"))
# UP034
print(("hell((goodybe))o"))
# UP034
print((("foo")))
# UP034
print((((1))))
# UP034
print(("foo{}".format(1)))
# UP034
print(
("foo{}".format(1))
)
# UP034
print(
(
"foo"
)
)
# UP034
def f():
x = int(((yield 1)))
# UP034
if True:
print(
("foo{}".format(1))
)
# UP034
print((x for x in range(3)))
# OK
print("foo")
# OK
print((1, 2, 3))
# OK
print(())
# OK
print((1,))
# OK
sum((block.code for block in blocks), [])
# OK
def f():
x = int((yield 1))
# OK
sum((i for i in range(3)), [])

View File

@@ -20,3 +20,20 @@ astonishment = ("we all feel", ) + Fun.words
chain = ['a', 'b', 'c'] + eggs + list(('yes', 'no', 'pants') + zoob)
baz = () + zoob
first = [
# The order
1, # here
2, # is
# extremely
3, # critical
# to preserve
]
second = first + [
# please
4,
# don't
5,
# touch
6,
]

View File

@@ -1,64 +1,89 @@
# This should ignore both errors.
from typing import ( # noqa: F401
List,
Sequence,
)
def f():
# This should ignore both errors.
from typing import ( # noqa: F401
List,
Sequence,
)
# This should ignore both errors.
from typing import ( # noqa
List,
Sequence,
)
# This should ignore both errors.
from typing import (
List, # noqa: F401
Sequence, # noqa: F401
)
def f():
# This should ignore both errors.
from typing import ( # noqa
List,
Sequence,
)
# This should ignore both errors.
from typing import (
List, # noqa
Sequence, # noqa
)
# This should ignore the first error.
from typing import (
Mapping, # noqa: F401
Union,
)
def f():
# This should ignore both errors.
from typing import (
List, # noqa: F401
Sequence, # noqa: F401
)
# This should ignore both errors.
from typing import ( # noqa
List,
Sequence,
)
# This should ignore the error, but the inner noqa should be marked as unused.
from typing import ( # noqa: F401
Optional, # noqa: F401
)
def f():
# This should ignore both errors.
from typing import (
List, # noqa
Sequence, # noqa
)
# This should ignore the error, but the inner noqa should be marked as unused.
from typing import ( # noqa
Optional, # noqa: F401
)
# This should ignore the error, but mark F501 as unused.
from typing import ( # noqa: F401
Dict, # noqa: F501
)
def f():
# This should ignore the first error.
from typing import (
Mapping, # noqa: F401
Union,
)
# This should ignore the error, but mark F501 as unused.
from typing import ( # noqa: F501
Tuple, # noqa: F401
)
# This should ignore both errors.
from typing import Any, AnyStr # noqa: F401
def f():
# This should ignore both errors.
from typing import ( # noqa
List,
Sequence,
)
# This should ignore both errors.
from typing import AsyncIterable, AsyncGenerator # noqa
# This should mark F501 as unused.
from typing import Awaitable, AwaitableGenerator # noqa: F501
def f():
# This should ignore the error, but the inner noqa should be marked as unused.
from typing import ( # noqa: F401
Optional, # noqa: F401
)
def f():
# This should ignore the error, but the inner noqa should be marked as unused.
from typing import ( # noqa
Optional, # noqa: F401
)
def f():
# This should ignore the error, but mark F501 as unused.
from typing import ( # noqa: F401
Dict, # noqa: F501
)
def f():
# This should ignore the error, but mark F501 as unused.
from typing import ( # noqa: F501
Tuple, # noqa: F401
)
def f():
# This should ignore both errors.
from typing import Any, AnyStr # noqa: F401
def f():
# This should ignore both errors.
from typing import AsyncIterable, AsyncGenerator # noqa
def f():
# This should mark F501 as unused.
from typing import Awaitable, AwaitableGenerator # noqa: F501

View File

@@ -0,0 +1,47 @@
"""
Violation:
Returning a final value inside a try block may indicate you could use an else block
instead to outline the success scenario
"""
import logging
logger = logging.getLogger(__name__)
class MyException(Exception):
pass
def bad():
try:
a = 1
b = process()
return b
except MyException:
logger.exception("process failed")
def good():
try:
a = 1
b = process()
except MyException:
logger.exception("process failed")
else:
return b
def noreturn():
try:
a = 1
b = process()
except MyException:
logger.exception("process failed")
def still_good():
try:
return process()
except MyException:
logger.exception("process failed")

View File

@@ -73,7 +73,7 @@
"null"
],
"items": {
"$ref": "#/definitions/RuleCodePrefix"
"$ref": "#/definitions/RuleSelector"
}
},
"extend-select": {
@@ -83,7 +83,7 @@
"null"
],
"items": {
"$ref": "#/definitions/RuleCodePrefix"
"$ref": "#/definitions/RuleSelector"
}
},
"external": {
@@ -117,7 +117,7 @@
"null"
],
"items": {
"$ref": "#/definitions/RuleCodePrefix"
"$ref": "#/definitions/RuleSelector"
}
},
"flake8-annotations": {
@@ -153,6 +153,17 @@
}
]
},
"flake8-builtins": {
"description": "Options for the `flake8-builtins` plugin.",
"anyOf": [
{
"$ref": "#/definitions/Flake8BuiltinsOptions"
},
{
"type": "null"
}
]
},
"flake8-errmsg": {
"description": "Options for the `flake8-errmsg` plugin.",
"anyOf": [
@@ -244,7 +255,7 @@
"null"
],
"items": {
"$ref": "#/definitions/RuleCodePrefix"
"$ref": "#/definitions/RuleSelector"
}
},
"ignore-init-module-imports": {
@@ -315,7 +326,7 @@
"additionalProperties": {
"type": "array",
"items": {
"$ref": "#/definitions/RuleCodePrefix"
"$ref": "#/definitions/RuleSelector"
}
}
},
@@ -388,7 +399,7 @@
"null"
],
"items": {
"$ref": "#/definitions/RuleCodePrefix"
"$ref": "#/definitions/RuleSelector"
}
},
"show-source": {
@@ -446,7 +457,7 @@
"null"
],
"items": {
"$ref": "#/definitions/RuleCodePrefix"
"$ref": "#/definitions/RuleSelector"
}
},
"update-check": {
@@ -584,6 +595,22 @@
},
"additionalProperties": false
},
"Flake8BuiltinsOptions": {
"type": "object",
"properties": {
"builtins-ignorelist": {
"description": "Ignore list of builtins.",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
}
},
"additionalProperties": false
},
"Flake8ErrMsgOptions": {
"type": "object",
"properties": {
@@ -1134,7 +1161,7 @@
}
]
},
"RuleCodePrefix": {
"RuleSelector": {
"type": "string",
"enum": [
"A",
@@ -1277,6 +1304,7 @@
"D4",
"D40",
"D400",
"D401",
"D402",
"D403",
"D404",
@@ -1310,6 +1338,9 @@
"DTZ011",
"DTZ012",
"E",
"E1",
"E10",
"E101",
"E4",
"E40",
"E401",
@@ -1347,6 +1378,12 @@
"ERA0",
"ERA00",
"ERA001",
"EXE",
"EXE0",
"EXE00",
"EXE003",
"EXE004",
"EXE005",
"F",
"F4",
"F40",
@@ -1710,6 +1747,14 @@
"TID25",
"TID251",
"TID252",
"TRY",
"TRY3",
"TRY30",
"TRY300",
"TYP",
"TYP0",
"TYP00",
"TYP005",
"U",
"U0",
"U00",
@@ -1766,8 +1811,10 @@
"UP029",
"UP03",
"UP030",
"UP031",
"UP032",
"UP033",
"UP034",
"W",
"W2",
"W29",

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_cli"
version = "0.0.227"
version = "0.0.229"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"

View File

@@ -4,7 +4,7 @@ use clap::{command, Parser};
use regex::Regex;
use ruff::fs;
use ruff::logging::LogLevel;
use ruff::registry::{Rule, RuleCodePrefix};
use ruff::registry::{Rule, RuleSelector};
use ruff::resolver::ConfigProcessor;
use ruff::settings::types::{
FilePattern, PatternPrefixPair, PerFileIgnore, PythonVersion, SerializationFormat,
@@ -66,18 +66,18 @@ pub struct Cli {
/// Comma-separated list of rule codes to enable (or ALL, to enable all
/// rules).
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
pub select: Option<Vec<RuleCodePrefix>>,
pub select: Option<Vec<RuleSelector>>,
/// Like --select, but adds additional rule codes on top of the selected
/// ones.
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
pub extend_select: Option<Vec<RuleCodePrefix>>,
pub extend_select: Option<Vec<RuleSelector>>,
/// Comma-separated list of rule codes to disable.
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
pub ignore: Option<Vec<RuleCodePrefix>>,
pub ignore: Option<Vec<RuleSelector>>,
/// Like --ignore, but adds additional rule codes on top of the ignored
/// ones.
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
pub extend_ignore: Option<Vec<RuleCodePrefix>>,
pub extend_ignore: Option<Vec<RuleSelector>>,
/// List of paths, used to omit files and/or directories from analysis.
#[arg(long, value_delimiter = ',', value_name = "FILE_PATTERN")]
pub exclude: Option<Vec<FilePattern>>,
@@ -88,11 +88,11 @@ pub struct Cli {
/// List of rule codes to treat as eligible for autofix. Only applicable
/// when autofix itself is enabled (e.g., via `--fix`).
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
pub fixable: Option<Vec<RuleCodePrefix>>,
pub fixable: Option<Vec<RuleSelector>>,
/// List of rule codes to treat as ineligible for autofix. Only applicable
/// when autofix itself is enabled (e.g., via `--fix`).
#[arg(long, value_delimiter = ',', value_name = "RULE_CODE")]
pub unfixable: Option<Vec<RuleCodePrefix>>,
pub unfixable: Option<Vec<RuleSelector>>,
/// List of mappings from file pattern to code to exclude
#[arg(long, value_delimiter = ',')]
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
@@ -324,17 +324,17 @@ pub struct Overrides {
pub dummy_variable_rgx: Option<Regex>,
pub exclude: Option<Vec<FilePattern>>,
pub extend_exclude: Option<Vec<FilePattern>>,
pub extend_ignore: Option<Vec<RuleCodePrefix>>,
pub extend_select: Option<Vec<RuleCodePrefix>>,
pub fixable: Option<Vec<RuleCodePrefix>>,
pub ignore: Option<Vec<RuleCodePrefix>>,
pub extend_ignore: Option<Vec<RuleSelector>>,
pub extend_select: Option<Vec<RuleSelector>>,
pub fixable: Option<Vec<RuleSelector>>,
pub ignore: Option<Vec<RuleSelector>>,
pub line_length: Option<usize>,
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
pub respect_gitignore: Option<bool>,
pub select: Option<Vec<RuleCodePrefix>>,
pub select: Option<Vec<RuleSelector>>,
pub show_source: Option<bool>,
pub target_version: Option<PythonVersion>,
pub unfixable: Option<Vec<RuleCodePrefix>>,
pub unfixable: Option<Vec<RuleSelector>>,
// TODO(charlie): Captured in pyproject.toml as a default, but not part of `Settings`.
pub cache_dir: Option<PathBuf>,
pub fix: Option<bool>,
@@ -435,7 +435,7 @@ pub fn extract_log_level(cli: &Arguments) -> LogLevel {
/// Convert a list of `PatternPrefixPair` structs to `PerFileIgnore`.
pub fn collect_per_file_ignores(pairs: Vec<PatternPrefixPair>) -> Vec<PerFileIgnore> {
let mut per_file_ignores: FxHashMap<String, Vec<RuleCodePrefix>> = FxHashMap::default();
let mut per_file_ignores: FxHashMap<String, Vec<RuleSelector>> = FxHashMap::default();
for pair in pairs {
per_file_ignores
.entry(pair.pattern)

View File

@@ -15,7 +15,7 @@ use ruff::cache::CACHE_DIR_NAME;
use ruff::linter::add_noqa_to_path;
use ruff::logging::LogLevel;
use ruff::message::{Location, Message};
use ruff::registry::Rule;
use ruff::registry::{Linter, ParseCode, Rule};
use ruff::resolver::{FileDiscovery, PyprojectDiscovery};
use ruff::settings::flags;
use ruff::settings::types::SerializationFormat;
@@ -285,16 +285,17 @@ pub fn show_files(
#[derive(Serialize)]
struct Explanation<'a> {
code: &'a str,
origin: &'a str,
linter: &'a str,
summary: &'a str,
}
/// Explain a `Rule` to the user.
pub fn explain(rule: &Rule, format: SerializationFormat) -> Result<()> {
let (linter, _) = Linter::parse_code(rule.code()).unwrap();
match format {
SerializationFormat::Text | SerializationFormat::Grouped => {
println!("{}\n", rule.as_ref());
println!("Code: {} ({})\n", rule.code(), rule.origin().name());
println!("Code: {} ({})\n", rule.code(), linter.name());
if let Some(autofix) = rule.autofixable() {
println!(
@@ -315,7 +316,7 @@ pub fn explain(rule: &Rule, format: SerializationFormat) -> Result<()> {
"{}",
serde_json::to_string_pretty(&Explanation {
code: rule.code(),
origin: rule.origin().name(),
linter: linter.name(),
summary: rule.message_formats()[0],
})?
);

View File

@@ -13,7 +13,7 @@ use assert_cmd::Command;
use itertools::Itertools;
use log::info;
use ruff::logging::{set_up_logging, LogLevel};
use ruff::registry::RuleOrigin;
use ruff::registry::Linter;
use strum::IntoEnumIterator;
use walkdir::WalkDir;
@@ -175,12 +175,12 @@ fn test_ruff_black_compatibility() -> Result<()> {
.filter_map(Result::ok)
.collect();
let codes = RuleOrigin::iter()
let codes = Linter::iter()
// Exclude ruff codes, specifically RUF100, because it causes differences that are not a
// problem. Ruff would add a `# noqa: W292` after the first run, black introduces a
// newline, and ruff removes the `# noqa: W292` again.
.filter(|origin| *origin != RuleOrigin::Ruff)
.map(|origin| origin.prefixes().as_list(","))
.filter(|linter| *linter != Linter::Ruff)
.map(|linter| linter.prefixes().as_list(","))
.join(",");
let ruff_args = [
"-",

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.227"
version = "0.0.229"
edition = "2021"
[dependencies]

View File

@@ -2,7 +2,7 @@
use anyhow::Result;
use clap::Args;
use ruff::registry::{Prefixes, RuleCodePrefix, RuleOrigin};
use ruff::registry::{Linter, Prefixes, RuleSelector};
use strum::IntoEnumIterator;
use crate::utils::replace_readme_section;
@@ -20,7 +20,7 @@ pub struct Cli {
pub(crate) dry_run: bool,
}
fn generate_table(table_out: &mut String, prefix: &RuleCodePrefix) {
fn generate_table(table_out: &mut String, prefix: &RuleSelector) {
table_out.push_str("| Code | Name | Message | Fix |");
table_out.push('\n');
table_out.push_str("| ---- | ---- | ------- | --- |");
@@ -47,22 +47,22 @@ pub fn main(cli: &Cli) -> Result<()> {
// Generate the table string.
let mut table_out = String::new();
let mut toc_out = String::new();
for origin in RuleOrigin::iter() {
let prefixes = origin.prefixes();
for linter in Linter::iter() {
let prefixes = linter.prefixes();
let codes_csv: String = prefixes.as_list(", ");
table_out.push_str(&format!("### {} ({codes_csv})", origin.name()));
table_out.push_str(&format!("### {} ({codes_csv})", linter.name()));
table_out.push('\n');
table_out.push('\n');
toc_out.push_str(&format!(
" 1. [{} ({})](#{}-{})\n",
origin.name(),
linter.name(),
codes_csv,
origin.name().to_lowercase().replace(' ', "-"),
linter.name().to_lowercase().replace(' ', "-"),
codes_csv.to_lowercase().replace(',', "-").replace(' ', "")
));
if let Some(url) = origin.url() {
if let Some(url) = linter.url() {
let host = url
.trim_start_matches("https://")
.split('/')
@@ -70,7 +70,7 @@ pub fn main(cli: &Cli) -> Result<()> {
.unwrap();
table_out.push_str(&format!(
"For more, see [{}]({}) on {}.",
origin.name(),
linter.name(),
url,
match host {
"pypi.org" => "PyPI",
@@ -78,7 +78,7 @@ pub fn main(cli: &Cli) -> Result<()> {
host => panic!(
"unexpected host in URL of {}, expected pypi.org or github.com but found \
{host}",
origin.name()
linter.name()
),
}
));

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_macros"
version = "0.0.227"
version = "0.0.229"
edition = "2021"
[lib]

View File

@@ -10,7 +10,6 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream {
let mut diagkind_variants = quote!();
let mut rule_message_formats_match_arms = quote!();
let mut rule_autofixable_match_arms = quote!();
let mut rule_origin_match_arms = quote!();
let mut rule_code_match_arms = quote!();
let mut rule_from_code_match_arms = quote!();
let mut diagkind_code_match_arms = quote!();
@@ -29,8 +28,6 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream {
rule_message_formats_match_arms
.extend(quote! {Self::#name => <#path as Violation>::message_formats(),});
rule_autofixable_match_arms.extend(quote! {Self::#name => <#path as Violation>::AUTOFIX,});
let origin = get_origin(code);
rule_origin_match_arms.extend(quote! {Self::#name => RuleOrigin::#origin,});
rule_code_match_arms.extend(quote! {Self::#name => #code_str,});
rule_from_code_match_arms.extend(quote! {#code_str => Ok(&Rule::#name), });
diagkind_code_match_arms.extend(quote! {Self::#name(..) => &Rule::#name, });
@@ -56,7 +53,7 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream {
let rulecodeprefix = super::rule_code_prefix::expand(
&Ident::new("Rule", Span::call_site()),
&Ident::new("RuleCodePrefix", Span::call_site()),
&Ident::new("RuleSelector", Span::call_site()),
mapping.entries.iter().map(|(code, ..)| code),
|code| code_to_name[code],
);
@@ -95,10 +92,6 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream {
match self { #rule_autofixable_match_arms }
}
pub fn origin(&self) -> RuleOrigin {
match self { #rule_origin_match_arms }
}
pub fn code(&self) -> &'static str {
match self { #rule_code_match_arms }
}
@@ -140,19 +133,6 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream {
}
}
fn get_origin(ident: &Ident) -> Ident {
let ident = ident.to_string();
let mut iter = crate::prefixes::PREFIX_TO_ORIGIN.iter();
let origin = loop {
let (prefix, origin) = iter
.next()
.unwrap_or_else(|| panic!("code doesn't start with any recognized prefix: {ident}"));
if ident.starts_with(prefix) {
break origin;
}
};
Ident::new(origin, Span::call_site())
}
pub struct Mapping {
entries: Vec<(Ident, Path, Ident)>,
}

View File

@@ -19,7 +19,7 @@ use syn::{parse_macro_input, DeriveInput, ItemFn};
mod config;
mod define_rule_mapping;
mod derive_message_formats;
mod prefixes;
mod parse_code;
mod rule_code_prefix;
#[proc_macro_derive(ConfigurationOptions, attributes(option, doc, option_group))]
@@ -37,6 +37,15 @@ pub fn define_rule_mapping(item: proc_macro::TokenStream) -> proc_macro::TokenSt
define_rule_mapping::define_rule_mapping(&mapping).into()
}
#[proc_macro_derive(ParseCode, attributes(prefix))]
pub fn derive_rule_code_prefix(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
parse_code::derive_impl(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
#[proc_macro_attribute]
pub fn derive_message_formats(_attr: TokenStream, item: TokenStream) -> TokenStream {
let func = parse_macro_input!(item as ItemFn);

View File

@@ -0,0 +1,54 @@
use quote::quote;
use syn::spanned::Spanned;
use syn::{Data, DataEnum, DeriveInput, Error, Lit, Meta, MetaNameValue};
pub fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let DeriveInput { ident, data: Data::Enum(DataEnum {
variants, ..
}), .. } = input else {
return Err(Error::new(input.ident.span(), "only named fields are supported"));
};
let mut parsed = Vec::new();
for variant in variants {
let prefix_attrs: Vec<_> = variant
.attrs
.iter()
.filter(|a| a.path.is_ident("prefix"))
.collect();
if prefix_attrs.is_empty() {
return Err(Error::new(
variant.span(),
r#"Missing [#prefix = "..."] attribute"#,
));
}
for attr in prefix_attrs {
let Ok(Meta::NameValue(MetaNameValue{lit: Lit::Str(lit), ..})) = attr.parse_meta() else {
return Err(Error::new(attr.span(), r#"expected attribute to be in the form of [#prefix = "..."]"#))
};
parsed.push((lit, variant.ident.clone()));
}
}
parsed.sort_by_key(|(prefix, _)| prefix.value().len());
let mut if_statements = quote!();
for (prefix, field) in parsed {
if_statements.extend(quote! {if let Some(rest) = code.strip_prefix(#prefix) {
return Some((#ident::#field, rest));
}});
}
Ok(quote! {
impl crate::registry::ParseCode for #ident {
fn parse_code(code: &str) -> Option<(Self, &str)> {
#if_statements
None
}
}
})
}

View File

@@ -1,54 +0,0 @@
// Longer prefixes should come first so that you can find an origin for a code
// by simply picking the first entry that starts with the given prefix.
pub const PREFIX_TO_ORIGIN: &[(&str, &str)] = &[
("ANN", "Flake8Annotations"),
("ARG", "Flake8UnusedArguments"),
("A", "Flake8Builtins"),
("BLE", "Flake8BlindExcept"),
("B", "Flake8Bugbear"),
("C4", "Flake8Comprehensions"),
("C9", "McCabe"),
("COM", "Flake8Commas"),
("DTZ", "Flake8Datetimez"),
("D", "Pydocstyle"),
("ERA", "Eradicate"),
("EM", "Flake8ErrMsg"),
("E", "Pycodestyle"),
("FBT", "Flake8BooleanTrap"),
("F", "Pyflakes"),
("ICN", "Flake8ImportConventions"),
("ISC", "Flake8ImplicitStrConcat"),
("I", "Isort"),
("N", "PEP8Naming"),
("PD", "PandasVet"),
("PGH", "PygrepHooks"),
("PL", "Pylint"),
("PT", "Flake8PytestStyle"),
("Q", "Flake8Quotes"),
("RET", "Flake8Return"),
("SIM", "Flake8Simplify"),
("S", "Flake8Bandit"),
("T10", "Flake8Debugger"),
("T20", "Flake8Print"),
("TID", "Flake8TidyImports"),
("UP", "Pyupgrade"),
("W", "Pycodestyle"),
("YTT", "Flake82020"),
("PIE", "Flake8Pie"),
("RUF", "Ruff"),
];
#[cfg(test)]
mod tests {
use super::PREFIX_TO_ORIGIN;
#[test]
fn order() {
for (idx, (prefix, _)) in PREFIX_TO_ORIGIN.iter().enumerate() {
for (prior_prefix, _) in PREFIX_TO_ORIGIN[..idx].iter() {
assert!(!prefix.starts_with(prior_prefix));
}
}
}
}

View File

@@ -7,8 +7,8 @@ use syn::Ident;
const ALL: &str = "ALL";
/// A hash map from deprecated `RuleCodePrefix` to latest
/// `RuleCodePrefix`.
/// A hash map from deprecated `RuleSelector` to latest
/// `RuleSelector`.
pub static PREFIX_REDIRECTS: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
HashMap::from_iter([
// TODO(charlie): Remove by 2023-01-01.
@@ -169,7 +169,7 @@ pub fn expand<'a>(
#prefix_impl
/// A hash map from deprecated `RuleCodePrefix` to latest `RuleCodePrefix`.
/// A hash map from deprecated `RuleSelector` to latest `RuleSelector`.
pub static PREFIX_REDIRECTS: ::once_cell::sync::Lazy<::rustc_hash::FxHashMap<&'static str, #prefix_ident>> = ::once_cell::sync::Lazy::new(|| {
::rustc_hash::FxHashMap::from_iter([
#(#prefix_redirects),*

18
scripts/_utils.py Normal file
View File

@@ -0,0 +1,18 @@
import os
import re
from pathlib import Path
ROOT_DIR = Path(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
def dir_name(linter_name: str) -> str:
return linter_name.replace("-", "_")
def pascal_case(linter_name: str) -> str:
"""Convert from snake-case to PascalCase."""
return "".join(word.title() for word in linter_name.split("-"))
def get_indent(line: str) -> str:
return re.match(r"^\s*", line).group() # pyright: ignore[reportOptionalMemberAccess]

39
scripts/add_plugin.py Normal file → Executable file
View File

@@ -10,17 +10,8 @@ Example usage:
import argparse
import os
from pathlib import Path
ROOT_DIR = Path(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
def dir_name(plugin: str) -> str:
return plugin.replace("-", "_")
def pascal_case(plugin: str) -> str:
return "".join(word.title() for word in plugin.split("-"))
from _utils import ROOT_DIR, dir_name, get_indent, pascal_case
def main(*, plugin: str, url: str) -> None:
@@ -36,6 +27,7 @@ def main(*, plugin: str, url: str) -> None:
with open(rust_module / "rules.rs", "w+") as fp:
fp.write("use crate::checkers::ast::Checker;\n")
with open(rust_module / "mod.rs", "w+") as fp:
fp.write(f"//! Rules from [{plugin}]({url}).\n")
fp.write("pub(crate) mod rules;\n")
fp.write("\n")
fp.write(
@@ -76,35 +68,24 @@ mod tests {
with open(ROOT_DIR / "src/registry.rs", "w") as fp:
for line in content.splitlines():
indent = get_indent(line)
if line.strip() == "// Ruff":
indent = line.split("// Ruff")[0]
fp.write(f"{indent}// {plugin}")
fp.write("\n")
elif line.strip() == "Ruff,":
indent = line.split("Ruff,")[0]
elif line.strip() == '#[prefix = "RUF"]':
fp.write(f'{indent}#[prefix = "TODO"]\n')
fp.write(f"{indent}{pascal_case(plugin)},")
fp.write("\n")
elif line.strip() == 'RuleOrigin::Ruff => "Ruff-specific rules",':
indent = line.split('RuleOrigin::Ruff => "Ruff-specific rules",')[0]
fp.write(f'{indent}RuleOrigin::{pascal_case(plugin)} => "{plugin}",')
fp.write("\n")
elif line.strip() == "RuleOrigin::Ruff => vec![RuleCodePrefix::RUF],":
indent = line.split("RuleOrigin::Ruff => vec![RuleCodePrefix::RUF],")[0]
elif line.strip() == "Linter::Ruff => Prefixes::Single(RuleSelector::RUF),":
prefix = 'todo!("Fill-in prefix after generating codes")'
fp.write(
f"{indent}RuleOrigin::{pascal_case(plugin)} => vec![\n"
f'{indent} todo!("Fill-in prefix after generating codes")\n'
f"{indent}],"
f"{indent}Linter::{pascal_case(plugin)} => Prefixes::Single({prefix}),"
)
fp.write("\n")
elif line.strip() == "RuleOrigin::Ruff => None,":
indent = line.split("RuleOrigin::Ruff => None,")[0]
fp.write(f"{indent}RuleOrigin::{pascal_case(plugin)} => " f'Some(("{url}", &Platform::PyPI)),')
fp.write("\n")
fp.write(line)
fp.write("\n")
@@ -114,7 +95,7 @@ mod tests {
with open(ROOT_DIR / "src/violations.rs", "w") as fp:
for line in content.splitlines():
if line.strip() == "// Ruff":
indent = line.split("// Ruff")[0]
indent = get_indent(line)
fp.write(f"{indent}// {plugin}")
fp.write("\n")

51
scripts/add_rule.py Normal file → Executable file
View File

@@ -6,23 +6,12 @@ Example usage:
python scripts/add_rule.py \
--name PreferListBuiltin \
--code PIE807 \
--origin flake8-pie
--linter flake8-pie
"""
import argparse
import os
from pathlib import Path
ROOT_DIR = Path(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
def dir_name(origin: str) -> str:
return origin.replace("-", "_")
def pascal_case(origin: str) -> str:
"""Convert from snake-case to PascalCase."""
return "".join(word.title() for word in origin.split("-"))
from _utils import ROOT_DIR, dir_name, get_indent
def snake_case(name: str) -> str:
@@ -30,22 +19,22 @@ def snake_case(name: str) -> str:
return "".join(f"_{word.lower()}" if word.isupper() else word for word in name).lstrip("_")
def main(*, name: str, code: str, origin: str) -> None:
def main(*, name: str, code: str, linter: str) -> None:
# Create a test fixture.
with open(
ROOT_DIR / "resources/test/fixtures" / dir_name(origin) / f"{code}.py",
ROOT_DIR / "resources/test/fixtures" / dir_name(linter) / f"{code}.py",
"a",
):
pass
# Add the relevant `#testcase` macro.
mod_rs = ROOT_DIR / "src/rules" / dir_name(origin) / "mod.rs"
mod_rs = ROOT_DIR / "src/rules" / dir_name(linter) / "mod.rs"
content = mod_rs.read_text()
with open(mod_rs, "w") as fp:
for line in content.splitlines():
if line.strip() == "fn rules(rule_code: Rule, path: &Path) -> Result<()> {":
indent = line.split("fn rules(rule_code: Rule, path: &Path) -> Result<()> {")[0]
indent = get_indent(line)
fp.write(f'{indent}#[test_case(Rule::{code}, Path::new("{code}.py"); "{code}")]')
fp.write("\n")
@@ -53,7 +42,7 @@ def main(*, name: str, code: str, origin: str) -> None:
fp.write("\n")
# Add the relevant rule function.
with open(ROOT_DIR / "src/rules" / dir_name(origin) / "rules.rs", "a") as fp:
with open(ROOT_DIR / "src/rules" / dir_name(linter) / (snake_case(name) + ".rs"), "w") as fp:
fp.write(
f"""
/// {code}
@@ -70,22 +59,20 @@ pub fn {snake_case(name)}(checker: &mut Checker) {{}}
fp.write(line)
fp.write("\n")
if line.startswith(f"// {origin}"):
if line.startswith(f"// {linter}"):
fp.write(
"""define_violation!(
pub struct %s;
);
impl Violation for %s {
#[derive_message_formats]
fn message(&self) -> String {
todo!("Implement message")
}
fn placeholder() -> Self {
%s
todo!("implement message");
format!("TODO: write message")
}
}
"""
% (name, name, name)
% (name, name)
)
fp.write("\n")
@@ -102,24 +89,26 @@ impl Violation for %s {
if has_written:
continue
if line.startswith("define_rule_mapping!"):
if line.startswith("ruff_macros::define_rule_mapping!"):
seen_macro = True
continue
if not seen_macro:
continue
if line.strip() == f"// {origin}":
indent = line.split("//")[0]
if line.strip() == f"// {linter}":
indent = get_indent(line)
fp.write(f"{indent}{code} => violations::{name},")
fp.write("\n")
has_written = True
assert has_written
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Generate boilerplate for a new rule.",
epilog="python scripts/add_rule.py --name PreferListBuiltin --code PIE807 --origin flake8-pie",
epilog="python scripts/add_rule.py --name PreferListBuiltin --code PIE807 --linter flake8-pie",
)
parser.add_argument(
"--name",
@@ -134,11 +123,11 @@ if __name__ == "__main__":
help="The code of the check to generate (e.g., 'A001').",
)
parser.add_argument(
"--origin",
"--linter",
type=str,
required=True,
help="The source with which the check originated (e.g., 'flake8-builtins').",
)
args = parser.parse_args()
main(name=args.name, code=args.code, origin=args.origin)
main(name=args.name, code=args.code, linter=args.linter)

9
scripts/pyproject.toml Normal file
View File

@@ -0,0 +1,9 @@
[tool.ruff]
select = ["ALL"]
ignore = [
"S101", # assert-used
"PLR2004", # magic-value-comparison
]
[tool.ruff.pydocstyle]
convention = "pep257"

View File

@@ -13,6 +13,7 @@ Please use `python -m pip install .` instead.
)
sys.exit(1)
"abc".isidentifier()
# The below code will never execute, however GitHub is particularly
# picky about where it finds Python packaging metadata.

View File

@@ -35,9 +35,9 @@ use crate::rules::{
flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except, flake8_boolean_trap,
flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez, flake8_debugger,
flake8_errmsg, flake8_implicit_str_concat, flake8_import_conventions, flake8_pie, flake8_print,
flake8_pytest_style, flake8_return, flake8_simplify, flake8_tidy_imports,
flake8_pytest_style, flake8_return, flake8_simplify, flake8_tidy_imports, flake8_type_checking,
flake8_unused_arguments, mccabe, pandas_vet, pep8_naming, pycodestyle, pydocstyle, pyflakes,
pygrep_hooks, pylint, pyupgrade, ruff,
pygrep_hooks, pylint, pyupgrade, ruff, tryceratops,
};
use crate::settings::types::PythonVersion;
use crate::settings::{flags, Settings};
@@ -1355,8 +1355,17 @@ where
if self.settings.rules.enabled(&Rule::IfTuple) {
pyflakes::rules::if_tuple(self, stmt, test);
}
if self.settings.rules.enabled(&Rule::EmptyTypeCheckingBlock) {
flake8_type_checking::rules::empty_type_checking_block(self, test, body);
}
if self.settings.rules.enabled(&Rule::NestedIfStatements) {
flake8_simplify::rules::nested_if_statements(self, stmt);
flake8_simplify::rules::nested_if_statements(
self,
stmt,
test,
body,
self.current_stmt_parent().map(Into::into),
);
}
if self
.settings
@@ -1535,6 +1544,9 @@ where
self, body, handlers, finalbody,
);
}
if self.settings.rules.enabled(&Rule::TryConsiderElse) {
tryceratops::rules::try_consider_else(self, body, orelse);
}
}
StmtKind::Assign { targets, value, .. } => {
if self.settings.rules.enabled(&Rule::DoNotAssignLambda) {
@@ -2618,7 +2630,7 @@ where
.enabled(&Rule::PercentFormatUnsupportedFormatCharacter)
{
let location = Range::from_located(expr);
match pyflakes::cformat::CFormatSummary::try_from(value.as_ref()) {
match pyflakes::cformat::CFormatSummary::try_from(value.as_str()) {
Err(CFormatError {
typ: CFormatErrorType::UnsupportedFormatChar(c),
..
@@ -2713,6 +2725,10 @@ where
}
}
}
if self.settings.rules.enabled(&Rule::PrintfStringFormatting) {
pyupgrade::rules::printf_string_formatting(self, expr, left, right);
}
}
}
ExprKind::BinOp {
@@ -3571,7 +3587,6 @@ impl<'a> Checker<'a> {
{
let binding_index = self.bindings.len();
let mut overridden = None;
if let Some((stack_index, scope_index)) = self
.scope_stack
.iter()
@@ -3603,7 +3618,6 @@ impl<'a> Checker<'a> {
| BindingKind::FutureImportation
);
if matches!(binding.kind, BindingKind::LoopVar) && existing_is_import {
overridden = Some((*scope_index, *existing_binding_index));
if self.settings.rules.enabled(&Rule::ImportShadowedByLoopVar) {
self.diagnostics.push(Diagnostic::new(
violations::ImportShadowedByLoopVar(
@@ -3623,7 +3637,6 @@ impl<'a> Checker<'a> {
cast::decorator_list(existing.source.as_ref().unwrap()),
))
{
overridden = Some((*scope_index, *existing_binding_index));
if self.settings.rules.enabled(&Rule::RedefinedWhileUnused) {
self.diagnostics.push(Diagnostic::new(
violations::RedefinedWhileUnused(
@@ -3643,13 +3656,6 @@ impl<'a> Checker<'a> {
}
}
// If we're about to lose the binding, store it as overridden.
if let Some((scope_index, binding_index)) = overridden {
self.scopes[scope_index]
.overridden
.push((name, binding_index));
}
// Assume the rebound name is used as a global or within a loop.
let scope = self.current_scope();
let binding = match scope.values.get(&name) {
@@ -4497,6 +4503,7 @@ impl<'a> Checker<'a> {
.rules
.enabled(&Rule::UsesRPrefixForBackslashedContent)
|| self.settings.rules.enabled(&Rule::EndsInPeriod)
|| self.settings.rules.enabled(&Rule::NonImperativeMood)
|| self.settings.rules.enabled(&Rule::NoSignature)
|| self.settings.rules.enabled(&Rule::FirstLineCapitalized)
|| self.settings.rules.enabled(&Rule::NoThisPrefix)
@@ -4645,6 +4652,9 @@ impl<'a> Checker<'a> {
if self.settings.rules.enabled(&Rule::EndsInPeriod) {
pydocstyle::rules::ends_with_period(self, &docstring);
}
if self.settings.rules.enabled(&Rule::NonImperativeMood) {
pydocstyle::rules::non_imperative_mood::non_imperative_mood(self, &docstring);
}
if self.settings.rules.enabled(&Rule::NoSignature) {
pydocstyle::rules::no_signature(self, &docstring);
}
@@ -4718,6 +4728,7 @@ impl<'a> Checker<'a> {
name,
located,
flake8_builtins::types::ShadowingType::Attribute,
&self.settings.flake8_builtins.builtins_ignorelist,
) {
self.diagnostics.push(diagnostic);
}
@@ -4728,6 +4739,7 @@ impl<'a> Checker<'a> {
name,
located,
flake8_builtins::types::ShadowingType::Variable,
&self.settings.flake8_builtins.builtins_ignorelist,
) {
self.diagnostics.push(diagnostic);
}
@@ -4741,6 +4753,7 @@ impl<'a> Checker<'a> {
name,
arg,
flake8_builtins::types::ShadowingType::Argument,
&self.settings.flake8_builtins.builtins_ignorelist,
) {
self.diagnostics.push(diagnostic);
}

View File

@@ -1,8 +1,10 @@
//! Lint rules based on checking raw physical lines.
use crate::registry::{Diagnostic, Rule};
use crate::rules::flake8_executable::helpers::extract_shebang;
use crate::rules::flake8_executable::rules::{shebang_newline, shebang_python, shebang_whitespace};
use crate::rules::pycodestyle::rules::{
doc_line_too_long, line_too_long, no_newline_at_end_of_file,
doc_line_too_long, line_too_long, mixed_spaces_and_tabs, no_newline_at_end_of_file,
};
use crate::rules::pygrep_hooks::rules::{blanket_noqa, blanket_type_ignore};
use crate::rules::pyupgrade::rules::unnecessary_coding_comment;
@@ -18,6 +20,9 @@ pub fn check_lines(
let mut diagnostics: Vec<Diagnostic> = vec![];
let enforce_blanket_noqa = settings.rules.enabled(&Rule::BlanketNOQA);
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);
@@ -25,6 +30,14 @@ pub fn check_lines(
let enforce_unnecessary_coding_comment = settings
.rules
.enabled(&Rule::PEP3120UnnecessaryCodingComment);
let enforce_mixed_spaces_and_tabs = settings.rules.enabled(&Rule::MixedSpacesAndTabs);
let fix_unnecessary_coding_comment = matches!(autofix, flags::Autofix::Enabled)
&& settings
.rules
.should_fix(&Rule::PEP3120UnnecessaryCodingComment);
let fix_shebang_whitespace = matches!(autofix, flags::Autofix::Enabled)
&& settings.rules.should_fix(&Rule::ShebangWhitespace);
let mut commented_lines_iter = commented_lines.iter().peekable();
let mut doc_lines_iter = doc_lines.iter().peekable();
@@ -35,14 +48,9 @@ pub fn check_lines(
{
if enforce_unnecessary_coding_comment {
if index < 2 {
if let Some(diagnostic) = unnecessary_coding_comment(
index,
line,
matches!(autofix, flags::Autofix::Enabled)
&& settings
.rules
.should_fix(&Rule::PEP3120UnnecessaryCodingComment),
) {
if let Some(diagnostic) =
unnecessary_coding_comment(index, line, fix_unnecessary_coding_comment)
{
diagnostics.push(diagnostic);
}
}
@@ -59,6 +67,27 @@ pub fn check_lines(
diagnostics.push(diagnostic);
}
}
if enforce_shebang_whitespace || enforce_shebang_newline || enforce_shebang_python {
let shebang = extract_shebang(line);
if enforce_shebang_whitespace {
if let Some(diagnostic) =
shebang_whitespace(index, &shebang, fix_shebang_whitespace)
{
diagnostics.push(diagnostic);
}
}
if enforce_shebang_newline {
if let Some(diagnostic) = shebang_newline(index, &shebang) {
diagnostics.push(diagnostic);
}
}
if enforce_shebang_python {
if let Some(diagnostic) = shebang_python(index, &shebang) {
diagnostics.push(diagnostic);
}
}
}
}
while doc_lines_iter
@@ -72,6 +101,12 @@ pub fn check_lines(
}
}
if enforce_mixed_spaces_and_tabs {
if let Some(diagnostic) = mixed_spaces_and_tabs(index, line) {
diagnostics.push(diagnostic);
}
}
if enforce_line_too_long {
if let Some(diagnostic) = line_too_long(index, line, settings) {
diagnostics.push(diagnostic);

View File

@@ -6,7 +6,8 @@ use crate::lex::docstring_detection::StateMachine;
use crate::registry::{Diagnostic, Rule};
use crate::rules::ruff::rules::Context;
use crate::rules::{
eradicate, flake8_commas, flake8_implicit_str_concat, flake8_quotes, pycodestyle, ruff,
eradicate, flake8_commas, flake8_implicit_str_concat, flake8_quotes, pycodestyle, pyupgrade,
ruff,
};
use crate::settings::{flags, Settings};
use crate::source_code::Locator;
@@ -45,6 +46,7 @@ pub fn check_tokens(
.rules
.enabled(&Rule::TrailingCommaOnBareTupleProhibited)
|| settings.rules.enabled(&Rule::TrailingCommaProhibited);
let enforce_extraneous_parenthesis = settings.rules.enabled(&Rule::ExtraneousParentheses);
let mut state_machine = StateMachine::default();
for &(start, ref tok, end) in tokens.iter().flatten() {
@@ -137,5 +139,13 @@ pub fn check_tokens(
);
}
// UP034
if enforce_extraneous_parenthesis {
diagnostics.extend(
pyupgrade::rules::extraneous_parentheses(tokens, locator, settings, autofix)
.into_iter(),
);
}
diagnostics
}

View File

@@ -6,7 +6,7 @@ use colored::Colorize;
use super::black::Black;
use super::plugin::Plugin;
use super::{parser, plugin};
use crate::registry::RuleCodePrefix;
use crate::registry::RuleSelector;
use crate::rules::flake8_pytest_style::types::{
ParametrizeNameType, ParametrizeValuesRowType, ParametrizeValuesType,
};
@@ -14,8 +14,8 @@ use crate::rules::flake8_quotes::settings::Quote;
use crate::rules::flake8_tidy_imports::relative_imports::Strictness;
use crate::rules::pydocstyle::settings::Convention;
use crate::rules::{
flake8_annotations, flake8_bugbear, flake8_errmsg, flake8_pytest_style, flake8_quotes,
flake8_tidy_imports, mccabe, pep8_naming, pydocstyle,
flake8_annotations, flake8_bugbear, flake8_builtins, flake8_errmsg, flake8_pytest_style,
flake8_quotes, flake8_tidy_imports, mccabe, pep8_naming, pydocstyle,
};
use crate::settings::options::Options;
use crate::settings::pyproject::Pyproject;
@@ -32,7 +32,7 @@ pub fn convert(
.expect("Unable to find flake8 section in INI file");
// Extract all referenced rule code prefixes, to power plugin inference.
let mut referenced_codes: BTreeSet<RuleCodePrefix> = BTreeSet::default();
let mut referenced_codes: BTreeSet<RuleSelector> = BTreeSet::default();
for (key, value) in flake8 {
if let Some(value) = value {
match key.as_str() {
@@ -90,6 +90,7 @@ pub fn convert(
let mut options = Options::default();
let mut flake8_annotations = flake8_annotations::settings::Options::default();
let mut flake8_bugbear = flake8_bugbear::settings::Options::default();
let mut flake8_builtins = flake8_builtins::settings::Options::default();
let mut flake8_errmsg = flake8_errmsg::settings::Options::default();
let mut flake8_pytest_style = flake8_pytest_style::settings::Options::default();
let mut flake8_quotes = flake8_quotes::settings::Options::default();
@@ -104,7 +105,7 @@ pub fn convert(
"builtins" => {
options.builtins = Some(parser::parse_strings(value.as_ref()));
}
"max-line-length" | "max_line_length" => match value.clone().parse::<usize>() {
"max-line-length" | "max_line_length" => match value.parse::<usize>() {
Ok(line_length) => options.line_length = Some(line_length),
Err(e) => {
warn_user!("Unable to parse '{key}' property: {e}");
@@ -147,6 +148,11 @@ pub fn convert(
flake8_bugbear.extend_immutable_calls =
Some(parser::parse_strings(value.as_ref()));
}
// flake8-builtins
"builtins-ignorelist" | "builtins_ignorelist" => {
flake8_builtins.builtins_ignorelist =
Some(parser::parse_strings(value.as_ref()));
}
// flake8-annotations
"suppress-none-returning" | "suppress_none_returning" => {
match parser::parse_bool(value.as_ref()) {
@@ -241,7 +247,7 @@ pub fn convert(
}
},
// mccabe
"max-complexity" | "max_complexity" => match value.clone().parse::<usize>() {
"max-complexity" | "max_complexity" => match value.parse::<usize>() {
Ok(max_complexity) => mccabe.max_complexity = Some(max_complexity),
Err(e) => {
warn_user!("Unable to parse '{key}' property: {e}");
@@ -249,7 +255,7 @@ pub fn convert(
},
// flake8-errmsg
"errmsg-max-string-length" | "errmsg_max_string_length" => {
match value.clone().parse::<usize>() {
match value.parse::<usize>() {
Ok(max_string_length) => {
flake8_errmsg.max_string_length = Some(max_string_length);
}
@@ -345,6 +351,9 @@ pub fn convert(
if flake8_bugbear != flake8_bugbear::settings::Options::default() {
options.flake8_bugbear = Some(flake8_bugbear);
}
if flake8_builtins != flake8_builtins::settings::Options::default() {
options.flake8_builtins = Some(flake8_builtins);
}
if flake8_errmsg != flake8_errmsg::settings::Options::default() {
options.flake8_errmsg = Some(flake8_errmsg);
}
@@ -392,7 +401,7 @@ mod tests {
use super::super::plugin::Plugin;
use super::convert;
use crate::registry::RuleCodePrefix;
use crate::registry::RuleSelector;
use crate::rules::pydocstyle::settings::Convention;
use crate::rules::{flake8_quotes, pydocstyle};
use crate::settings::options::Options;
@@ -428,11 +437,7 @@ mod tests {
per_file_ignores: None,
required_version: None,
respect_gitignore: None,
select: Some(vec![
RuleCodePrefix::E,
RuleCodePrefix::F,
RuleCodePrefix::W,
]),
select: Some(vec![RuleSelector::E, RuleSelector::F, RuleSelector::W]),
show_source: None,
src: None,
target_version: None,
@@ -443,6 +448,7 @@ mod tests {
flake8_annotations: None,
flake8_bandit: None,
flake8_bugbear: None,
flake8_builtins: None,
flake8_errmsg: None,
flake8_pytest_style: None,
flake8_quotes: None,
@@ -495,11 +501,7 @@ mod tests {
per_file_ignores: None,
required_version: None,
respect_gitignore: None,
select: Some(vec![
RuleCodePrefix::E,
RuleCodePrefix::F,
RuleCodePrefix::W,
]),
select: Some(vec![RuleSelector::E, RuleSelector::F, RuleSelector::W]),
show_source: None,
src: None,
target_version: None,
@@ -510,6 +512,7 @@ mod tests {
flake8_annotations: None,
flake8_bandit: None,
flake8_bugbear: None,
flake8_builtins: None,
flake8_errmsg: None,
flake8_pytest_style: None,
flake8_quotes: None,
@@ -562,11 +565,7 @@ mod tests {
per_file_ignores: None,
required_version: None,
respect_gitignore: None,
select: Some(vec![
RuleCodePrefix::E,
RuleCodePrefix::F,
RuleCodePrefix::W,
]),
select: Some(vec![RuleSelector::E, RuleSelector::F, RuleSelector::W]),
show_source: None,
src: None,
target_version: None,
@@ -577,6 +576,7 @@ mod tests {
flake8_annotations: None,
flake8_bandit: None,
flake8_bugbear: None,
flake8_builtins: None,
flake8_errmsg: None,
flake8_pytest_style: None,
flake8_quotes: None,
@@ -629,11 +629,7 @@ mod tests {
per_file_ignores: None,
required_version: None,
respect_gitignore: None,
select: Some(vec![
RuleCodePrefix::E,
RuleCodePrefix::F,
RuleCodePrefix::W,
]),
select: Some(vec![RuleSelector::E, RuleSelector::F, RuleSelector::W]),
show_source: None,
src: None,
target_version: None,
@@ -644,6 +640,7 @@ mod tests {
flake8_annotations: None,
flake8_bandit: None,
flake8_bugbear: None,
flake8_builtins: None,
flake8_errmsg: None,
flake8_pytest_style: None,
flake8_quotes: None,
@@ -696,11 +693,7 @@ mod tests {
per_file_ignores: None,
required_version: None,
respect_gitignore: None,
select: Some(vec![
RuleCodePrefix::E,
RuleCodePrefix::F,
RuleCodePrefix::W,
]),
select: Some(vec![RuleSelector::E, RuleSelector::F, RuleSelector::W]),
show_source: None,
src: None,
target_version: None,
@@ -711,6 +704,7 @@ mod tests {
flake8_annotations: None,
flake8_bandit: None,
flake8_bugbear: None,
flake8_builtins: None,
flake8_errmsg: None,
flake8_pytest_style: None,
flake8_quotes: Some(flake8_quotes::settings::Options {
@@ -772,10 +766,10 @@ mod tests {
required_version: None,
respect_gitignore: None,
select: Some(vec![
RuleCodePrefix::D,
RuleCodePrefix::E,
RuleCodePrefix::F,
RuleCodePrefix::W,
RuleSelector::D,
RuleSelector::E,
RuleSelector::F,
RuleSelector::W,
]),
show_source: None,
src: None,
@@ -787,6 +781,7 @@ mod tests {
flake8_annotations: None,
flake8_bandit: None,
flake8_bugbear: None,
flake8_builtins: None,
flake8_errmsg: None,
flake8_pytest_style: None,
flake8_quotes: None,
@@ -842,10 +837,10 @@ mod tests {
required_version: None,
respect_gitignore: None,
select: Some(vec![
RuleCodePrefix::E,
RuleCodePrefix::F,
RuleCodePrefix::Q,
RuleCodePrefix::W,
RuleSelector::E,
RuleSelector::F,
RuleSelector::Q,
RuleSelector::W,
]),
show_source: None,
src: None,
@@ -857,6 +852,7 @@ mod tests {
flake8_annotations: None,
flake8_bandit: None,
flake8_bugbear: None,
flake8_builtins: None,
flake8_errmsg: None,
flake8_pytest_style: None,
flake8_quotes: Some(flake8_quotes::settings::Options {

View File

@@ -6,16 +6,16 @@ use once_cell::sync::Lazy;
use regex::Regex;
use rustc_hash::FxHashMap;
use crate::registry::{RuleCodePrefix, PREFIX_REDIRECTS};
use crate::registry::{RuleSelector, PREFIX_REDIRECTS};
use crate::settings::types::PatternPrefixPair;
use crate::warn_user;
static COMMA_SEPARATED_LIST_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").unwrap());
/// Parse a comma-separated list of `RuleCodePrefix` values (e.g.,
/// Parse a comma-separated list of `RuleSelector` values (e.g.,
/// "F401,E501").
pub fn parse_prefix_codes(value: &str) -> Vec<RuleCodePrefix> {
let mut codes: Vec<RuleCodePrefix> = vec![];
pub fn parse_prefix_codes(value: &str) -> Vec<RuleSelector> {
let mut codes: Vec<RuleSelector> = vec![];
for code in COMMA_SEPARATED_LIST_RE.split(value) {
let code = code.trim();
if code.is_empty() {
@@ -23,7 +23,7 @@ pub fn parse_prefix_codes(value: &str) -> Vec<RuleCodePrefix> {
}
if let Some(code) = PREFIX_REDIRECTS.get(code) {
codes.push(code.clone());
} else if let Ok(code) = RuleCodePrefix::from_str(code) {
} else if let Ok(code) = RuleSelector::from_str(code) {
codes.push(code);
} else {
warn_user!("Unsupported prefix code: {code}");
@@ -96,7 +96,7 @@ impl State {
prefix: code.clone(),
});
}
} else if let Ok(code) = RuleCodePrefix::from_str(code) {
} else if let Ok(code) = RuleSelector::from_str(code) {
for filename in &self.filenames {
codes.push(PatternPrefixPair {
pattern: filename.clone(),
@@ -190,8 +190,8 @@ pub fn parse_files_to_codes_mapping(value: &str) -> Result<Vec<PatternPrefixPair
/// Collect a list of `PatternPrefixPair` structs as a `BTreeMap`.
pub fn collect_per_file_ignores(
pairs: Vec<PatternPrefixPair>,
) -> FxHashMap<String, Vec<RuleCodePrefix>> {
let mut per_file_ignores: FxHashMap<String, Vec<RuleCodePrefix>> = FxHashMap::default();
) -> FxHashMap<String, Vec<RuleSelector>> {
let mut per_file_ignores: FxHashMap<String, Vec<RuleSelector>> = FxHashMap::default();
for pair in pairs {
per_file_ignores
.entry(pair.pattern)
@@ -206,33 +206,33 @@ mod tests {
use anyhow::Result;
use super::{parse_files_to_codes_mapping, parse_prefix_codes, parse_strings};
use crate::registry::RuleCodePrefix;
use crate::registry::RuleSelector;
use crate::settings::types::PatternPrefixPair;
#[test]
fn it_parses_prefix_codes() {
let actual = parse_prefix_codes("");
let expected: Vec<RuleCodePrefix> = vec![];
let expected: Vec<RuleSelector> = vec![];
assert_eq!(actual, expected);
let actual = parse_prefix_codes(" ");
let expected: Vec<RuleCodePrefix> = vec![];
let expected: Vec<RuleSelector> = vec![];
assert_eq!(actual, expected);
let actual = parse_prefix_codes("F401");
let expected = vec![RuleCodePrefix::F401];
let expected = vec![RuleSelector::F401];
assert_eq!(actual, expected);
let actual = parse_prefix_codes("F401,");
let expected = vec![RuleCodePrefix::F401];
let expected = vec![RuleSelector::F401];
assert_eq!(actual, expected);
let actual = parse_prefix_codes("F401,E501");
let expected = vec![RuleCodePrefix::F401, RuleCodePrefix::E501];
let expected = vec![RuleSelector::F401, RuleSelector::E501];
assert_eq!(actual, expected);
let actual = parse_prefix_codes("F401, E501");
let expected = vec![RuleCodePrefix::F401, RuleCodePrefix::E501];
let expected = vec![RuleSelector::F401, RuleSelector::E501];
assert_eq!(actual, expected);
}
@@ -285,11 +285,11 @@ mod tests {
let expected: Vec<PatternPrefixPair> = vec![
PatternPrefixPair {
pattern: "locust/test/*".to_string(),
prefix: RuleCodePrefix::F841,
prefix: RuleSelector::F841,
},
PatternPrefixPair {
pattern: "examples/*".to_string(),
prefix: RuleCodePrefix::F841,
prefix: RuleSelector::F841,
},
];
assert_eq!(actual, expected);
@@ -305,23 +305,23 @@ mod tests {
let expected: Vec<PatternPrefixPair> = vec![
PatternPrefixPair {
pattern: "t/*".to_string(),
prefix: RuleCodePrefix::D,
prefix: RuleSelector::D,
},
PatternPrefixPair {
pattern: "setup.py".to_string(),
prefix: RuleCodePrefix::D,
prefix: RuleSelector::D,
},
PatternPrefixPair {
pattern: "examples/*".to_string(),
prefix: RuleCodePrefix::D,
prefix: RuleSelector::D,
},
PatternPrefixPair {
pattern: "docs/*".to_string(),
prefix: RuleCodePrefix::D,
prefix: RuleSelector::D,
},
PatternPrefixPair {
pattern: "extra/*".to_string(),
prefix: RuleCodePrefix::D,
prefix: RuleSelector::D,
},
];
assert_eq!(actual, expected);
@@ -343,47 +343,47 @@ mod tests {
let expected: Vec<PatternPrefixPair> = vec![
PatternPrefixPair {
pattern: "scrapy/__init__.py".to_string(),
prefix: RuleCodePrefix::E402,
prefix: RuleSelector::E402,
},
PatternPrefixPair {
pattern: "scrapy/core/downloader/handlers/http.py".to_string(),
prefix: RuleCodePrefix::F401,
prefix: RuleSelector::F401,
},
PatternPrefixPair {
pattern: "scrapy/http/__init__.py".to_string(),
prefix: RuleCodePrefix::F401,
prefix: RuleSelector::F401,
},
PatternPrefixPair {
pattern: "scrapy/linkextractors/__init__.py".to_string(),
prefix: RuleCodePrefix::E402,
prefix: RuleSelector::E402,
},
PatternPrefixPair {
pattern: "scrapy/linkextractors/__init__.py".to_string(),
prefix: RuleCodePrefix::F401,
prefix: RuleSelector::F401,
},
PatternPrefixPair {
pattern: "scrapy/selector/__init__.py".to_string(),
prefix: RuleCodePrefix::F401,
prefix: RuleSelector::F401,
},
PatternPrefixPair {
pattern: "scrapy/spiders/__init__.py".to_string(),
prefix: RuleCodePrefix::E402,
prefix: RuleSelector::E402,
},
PatternPrefixPair {
pattern: "scrapy/spiders/__init__.py".to_string(),
prefix: RuleCodePrefix::F401,
prefix: RuleSelector::F401,
},
PatternPrefixPair {
pattern: "scrapy/utils/url.py".to_string(),
prefix: RuleCodePrefix::F403,
prefix: RuleSelector::F403,
},
PatternPrefixPair {
pattern: "scrapy/utils/url.py".to_string(),
prefix: RuleCodePrefix::F405,
prefix: RuleSelector::F405,
},
PatternPrefixPair {
pattern: "tests/test_loader.py".to_string(),
prefix: RuleCodePrefix::E741,
prefix: RuleSelector::E741,
},
];
assert_eq!(actual, expected);

View File

@@ -4,7 +4,7 @@ use std::str::FromStr;
use anyhow::anyhow;
use crate::registry::RuleCodePrefix;
use crate::registry::RuleSelector;
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum Plugin {
@@ -98,32 +98,32 @@ impl fmt::Debug for Plugin {
}
impl Plugin {
pub fn prefix(&self) -> RuleCodePrefix {
pub fn prefix(&self) -> RuleSelector {
match self {
Plugin::Flake8Annotations => RuleCodePrefix::ANN,
Plugin::Flake8Bandit => RuleCodePrefix::S,
Plugin::Flake8Annotations => RuleSelector::ANN,
Plugin::Flake8Bandit => RuleSelector::S,
// TODO(charlie): Handle rename of `B` to `BLE`.
Plugin::Flake8BlindExcept => RuleCodePrefix::BLE,
Plugin::Flake8Bugbear => RuleCodePrefix::B,
Plugin::Flake8Builtins => RuleCodePrefix::A,
Plugin::Flake8Comprehensions => RuleCodePrefix::C4,
Plugin::Flake8Datetimez => RuleCodePrefix::DTZ,
Plugin::Flake8Debugger => RuleCodePrefix::T1,
Plugin::Flake8Docstrings => RuleCodePrefix::D,
Plugin::Flake8BlindExcept => RuleSelector::BLE,
Plugin::Flake8Bugbear => RuleSelector::B,
Plugin::Flake8Builtins => RuleSelector::A,
Plugin::Flake8Comprehensions => RuleSelector::C4,
Plugin::Flake8Datetimez => RuleSelector::DTZ,
Plugin::Flake8Debugger => RuleSelector::T1,
Plugin::Flake8Docstrings => RuleSelector::D,
// TODO(charlie): Handle rename of `E` to `ERA`.
Plugin::Flake8Eradicate => RuleCodePrefix::ERA,
Plugin::Flake8ErrMsg => RuleCodePrefix::EM,
Plugin::Flake8ImplicitStrConcat => RuleCodePrefix::ISC,
Plugin::Flake8Print => RuleCodePrefix::T2,
Plugin::Flake8PytestStyle => RuleCodePrefix::PT,
Plugin::Flake8Quotes => RuleCodePrefix::Q,
Plugin::Flake8Return => RuleCodePrefix::RET,
Plugin::Flake8Simplify => RuleCodePrefix::SIM,
Plugin::Flake8TidyImports => RuleCodePrefix::TID25,
Plugin::McCabe => RuleCodePrefix::C9,
Plugin::PandasVet => RuleCodePrefix::PD,
Plugin::PEP8Naming => RuleCodePrefix::N,
Plugin::Pyupgrade => RuleCodePrefix::UP,
Plugin::Flake8Eradicate => RuleSelector::ERA,
Plugin::Flake8ErrMsg => RuleSelector::EM,
Plugin::Flake8ImplicitStrConcat => RuleSelector::ISC,
Plugin::Flake8Print => RuleSelector::T2,
Plugin::Flake8PytestStyle => RuleSelector::PT,
Plugin::Flake8Quotes => RuleSelector::Q,
Plugin::Flake8Return => RuleSelector::RET,
Plugin::Flake8Simplify => RuleSelector::SIM,
Plugin::Flake8TidyImports => RuleSelector::TID25,
Plugin::McCabe => RuleSelector::C9,
Plugin::PandasVet => RuleSelector::PD,
Plugin::PEP8Naming => RuleSelector::N,
Plugin::Pyupgrade => RuleSelector::UP,
}
}
}
@@ -249,7 +249,7 @@ pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> V
///
/// For example, if the user ignores `ANN101`, we should infer that
/// `flake8-annotations` is active.
pub fn infer_plugins_from_codes(codes: &BTreeSet<RuleCodePrefix>) -> Vec<Plugin> {
pub fn infer_plugins_from_codes(codes: &BTreeSet<RuleSelector>) -> Vec<Plugin> {
[
Plugin::Flake8Annotations,
Plugin::Flake8Bandit,
@@ -287,10 +287,10 @@ pub fn infer_plugins_from_codes(codes: &BTreeSet<RuleCodePrefix>) -> Vec<Plugin>
.collect()
}
/// Resolve the set of enabled `RuleCodePrefix` values for the given
/// Resolve the set of enabled `RuleSelector` values for the given
/// plugins.
pub fn resolve_select(plugins: &[Plugin]) -> BTreeSet<RuleCodePrefix> {
let mut select = BTreeSet::from([RuleCodePrefix::F, RuleCodePrefix::E, RuleCodePrefix::W]);
pub fn resolve_select(plugins: &[Plugin]) -> BTreeSet<RuleSelector> {
let mut select = BTreeSet::from([RuleSelector::F, RuleSelector::E, RuleSelector::W]);
select.extend(plugins.iter().map(Plugin::prefix));
select
}

View File

@@ -9,9 +9,10 @@ use crate::directives;
use crate::linter::check_path;
use crate::registry::Rule;
use crate::rules::{
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_errmsg, flake8_import_conventions,
flake8_pytest_style, flake8_quotes, flake8_tidy_imports, flake8_unused_arguments, isort,
mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, pyupgrade,
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_errmsg,
flake8_import_conventions, flake8_pytest_style, flake8_quotes, flake8_tidy_imports,
flake8_unused_arguments, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint,
pyupgrade,
};
use crate::rustpython_helpers::tokenize;
use crate::settings::configuration::Configuration;
@@ -136,6 +137,7 @@ pub fn defaultSettings() -> Result<JsValue, JsValue> {
flake8_annotations: Some(flake8_annotations::settings::Settings::default().into()),
flake8_bandit: Some(flake8_bandit::settings::Settings::default().into()),
flake8_bugbear: Some(flake8_bugbear::settings::Settings::default().into()),
flake8_builtins: Some(flake8_builtins::settings::Settings::default().into()),
flake8_errmsg: Some(flake8_errmsg::settings::Settings::default().into()),
flake8_pytest_style: Some(flake8_pytest_style::settings::Settings::default().into()),
flake8_quotes: Some(flake8_quotes::settings::Settings::default().into()),

View File

@@ -1,5 +1,15 @@
use once_cell::sync::Lazy;
use regex::Regex;
/// Returns `true` if a string is a valid Python identifier (e.g., variable
/// name).
pub fn is_identifier(s: &str) -> bool {
// Is the first character a letter or underscore?
if !s
.chars()
.next()
.map_or(false, |c| c.is_alphabetic() || c == '_')
{
return false;
}
pub static IDENTIFIER_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^[A-Za-z_][A-Za-z0-9_]*$").unwrap());
// Are the rest of the characters letters, digits, or underscores?
s.chars().skip(1).all(|c| c.is_alphanumeric() || c == '_')
}

View File

@@ -2,6 +2,7 @@
use itertools::Itertools;
use once_cell::sync::Lazy;
use ruff_macros::ParseCode;
use rustc_hash::FxHashMap;
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
@@ -14,6 +15,7 @@ use crate::{rules, violations};
ruff_macros::define_rule_mapping!(
// pycodestyle errors
E101 => violations::MixedSpacesAndTabs,
E401 => violations::MultipleImportsOnOneLine,
E402 => violations::ModuleImportNotAtTopOfFile,
E501 => violations::LineTooLong,
@@ -250,8 +252,10 @@ ruff_macros::define_rule_mapping!(
UP028 => violations::RewriteYieldFrom,
UP029 => violations::UnnecessaryBuiltinImport,
UP030 => violations::FormatLiterals,
UP031 => violations::PrintfStringFormatting,
UP032 => violations::FString,
UP033 => violations::FunctoolsCache,
UP033 => violations::FunctoolsCache,
UP034 => violations::ExtraneousParentheses,
// pydocstyle
D100 => violations::PublicModule,
D101 => violations::PublicClass,
@@ -280,6 +284,7 @@ ruff_macros::define_rule_mapping!(
D300 => violations::UsesTripleQuotes,
D301 => violations::UsesRPrefixForBackslashedContent,
D400 => violations::EndsInPeriod,
D401 => crate::rules::pydocstyle::rules::non_imperative_mood::NonImperativeMood,
D402 => violations::NoSignature,
D403 => violations::FirstLineCapitalized,
D404 => violations::NoThisPrefix,
@@ -416,6 +421,14 @@ ruff_macros::define_rule_mapping!(
COM819 => violations::TrailingCommaProhibited,
// flake8-no-pep420
INP001 => violations::ImplicitNamespacePackage,
// flake8-executable
EXE003 => rules::flake8_executable::rules::ShebangPython,
EXE004 => rules::flake8_executable::rules::ShebangWhitespace,
EXE005 => rules::flake8_executable::rules::ShebangNewline,
// flake8-type-checking
TYP005 => rules::flake8_type_checking::rules::EmptyTypeCheckingBlock,
// tryceratops
TRY300 => rules::tryceratops::rules::TryConsiderElse,
// Ruff
RUF001 => violations::AmbiguousUnicodeCharacterString,
RUF002 => violations::AmbiguousUnicodeCharacterDocstring,
@@ -425,48 +438,94 @@ ruff_macros::define_rule_mapping!(
RUF100 => violations::UnusedNOQA,
);
#[derive(EnumIter, Debug, PartialEq, Eq)]
pub enum RuleOrigin {
#[derive(EnumIter, Debug, PartialEq, Eq, ParseCode)]
pub enum Linter {
#[prefix = "F"]
Pyflakes,
#[prefix = "E"]
#[prefix = "W"]
Pycodestyle,
#[prefix = "C9"]
McCabe,
#[prefix = "I"]
Isort,
#[prefix = "D"]
Pydocstyle,
#[prefix = "UP"]
Pyupgrade,
#[prefix = "N"]
PEP8Naming,
#[prefix = "YTT"]
Flake82020,
#[prefix = "ANN"]
Flake8Annotations,
#[prefix = "S"]
Flake8Bandit,
#[prefix = "BLE"]
Flake8BlindExcept,
#[prefix = "FBT"]
Flake8BooleanTrap,
#[prefix = "B"]
Flake8Bugbear,
#[prefix = "A"]
Flake8Builtins,
#[prefix = "C4"]
Flake8Comprehensions,
#[prefix = "T10"]
Flake8Debugger,
#[prefix = "EM"]
Flake8ErrMsg,
#[prefix = "ISC"]
Flake8ImplicitStrConcat,
#[prefix = "ICN"]
Flake8ImportConventions,
#[prefix = "T20"]
Flake8Print,
#[prefix = "PT"]
Flake8PytestStyle,
#[prefix = "Q"]
Flake8Quotes,
#[prefix = "RET"]
Flake8Return,
#[prefix = "SIM"]
Flake8Simplify,
#[prefix = "TID"]
Flake8TidyImports,
#[prefix = "ARG"]
Flake8UnusedArguments,
#[prefix = "DTZ"]
Flake8Datetimez,
#[prefix = "ERA"]
Eradicate,
#[prefix = "PD"]
PandasVet,
#[prefix = "PGH"]
PygrepHooks,
#[prefix = "PL"]
Pylint,
#[prefix = "PIE"]
Flake8Pie,
#[prefix = "COM"]
Flake8Commas,
#[prefix = "INP"]
Flake8NoPep420,
#[prefix = "EXE"]
Flake8Executable,
#[prefix = "TYP"]
Flake8TypeChecking,
#[prefix = "TRY"]
Tryceratops,
#[prefix = "RUF"]
Ruff,
}
pub trait ParseCode: Sized {
fn parse_code(code: &str) -> Option<(Self, &str)>;
}
pub enum Prefixes {
Single(RuleCodePrefix),
Multiple(Vec<(RuleCodePrefix, &'static str)>),
Single(RuleSelector),
Multiple(Vec<(RuleSelector, &'static str)>),
}
impl Prefixes {
@@ -481,54 +540,57 @@ impl Prefixes {
}
}
include!(concat!(env!("OUT_DIR"), "/origin.rs"));
include!(concat!(env!("OUT_DIR"), "/linter.rs"));
impl RuleOrigin {
impl Linter {
pub fn prefixes(&self) -> Prefixes {
match self {
RuleOrigin::Eradicate => Prefixes::Single(RuleCodePrefix::ERA),
RuleOrigin::Flake82020 => Prefixes::Single(RuleCodePrefix::YTT),
RuleOrigin::Flake8Annotations => Prefixes::Single(RuleCodePrefix::ANN),
RuleOrigin::Flake8Bandit => Prefixes::Single(RuleCodePrefix::S),
RuleOrigin::Flake8BlindExcept => Prefixes::Single(RuleCodePrefix::BLE),
RuleOrigin::Flake8BooleanTrap => Prefixes::Single(RuleCodePrefix::FBT),
RuleOrigin::Flake8Bugbear => Prefixes::Single(RuleCodePrefix::B),
RuleOrigin::Flake8Builtins => Prefixes::Single(RuleCodePrefix::A),
RuleOrigin::Flake8Comprehensions => Prefixes::Single(RuleCodePrefix::C4),
RuleOrigin::Flake8Datetimez => Prefixes::Single(RuleCodePrefix::DTZ),
RuleOrigin::Flake8Debugger => Prefixes::Single(RuleCodePrefix::T10),
RuleOrigin::Flake8ErrMsg => Prefixes::Single(RuleCodePrefix::EM),
RuleOrigin::Flake8ImplicitStrConcat => Prefixes::Single(RuleCodePrefix::ISC),
RuleOrigin::Flake8ImportConventions => Prefixes::Single(RuleCodePrefix::ICN),
RuleOrigin::Flake8Print => Prefixes::Single(RuleCodePrefix::T20),
RuleOrigin::Flake8PytestStyle => Prefixes::Single(RuleCodePrefix::PT),
RuleOrigin::Flake8Quotes => Prefixes::Single(RuleCodePrefix::Q),
RuleOrigin::Flake8Return => Prefixes::Single(RuleCodePrefix::RET),
RuleOrigin::Flake8Simplify => Prefixes::Single(RuleCodePrefix::SIM),
RuleOrigin::Flake8TidyImports => Prefixes::Single(RuleCodePrefix::TID),
RuleOrigin::Flake8UnusedArguments => Prefixes::Single(RuleCodePrefix::ARG),
RuleOrigin::Isort => Prefixes::Single(RuleCodePrefix::I),
RuleOrigin::McCabe => Prefixes::Single(RuleCodePrefix::C90),
RuleOrigin::PEP8Naming => Prefixes::Single(RuleCodePrefix::N),
RuleOrigin::PandasVet => Prefixes::Single(RuleCodePrefix::PD),
RuleOrigin::Pycodestyle => Prefixes::Multiple(vec![
(RuleCodePrefix::E, "Error"),
(RuleCodePrefix::W, "Warning"),
Linter::Eradicate => Prefixes::Single(RuleSelector::ERA),
Linter::Flake82020 => Prefixes::Single(RuleSelector::YTT),
Linter::Flake8Annotations => Prefixes::Single(RuleSelector::ANN),
Linter::Flake8Bandit => Prefixes::Single(RuleSelector::S),
Linter::Flake8BlindExcept => Prefixes::Single(RuleSelector::BLE),
Linter::Flake8BooleanTrap => Prefixes::Single(RuleSelector::FBT),
Linter::Flake8Bugbear => Prefixes::Single(RuleSelector::B),
Linter::Flake8Builtins => Prefixes::Single(RuleSelector::A),
Linter::Flake8Comprehensions => Prefixes::Single(RuleSelector::C4),
Linter::Flake8Datetimez => Prefixes::Single(RuleSelector::DTZ),
Linter::Flake8Debugger => Prefixes::Single(RuleSelector::T10),
Linter::Flake8ErrMsg => Prefixes::Single(RuleSelector::EM),
Linter::Flake8ImplicitStrConcat => Prefixes::Single(RuleSelector::ISC),
Linter::Flake8ImportConventions => Prefixes::Single(RuleSelector::ICN),
Linter::Flake8Print => Prefixes::Single(RuleSelector::T20),
Linter::Flake8PytestStyle => Prefixes::Single(RuleSelector::PT),
Linter::Flake8Quotes => Prefixes::Single(RuleSelector::Q),
Linter::Flake8Return => Prefixes::Single(RuleSelector::RET),
Linter::Flake8Simplify => Prefixes::Single(RuleSelector::SIM),
Linter::Flake8TidyImports => Prefixes::Single(RuleSelector::TID),
Linter::Flake8UnusedArguments => Prefixes::Single(RuleSelector::ARG),
Linter::Isort => Prefixes::Single(RuleSelector::I),
Linter::McCabe => Prefixes::Single(RuleSelector::C90),
Linter::PEP8Naming => Prefixes::Single(RuleSelector::N),
Linter::PandasVet => Prefixes::Single(RuleSelector::PD),
Linter::Pycodestyle => Prefixes::Multiple(vec![
(RuleSelector::E, "Error"),
(RuleSelector::W, "Warning"),
]),
RuleOrigin::Pydocstyle => Prefixes::Single(RuleCodePrefix::D),
RuleOrigin::Pyflakes => Prefixes::Single(RuleCodePrefix::F),
RuleOrigin::PygrepHooks => Prefixes::Single(RuleCodePrefix::PGH),
RuleOrigin::Pylint => Prefixes::Multiple(vec![
(RuleCodePrefix::PLC, "Convention"),
(RuleCodePrefix::PLE, "Error"),
(RuleCodePrefix::PLR, "Refactor"),
(RuleCodePrefix::PLW, "Warning"),
Linter::Pydocstyle => Prefixes::Single(RuleSelector::D),
Linter::Pyflakes => Prefixes::Single(RuleSelector::F),
Linter::PygrepHooks => Prefixes::Single(RuleSelector::PGH),
Linter::Pylint => Prefixes::Multiple(vec![
(RuleSelector::PLC, "Convention"),
(RuleSelector::PLE, "Error"),
(RuleSelector::PLR, "Refactor"),
(RuleSelector::PLW, "Warning"),
]),
RuleOrigin::Pyupgrade => Prefixes::Single(RuleCodePrefix::UP),
RuleOrigin::Flake8Pie => Prefixes::Single(RuleCodePrefix::PIE),
RuleOrigin::Flake8Commas => Prefixes::Single(RuleCodePrefix::COM),
RuleOrigin::Flake8NoPep420 => Prefixes::Single(RuleCodePrefix::INP),
RuleOrigin::Ruff => Prefixes::Single(RuleCodePrefix::RUF),
Linter::Pyupgrade => Prefixes::Single(RuleSelector::UP),
Linter::Flake8Pie => Prefixes::Single(RuleSelector::PIE),
Linter::Flake8Commas => Prefixes::Single(RuleSelector::COM),
Linter::Flake8NoPep420 => Prefixes::Single(RuleSelector::INP),
Linter::Flake8Executable => Prefixes::Single(RuleSelector::EXE),
Linter::Flake8TypeChecking => Prefixes::Single(RuleSelector::TYP),
Linter::Tryceratops => Prefixes::Single(RuleSelector::TRY),
Linter::Ruff => Prefixes::Single(RuleSelector::RUF),
}
}
}
@@ -549,26 +611,31 @@ impl Rule {
pub fn lint_source(&self) -> &'static LintSource {
match self {
Rule::UnusedNOQA => &LintSource::NoQa,
Rule::LineTooLong
| Rule::NoNewLineAtEndOfFile
| Rule::DocLineTooLong
| Rule::PEP3120UnnecessaryCodingComment
Rule::BlanketNOQA
| Rule::BlanketTypeIgnore
| Rule::BlanketNOQA => &LintSource::Lines,
Rule::CommentedOutCode
| Rule::SingleLineImplicitStringConcatenation
| Rule::MultiLineImplicitStringConcatenation
| Rule::DocLineTooLong
| Rule::LineTooLong
| Rule::MixedSpacesAndTabs
| Rule::NoNewLineAtEndOfFile
| Rule::PEP3120UnnecessaryCodingComment
| Rule::ShebangNewline
| Rule::ShebangPython
| Rule::ShebangWhitespace => &LintSource::Lines,
Rule::AmbiguousUnicodeCharacterComment
| Rule::AmbiguousUnicodeCharacterDocstring
| Rule::AmbiguousUnicodeCharacterString
| Rule::AvoidQuoteEscape
| Rule::BadQuotesDocstring
| Rule::BadQuotesInlineString
| Rule::BadQuotesMultilineString
| Rule::BadQuotesDocstring
| Rule::AvoidQuoteEscape
| Rule::CommentedOutCode
| Rule::ExtraneousParentheses
| Rule::InvalidEscapeSequence
| Rule::MultiLineImplicitStringConcatenation
| Rule::SingleLineImplicitStringConcatenation
| Rule::TrailingCommaMissing
| Rule::TrailingCommaOnBareTupleProhibited
| Rule::TrailingCommaProhibited
| Rule::AmbiguousUnicodeCharacterString
| Rule::AmbiguousUnicodeCharacterDocstring
| Rule::AmbiguousUnicodeCharacterComment => &LintSource::Tokens,
| Rule::TrailingCommaProhibited => &LintSource::Tokens,
Rule::IOError => &LintSource::Io,
Rule::UnsortedImports | Rule::MissingRequiredImport => &LintSource::Imports,
Rule::ImplicitNamespacePackage => &LintSource::Filesystem,
@@ -674,7 +741,7 @@ pub static CODE_REDIRECTS: Lazy<FxHashMap<&'static str, Rule>> = Lazy::new(|| {
mod tests {
use strum::IntoEnumIterator;
use crate::registry::Rule;
use super::{Linter, ParseCode, Rule};
#[test]
fn check_code_serialization() {
@@ -685,4 +752,12 @@ mod tests {
);
}
}
#[test]
fn test_linter_prefixes() {
for rule in Rule::iter() {
Linter::parse_code(rule.code())
.unwrap_or_else(|| panic!("couldn't parse {:?}", rule.code()));
}
}
}

View File

@@ -119,7 +119,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
.rules
.enabled(&Rule::DynamicallyTypedExpression)
{
let name = arg.node.arg.to_string();
let name = &arg.node.arg;
check_dynamically_typed(checker, expr, || format!("*{name}"));
}
}
@@ -146,7 +146,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
.rules
.enabled(&Rule::DynamicallyTypedExpression)
{
let name = arg.node.arg.to_string();
let name = &arg.node.arg;
check_dynamically_typed(checker, expr, || format!("**{name}"));
}
}
@@ -266,7 +266,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
.rules
.enabled(&Rule::DynamicallyTypedExpression)
{
let name = arg.node.arg.to_string();
let name = &arg.node.arg;
check_dynamically_typed(checker, expr, || format!("*{name}"));
}
}
@@ -294,7 +294,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
.rules
.enabled(&Rule::DynamicallyTypedExpression)
{
let name = arg.node.arg.to_string();
let name = &arg.node.arg;
check_dynamically_typed(checker, expr, || format!("**{name}"));
}
}

View File

@@ -6,5 +6,8 @@ use crate::violations;
/// S101
pub fn assert_used(stmt: &Located<StmtKind>) -> Diagnostic {
Diagnostic::new(violations::AssertUsed, Range::from_located(stmt))
Diagnostic::new(
violations::AssertUsed,
Range::new(stmt.location, stmt.location.with_col_offset("assert".len())),
)
}

View File

@@ -9,7 +9,7 @@ expression: diagnostics
column: 0
end_location:
row: 2
column: 11
column: 6
fix: ~
parent: ~
- kind:
@@ -19,7 +19,7 @@ expression: diagnostics
column: 4
end_location:
row: 8
column: 17
column: 10
fix: ~
parent: ~
- kind:
@@ -29,7 +29,7 @@ expression: diagnostics
column: 4
end_location:
row: 11
column: 17
column: 10
fix: ~
parent: ~

View File

@@ -3,7 +3,7 @@ use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::fix::Fix;
use crate::python::identifiers::IDENTIFIER_REGEX;
use crate::python::identifiers::is_identifier;
use crate::python::keyword::KWLIST;
use crate::registry::Diagnostic;
use crate::source_code::Generator;
@@ -38,7 +38,7 @@ pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
} = &arg.node else {
return;
};
if !IDENTIFIER_REGEX.is_match(value) {
if !is_identifier(value) {
return;
}
if KWLIST.contains(&value.as_str()) {

View File

@@ -3,7 +3,7 @@ use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location, Stmt, Stmt
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::fix::Fix;
use crate::python::identifiers::IDENTIFIER_REGEX;
use crate::python::identifiers::is_identifier;
use crate::python::keyword::KWLIST;
use crate::registry::Diagnostic;
use crate::source_code::{Generator, Stylist};
@@ -49,7 +49,7 @@ pub fn setattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
} = &name.node else {
return;
};
if !IDENTIFIER_REGEX.is_match(name) {
if !is_identifier(name) {
return;
}
if KWLIST.contains(&name.as_str()) {

View File

@@ -1,5 +1,6 @@
//! Rules from [flake8-builtins](https://pypi.org/project/flake8-builtins/2.0.1/).
pub(crate) mod rules;
pub mod settings;
pub(crate) mod types;
#[cfg(test)]
@@ -11,7 +12,7 @@ mod tests {
use crate::linter::test_path;
use crate::registry::Rule;
use crate::settings;
use crate::settings::Settings;
#[test_case(Rule::BuiltinVariableShadowing, Path::new("A001.py"); "A001")]
#[test_case(Rule::BuiltinArgumentShadowing, Path::new("A002.py"); "A002")]
@@ -22,9 +23,35 @@ mod tests {
Path::new("./resources/test/fixtures/flake8_builtins")
.join(path)
.as_path(),
&settings::Settings::for_rule(rule_code),
&Settings::for_rule(rule_code),
)?;
insta::assert_yaml_snapshot!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::BuiltinVariableShadowing, Path::new("A001.py"); "A001")]
#[test_case(Rule::BuiltinArgumentShadowing, Path::new("A002.py"); "A002")]
#[test_case(Rule::BuiltinAttributeShadowing, Path::new("A003.py"); "A003")]
fn builtins_ignorelist(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"{}_{}_builtins_ignorelist",
rule_code.code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("./resources/test/fixtures/flake8_builtins")
.join(path)
.as_path(),
&Settings {
flake8_builtins: super::settings::Settings {
builtins_ignorelist: vec!["id".to_string(), "dir".to_string()],
},
..Settings::for_rules(vec![rule_code])
},
)?;
insta::assert_yaml_snapshot!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -11,8 +11,9 @@ pub fn builtin_shadowing<T>(
name: &str,
located: &Located<T>,
node_type: ShadowingType,
ignorelist: &[String],
) -> Option<Diagnostic> {
if BUILTINS.contains(&name) {
if BUILTINS.contains(&name) && !ignorelist.contains(&name.to_string()) {
Some(Diagnostic::new::<DiagnosticKind>(
match node_type {
ShadowingType::Variable => {

View File

@@ -0,0 +1,44 @@
//! Settings for the `flake8-builtins` plugin.
use ruff_macros::ConfigurationOptions;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(
Debug, PartialEq, Eq, Default, Serialize, Deserialize, ConfigurationOptions, JsonSchema,
)]
#[serde(
deny_unknown_fields,
rename_all = "kebab-case",
rename = "Flake8BuiltinsOptions"
)]
pub struct Options {
#[option(
default = r#"[]"#,
value_type = "Vec<String>",
example = "builtins-ignorelist = [\"id\"]"
)]
/// Ignore list of builtins.
pub builtins_ignorelist: Option<Vec<String>>,
}
#[derive(Debug, Default, Hash)]
pub struct Settings {
pub builtins_ignorelist: Vec<String>,
}
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
builtins_ignorelist: options.builtins_ignorelist.unwrap_or_default(),
}
}
}
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
builtins_ignorelist: Some(settings.builtins_ignorelist),
}
}
}

View File

@@ -23,87 +23,97 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: print
BuiltinVariableShadowing: dir
location:
row: 4
row: 3
column: 0
end_location:
row: 4
row: 3
column: 32
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: print
location:
row: 5
column: 0
end_location:
row: 5
column: 5
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: copyright
location:
row: 5
row: 6
column: 0
end_location:
row: 5
row: 6
column: 9
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: complex
location:
row: 6
row: 7
column: 1
end_location:
row: 6
row: 7
column: 8
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: float
location:
row: 7
row: 8
column: 0
end_location:
row: 7
row: 8
column: 5
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: object
location:
row: 7
row: 8
column: 8
end_location:
row: 7
row: 8
column: 14
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: min
location:
row: 8
row: 9
column: 0
end_location:
row: 8
row: 9
column: 3
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: max
location:
row: 8
row: 9
column: 5
end_location:
row: 8
row: 9
column: 8
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: id
location:
row: 11
column: 0
end_location:
row: 11
column: 2
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: bytes
location:
row: 10
column: 0
end_location:
row: 11
column: 8
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: slice
location:
row: 13
column: 0
@@ -113,72 +123,82 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: ValueError
BuiltinVariableShadowing: slice
location:
row: 18
row: 16
column: 0
end_location:
row: 19
row: 17
column: 8
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: ValueError
location:
row: 21
column: 0
end_location:
row: 22
column: 7
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: memoryview
location:
row: 21
row: 24
column: 4
end_location:
row: 21
row: 24
column: 14
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: bytearray
location:
row: 21
row: 24
column: 17
end_location:
row: 21
row: 24
column: 26
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: str
location:
row: 24
row: 27
column: 21
end_location:
row: 24
row: 27
column: 24
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: all
location:
row: 24
row: 27
column: 44
end_location:
row: 24
row: 27
column: 47
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: any
location:
row: 24
row: 27
column: 49
end_location:
row: 24
row: 27
column: 52
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: sum
location:
row: 27
row: 30
column: 7
end_location:
row: 27
row: 30
column: 10
fix: ~
parent: ~

View File

@@ -0,0 +1,185 @@
---
source: src/rules/flake8_builtins/mod.rs
expression: diagnostics
---
- kind:
BuiltinVariableShadowing: sum
location:
row: 1
column: 0
end_location:
row: 1
column: 18
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: int
location:
row: 2
column: 0
end_location:
row: 2
column: 29
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: print
location:
row: 5
column: 0
end_location:
row: 5
column: 5
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: copyright
location:
row: 6
column: 0
end_location:
row: 6
column: 9
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: complex
location:
row: 7
column: 1
end_location:
row: 7
column: 8
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: float
location:
row: 8
column: 0
end_location:
row: 8
column: 5
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: object
location:
row: 8
column: 8
end_location:
row: 8
column: 14
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: min
location:
row: 9
column: 0
end_location:
row: 9
column: 3
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: max
location:
row: 9
column: 5
end_location:
row: 9
column: 8
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: bytes
location:
row: 13
column: 0
end_location:
row: 14
column: 8
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: slice
location:
row: 16
column: 0
end_location:
row: 17
column: 8
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: ValueError
location:
row: 21
column: 0
end_location:
row: 22
column: 7
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: memoryview
location:
row: 24
column: 4
end_location:
row: 24
column: 14
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: bytearray
location:
row: 24
column: 17
end_location:
row: 24
column: 26
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: str
location:
row: 27
column: 21
end_location:
row: 27
column: 24
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: all
location:
row: 27
column: 44
end_location:
row: 27
column: 47
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: any
location:
row: 27
column: 49
end_location:
row: 27
column: 52
fix: ~
parent: ~
- kind:
BuiltinVariableShadowing: sum
location:
row: 30
column: 7
end_location:
row: 30
column: 10
fix: ~
parent: ~

View File

@@ -62,13 +62,33 @@ expression: diagnostics
column: 21
fix: ~
parent: ~
- kind:
BuiltinArgumentShadowing: id
location:
row: 8
column: 16
end_location:
row: 8
column: 18
fix: ~
parent: ~
- kind:
BuiltinArgumentShadowing: dir
location:
row: 8
column: 20
end_location:
row: 8
column: 23
fix: ~
parent: ~
- kind:
BuiltinArgumentShadowing: float
location:
row: 9
row: 11
column: 15
end_location:
row: 9
row: 11
column: 20
fix: ~
parent: ~

View File

@@ -0,0 +1,75 @@
---
source: src/rules/flake8_builtins/mod.rs
expression: diagnostics
---
- kind:
BuiltinArgumentShadowing: str
location:
row: 1
column: 10
end_location:
row: 1
column: 13
fix: ~
parent: ~
- kind:
BuiltinArgumentShadowing: type
location:
row: 1
column: 18
end_location:
row: 1
column: 22
fix: ~
parent: ~
- kind:
BuiltinArgumentShadowing: complex
location:
row: 1
column: 25
end_location:
row: 1
column: 32
fix: ~
parent: ~
- kind:
BuiltinArgumentShadowing: Exception
location:
row: 1
column: 34
end_location:
row: 1
column: 43
fix: ~
parent: ~
- kind:
BuiltinArgumentShadowing: getattr
location:
row: 1
column: 47
end_location:
row: 1
column: 54
fix: ~
parent: ~
- kind:
BuiltinArgumentShadowing: bytes
location:
row: 5
column: 16
end_location:
row: 5
column: 21
fix: ~
parent: ~
- kind:
BuiltinArgumentShadowing: float
location:
row: 11
column: 15
end_location:
row: 11
column: 20
fix: ~
parent: ~

View File

@@ -13,12 +13,32 @@ expression: diagnostics
fix: ~
parent: ~
- kind:
BuiltinAttributeShadowing: str
BuiltinAttributeShadowing: id
location:
row: 7
row: 3
column: 4
end_location:
row: 8
row: 3
column: 6
fix: ~
parent: ~
- kind:
BuiltinAttributeShadowing: dir
location:
row: 4
column: 4
end_location:
row: 4
column: 7
fix: ~
parent: ~
- kind:
BuiltinAttributeShadowing: str
location:
row: 11
column: 4
end_location:
row: 12
column: 12
fix: ~
parent: ~

View File

@@ -0,0 +1,25 @@
---
source: src/rules/flake8_builtins/mod.rs
expression: diagnostics
---
- kind:
BuiltinAttributeShadowing: ImportError
location:
row: 2
column: 4
end_location:
row: 2
column: 15
fix: ~
parent: ~
- kind:
BuiltinAttributeShadowing: str
location:
row: 11
column: 4
end_location:
row: 12
column: 12
fix: ~
parent: ~

View File

@@ -0,0 +1,73 @@
use once_cell::sync::Lazy;
use regex::Regex;
static SHEBANG_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?P<spaces>\s*)#!(?P<directive>.*)").unwrap());
#[derive(Debug)]
pub enum ShebangDirective<'a> {
None,
// whitespace length, start of shebang, end, shebang contents
Match(usize, usize, usize, &'a str),
}
pub fn extract_shebang(line: &str) -> ShebangDirective {
// Minor optimization to avoid matches in the common case.
if !line.contains('!') {
return ShebangDirective::None;
}
match SHEBANG_REGEX.captures(line) {
Some(caps) => match caps.name("spaces") {
Some(spaces) => match caps.name("directive") {
Some(matches) => ShebangDirective::Match(
spaces.as_str().chars().count(),
matches.start(),
matches.end(),
matches.as_str(),
),
None => ShebangDirective::None,
},
None => ShebangDirective::None,
},
None => ShebangDirective::None,
}
}
#[cfg(test)]
mod tests {
use crate::rules::flake8_executable::helpers::{
extract_shebang, ShebangDirective, SHEBANG_REGEX,
};
#[test]
fn shebang_regex() {
// Positive cases
assert!(SHEBANG_REGEX.is_match("#!/usr/bin/python"));
assert!(SHEBANG_REGEX.is_match("#!/usr/bin/env python"));
assert!(SHEBANG_REGEX.is_match(" #!/usr/bin/env python"));
assert!(SHEBANG_REGEX.is_match(" #!/usr/bin/env python"));
// Negative cases
assert!(!SHEBANG_REGEX.is_match("hello world"));
}
#[test]
fn shebang_extract_match() {
assert!(matches!(
extract_shebang("not a match"),
ShebangDirective::None
));
assert!(matches!(
extract_shebang("#!/usr/bin/env python"),
ShebangDirective::Match(0, 2, 21, "/usr/bin/env python")
));
assert!(matches!(
extract_shebang(" #!/usr/bin/env python"),
ShebangDirective::Match(2, 4, 23, "/usr/bin/env python")
));
assert!(matches!(
extract_shebang("print('test') #!/usr/bin/python"),
ShebangDirective::Match(2, 17, 32, "/usr/bin/python")
));
}
}

View File

@@ -0,0 +1,38 @@
//! Rules from [flake8-executable](https://pypi.org/project/flake8-executable/2.1.1/).
pub(crate) mod helpers;
pub(crate) mod rules;
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::linter::test_path;
use crate::registry::Rule;
use crate::settings;
#[test_case(Path::new("EXE003.py"); "EXE003")]
#[test_case(Path::new("EXE004_1.py"); "EXE004_1")]
#[test_case(Path::new("EXE004_2.py"); "EXE004_2")]
#[test_case(Path::new("EXE004_3.py"); "EXE004_3")]
#[test_case(Path::new("EXE005_1.py"); "EXE005_1")]
#[test_case(Path::new("EXE005_2.py"); "EXE005_2")]
#[test_case(Path::new("EXE005_3.py"); "EXE005_3")]
fn rules(path: &Path) -> Result<()> {
let snapshot = path.to_string_lossy().into_owned();
let diagnostics = test_path(
Path::new("./resources/test/fixtures/flake8_executable")
.join(path)
.as_path(),
&settings::Settings::for_rules(vec![
Rule::ShebangWhitespace,
Rule::ShebangNewline,
Rule::ShebangPython,
]),
)?;
insta::assert_yaml_snapshot!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -0,0 +1,7 @@
pub use shebang_newline::{shebang_newline, ShebangNewline};
pub use shebang_python::{shebang_python, ShebangPython};
pub use shebang_whitespace::{shebang_whitespace, ShebangWhitespace};
mod shebang_newline;
mod shebang_python;
mod shebang_whitespace;

View File

@@ -0,0 +1,38 @@
use ruff_macros::derive_message_formats;
use rustpython_ast::Location;
use crate::ast::types::Range;
use crate::define_violation;
use crate::registry::Diagnostic;
use crate::rules::flake8_executable::helpers::ShebangDirective;
use crate::violation::Violation;
define_violation!(
pub struct ShebangNewline;
);
impl Violation for ShebangNewline {
#[derive_message_formats]
fn message(&self) -> String {
format!("Shebang should be at the beginning of the file")
}
}
/// EXE005
pub fn shebang_newline(lineno: usize, shebang: &ShebangDirective) -> Option<Diagnostic> {
if let ShebangDirective::Match(_, start, end, _) = shebang {
if lineno > 1 {
let diagnostic = Diagnostic::new(
ShebangNewline,
Range::new(
Location::new(lineno + 1, *start),
Location::new(lineno + 1, *end),
),
);
Some(diagnostic)
} else {
None
}
} else {
None
}
}

View File

@@ -0,0 +1,39 @@
use ruff_macros::derive_message_formats;
use rustpython_ast::Location;
use crate::ast::types::Range;
use crate::define_violation;
use crate::registry::Diagnostic;
use crate::rules::flake8_executable::helpers::ShebangDirective;
use crate::violation::Violation;
define_violation!(
pub struct ShebangPython;
);
impl Violation for ShebangPython {
#[derive_message_formats]
fn message(&self) -> String {
format!("Shebang should contain \"python\"")
}
}
/// EXE003
pub fn shebang_python(lineno: usize, shebang: &ShebangDirective) -> Option<Diagnostic> {
if let ShebangDirective::Match(_, start, end, content) = shebang {
if content.contains("python") {
None
} else {
let diagnostic = Diagnostic::new(
ShebangPython,
Range::new(
Location::new(lineno + 1, start - 2),
Location::new(lineno + 1, *end),
),
);
Some(diagnostic)
}
} else {
None
}
}

View File

@@ -0,0 +1,53 @@
use ruff_macros::derive_message_formats;
use rustpython_ast::Location;
use crate::ast::types::Range;
use crate::define_violation;
use crate::fix::Fix;
use crate::registry::Diagnostic;
use crate::rules::flake8_executable::helpers::ShebangDirective;
use crate::violation::AlwaysAutofixableViolation;
define_violation!(
pub struct ShebangWhitespace;
);
impl AlwaysAutofixableViolation for ShebangWhitespace {
#[derive_message_formats]
fn message(&self) -> String {
format!("Avoid whitespace before shebang")
}
fn autofix_title(&self) -> String {
format!("Remove whitespace before shebang")
}
}
/// EXE004
pub fn shebang_whitespace(
lineno: usize,
shebang: &ShebangDirective,
autofix: bool,
) -> Option<Diagnostic> {
if let ShebangDirective::Match(n_spaces, start, ..) = shebang {
if *n_spaces > 0 && *start == n_spaces + 2 {
let mut diagnostic = Diagnostic::new(
ShebangWhitespace,
Range::new(
Location::new(lineno + 1, 0),
Location::new(lineno + 1, *n_spaces),
),
);
if autofix {
diagnostic.amend(Fix::deletion(
Location::new(lineno + 1, 0),
Location::new(lineno + 1, *n_spaces),
));
}
Some(diagnostic)
} else {
None
}
} else {
None
}
}

View File

@@ -0,0 +1,15 @@
---
source: src/rules/flake8_executable/mod.rs
expression: diagnostics
---
- kind:
ShebangPython: ~
location:
row: 1
column: 0
end_location:
row: 1
column: 15
fix: ~
parent: ~

View File

@@ -0,0 +1,21 @@
---
source: src/rules/flake8_executable/mod.rs
expression: diagnostics
---
- kind:
ShebangWhitespace: ~
location:
row: 1
column: 0
end_location:
row: 1
column: 4
fix:
content: ""
location:
row: 1
column: 0
end_location:
row: 1
column: 4
parent: ~

View File

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

View File

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

View File

@@ -0,0 +1,15 @@
---
source: src/rules/flake8_executable/mod.rs
expression: diagnostics
---
- kind:
ShebangNewline: ~
location:
row: 3
column: 2
end_location:
row: 3
column: 17
fix: ~
parent: ~

View File

@@ -0,0 +1,15 @@
---
source: src/rules/flake8_executable/mod.rs
expression: diagnostics
---
- kind:
ShebangNewline: ~
location:
row: 4
column: 2
end_location:
row: 4
column: 17
fix: ~
parent: ~

View File

@@ -0,0 +1,15 @@
---
source: src/rules/flake8_executable/mod.rs
expression: diagnostics
---
- kind:
ShebangNewline: ~
location:
row: 6
column: 2
end_location:
row: 6
column: 17
fix: ~
parent: ~

View File

@@ -217,10 +217,10 @@ fn check_fixture_returns(checker: &mut Checker, func: &Stmt, func_name: &str, bo
/// PT019
fn check_test_function_args(checker: &mut Checker, args: &Arguments) {
args.args.iter().chain(&args.kwonlyargs).for_each(|arg| {
let name = arg.node.arg.to_string();
let name = &arg.node.arg;
if name.starts_with('_') {
checker.diagnostics.push(Diagnostic::new(
violations::FixtureParamWithoutValue(name),
violations::FixtureParamWithoutValue(name.to_string()),
Range::from_located(arg),
));
}

View File

@@ -3,8 +3,8 @@ use rustpython_ast::{Cmpop, Constant, Expr, ExprContext, ExprKind, Stmt, StmtKin
use crate::ast::comparable::ComparableExpr;
use crate::ast::helpers::{
contains_call_path, contains_effect, create_expr, create_stmt, has_comments_in, unparse_expr,
unparse_stmt,
contains_call_path, contains_effect, create_expr, create_stmt, first_colon_range,
has_comments_in, unparse_expr, unparse_stmt,
};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
@@ -37,36 +37,64 @@ fn is_main_check(expr: &Expr) -> bool {
false
}
/// Find the last nested if statement and return the test expression and the
/// first statement.
///
/// ```python
/// if xxx:
/// if yyy:
/// # ^^^ returns this expression
/// z = 1
/// # ^^^^^ and this statement
/// ...
/// ```
fn find_last_nested_if(body: &[Stmt]) -> Option<(&Expr, &Stmt)> {
let [Stmt { node: StmtKind::If { test, body: inner_body, orelse }, ..}] = body else { return None };
if !(orelse.is_empty() && body.len() == 1) {
return None;
}
find_last_nested_if(inner_body).or_else(|| {
Some((
test,
inner_body.last().expect("Expected body to be non-empty"),
))
})
}
/// SIM102
pub fn nested_if_statements(checker: &mut Checker, stmt: &Stmt) {
let StmtKind::If { test, body, orelse } = &stmt.node else {
return;
};
// if a: <---
// if b: <---
// c
let is_nested_if = {
if orelse.is_empty() && body.len() == 1 {
if let StmtKind::If { orelse, .. } = &body[0].node {
orelse.is_empty()
} else {
false
}
} else {
false
}
};
if !is_nested_if {
return;
};
pub fn nested_if_statements(
checker: &mut Checker,
stmt: &Stmt,
test: &Expr,
body: &[Stmt],
parent: Option<&Stmt>,
) {
if is_main_check(test) {
return;
}
let mut diagnostic = Diagnostic::new(violations::NestedIfStatements, Range::from_located(stmt));
if let Some(parent) = parent {
if let StmtKind::If { body, orelse, .. } = &parent.node {
if orelse.is_empty() && body.len() == 1 {
return;
}
}
}
let Some((test, first_stmt)) = find_last_nested_if(body) else {
return;
};
let colon = first_colon_range(
Range::new(test.end_location.unwrap(), first_stmt.location),
checker.locator,
);
let mut diagnostic = Diagnostic::new(
violations::NestedIfStatements,
colon.map_or_else(
|| Range::from_located(stmt),
|colon| Range::new(stmt.location, colon.end_location),
),
);
if checker.patch(&Rule::NestedIfStatements) {
// The fixer preserves comments in the nested body, but removes comments between
// the outer and inner if statements.
@@ -75,7 +103,7 @@ pub fn nested_if_statements(checker: &mut Checker, stmt: &Stmt) {
Range::new(stmt.location, nested_if.location),
checker.locator,
) {
match fix_if::fix_nested_if_statements(checker.locator, stmt) {
match fix_if::fix_nested_if_statements(checker.locator, checker.stylist, stmt) {
Ok(fix) => {
if fix
.content
@@ -92,17 +120,35 @@ pub fn nested_if_statements(checker: &mut Checker, stmt: &Stmt) {
checker.diagnostics.push(diagnostic);
}
fn is_one_line_return_bool(stmts: &[Stmt]) -> bool {
enum Bool {
True,
False,
}
impl From<bool> for Bool {
fn from(value: bool) -> Self {
if value {
Bool::True
} else {
Bool::False
}
}
}
fn is_one_line_return_bool(stmts: &[Stmt]) -> Option<Bool> {
if stmts.len() != 1 {
return false;
return None;
}
let StmtKind::Return { value } = &stmts[0].node else {
return false;
return None;
};
let Some(ExprKind::Constant { value, .. }) = value.as_ref().map(|value| &value.node) else {
return false;
return None;
};
matches!(value, Constant::Bool(_))
let Constant::Bool(value) = value else {
return None;
};
Some((*value).into())
}
/// SIM103
@@ -110,17 +156,18 @@ pub fn return_bool_condition_directly(checker: &mut Checker, stmt: &Stmt) {
let StmtKind::If { test, body, orelse } = &stmt.node else {
return;
};
if !(is_one_line_return_bool(body) && is_one_line_return_bool(orelse)) {
let (Some(if_return), Some(else_return)) = (is_one_line_return_bool(body), is_one_line_return_bool(orelse)) else {
return;
}
};
let condition = unparse_expr(test, checker.stylist);
let mut diagnostic = Diagnostic::new(
violations::ReturnBoolConditionDirectly(condition),
Range::from_located(stmt),
);
if checker.patch(&Rule::ReturnBoolConditionDirectly)
&& !(has_comments_in(Range::from_located(stmt), checker.locator)
|| has_comments_in(Range::from_located(&orelse[0]), checker.locator))
&& matches!(if_return, Bool::True)
&& matches!(else_return, Bool::False)
&& !has_comments_in(Range::from_located(stmt), checker.locator)
{
let return_stmt = create_stmt(StmtKind::Return {
value: Some(test.clone()),

View File

@@ -12,7 +12,7 @@ use crate::ast::types::Range;
use crate::ast::whitespace;
use crate::cst::matchers::match_module;
use crate::fix::Fix;
use crate::source_code::Locator;
use crate::source_code::{Locator, Stylist};
fn parenthesize_and_operand(expr: Expression) -> Expression {
match &expr {
@@ -32,6 +32,7 @@ fn parenthesize_and_operand(expr: Expression) -> Expression {
/// (SIM102) Convert `if a: if b:` to `if a and b:`.
pub(crate) fn fix_nested_if_statements(
locator: &Locator,
stylist: &Stylist,
stmt: &rustpython_ast::Stmt,
) -> Result<Fix> {
// Infer the indentation of the outer block.
@@ -62,7 +63,10 @@ pub(crate) fn fix_nested_if_statements(
let module_text = if outer_indent.is_empty() {
module_text
} else {
Cow::Owned(format!("def f():\n{module_text}"))
Cow::Owned(format!(
"def f():{}{module_text}",
stylist.line_ending().as_str()
))
};
// Parse the CST.
@@ -113,7 +117,10 @@ pub(crate) fn fix_nested_if_statements(
}));
outer_if.body = inner_if.body.clone();
let mut state = CodegenState::default();
let mut state = CodegenState {
default_newline: stylist.line_ending(),
..Default::default()
};
tree.codegen(&mut state);
// Reconstruct and reformat the code.
@@ -121,7 +128,9 @@ pub(crate) fn fix_nested_if_statements(
let module_text = if outer_indent.is_empty() {
&module_text
} else {
module_text.strip_prefix("def f():\n").unwrap()
module_text
.strip_prefix(&format!("def f():{}", stylist.line_ending().as_str()))
.unwrap()
};
let contents = if is_elif {
module_text.replacen("if", "elif", 1)

View File

@@ -8,7 +8,7 @@ expression: diagnostics
row: 2
column: 0
end_location:
row: 4
row: 3
column: 9
fix:
content: "if a and b:\n c\n"
@@ -22,29 +22,36 @@ expression: diagnostics
- kind:
NestedIfStatements: ~
location:
row: 9
row: 7
column: 0
end_location:
row: 11
column: 9
row: 9
column: 13
fix:
content: "elif b and c:\n d\n"
content: "if a and b:\n if c:\n d\n"
location:
row: 9
row: 7
column: 0
end_location:
row: 12
row: 11
column: 0
parent: ~
- kind:
NestedIfStatements: ~
location:
row: 14
row: 15
column: 0
end_location:
row: 17
row: 16
column: 9
fix: ~
fix:
content: "elif b and c:\n d\n"
location:
row: 15
column: 0
end_location:
row: 18
column: 0
parent: ~
- kind:
NestedIfStatements: ~
@@ -52,83 +59,93 @@ expression: diagnostics
row: 20
column: 0
end_location:
row: 23
row: 22
column: 9
fix: ~
parent: ~
- kind:
NestedIfStatements: ~
location:
row: 26
column: 0
end_location:
row: 27
column: 9
fix:
content: "if a and b:\n # Fixable due to placement of this comment.\n c\n"
location:
row: 20
row: 26
column: 0
end_location:
row: 24
row: 30
column: 0
parent: ~
- kind:
NestedIfStatements: ~
location:
row: 45
row: 51
column: 4
end_location:
row: 57
column: 23
row: 52
column: 16
fix:
content: " if True and True:\n \"\"\"this\nis valid\"\"\"\n\n \"\"\"the indentation on\n this line is significant\"\"\"\n\n \"this is\" \\\n\"allowed too\"\n\n (\"so is\"\n\"this for some reason\")\n"
location:
row: 45
row: 51
column: 0
end_location:
row: 58
row: 64
column: 0
parent: ~
- kind:
NestedIfStatements: ~
location:
row: 61
row: 67
column: 0
end_location:
row: 73
column: 23
row: 68
column: 12
fix:
content: "if True and True:\n \"\"\"this\nis valid\"\"\"\n\n \"\"\"the indentation on\n this line is significant\"\"\"\n\n \"this is\" \\\n\"allowed too\"\n\n (\"so is\"\n\"this for some reason\")\n"
location:
row: 61
row: 67
column: 0
end_location:
row: 74
row: 80
column: 0
parent: ~
- kind:
NestedIfStatements: ~
location:
row: 77
row: 83
column: 4
end_location:
row: 81
column: 32
row: 86
column: 10
fix:
content: " if node.module and (node.module == \"multiprocessing\" or node.module.startswith(\n \"multiprocessing.\"\n )):\n print(\"Bad module!\")\n"
location:
row: 77
row: 83
column: 0
end_location:
row: 82
row: 88
column: 0
parent: ~
- kind:
NestedIfStatements: ~
location:
row: 84
row: 90
column: 0
end_location:
row: 88
column: 28
row: 93
column: 6
fix:
content: "if node.module and (node.module == \"multiprocessing\" or node.module.startswith(\n \"multiprocessing.\"\n)):\n print(\"Bad module!\")\n"
location:
row: 84
row: 90
column: 0
end_location:
row: 89
row: 95
column: 0
parent: ~

View File

@@ -53,4 +53,14 @@ expression: diagnostics
row: 27
column: 24
parent: ~
- kind:
ReturnBoolConditionDirectly: a
location:
row: 49
column: 4
end_location:
row: 52
column: 19
fix: ~
parent: ~

View File

@@ -0,0 +1,28 @@
//! Rules from [flake8-type-checking](https://pypi.org/project/flake8-type-checking/2.3.0/).
pub(crate) mod rules;
#[cfg(test)]
mod tests {
use std::convert::AsRef;
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::linter::test_path;
use crate::registry::Rule;
use crate::settings;
#[test_case(Rule::EmptyTypeCheckingBlock, Path::new("TYP005.py"); "TYP005")]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy());
let diagnostics = test_path(
Path::new("./resources/test/fixtures/flake8_type_checking")
.join(path)
.as_path(),
&settings::Settings::for_rule(rule_code),
)?;
insta::assert_yaml_snapshot!(snapshot, diagnostics);
Ok(())
}
}

View File

@@ -0,0 +1,32 @@
use ruff_macros::derive_message_formats;
use rustpython_ast::{Expr, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::registry::Diagnostic;
use crate::violation::Violation;
define_violation!(
pub struct EmptyTypeCheckingBlock;
);
impl Violation for EmptyTypeCheckingBlock {
#[derive_message_formats]
fn message(&self) -> String {
format!("Found empty type-checking block")
}
}
/// TYP005
pub fn empty_type_checking_block(checker: &mut Checker, test: &Expr, body: &[Stmt]) {
if checker.resolve_call_path(test).map_or(false, |call_path| {
call_path.as_slice() == ["typing", "TYPE_CHECKING"]
}) {
if body.len() == 1 && matches!(body[0].node, StmtKind::Pass) {
checker.diagnostics.push(Diagnostic::new(
EmptyTypeCheckingBlock,
Range::from_located(&body[0]),
));
}
}
}

View File

@@ -0,0 +1,3 @@
pub use empty_type_checking_block::{empty_type_checking_block, EmptyTypeCheckingBlock};
mod empty_type_checking_block;

View File

@@ -0,0 +1,35 @@
---
source: src/rules/flake8_type_checking/mod.rs
expression: diagnostics
---
- kind:
EmptyTypeCheckingBlock: ~
location:
row: 4
column: 4
end_location:
row: 4
column: 8
fix: ~
parent: ~
- kind:
EmptyTypeCheckingBlock: ~
location:
row: 9
column: 8
end_location:
row: 9
column: 12
fix: ~
parent: ~
- kind:
EmptyTypeCheckingBlock: ~
location:
row: 15
column: 8
end_location:
row: 15
column: 12
fix: ~
parent: ~

View File

@@ -12,6 +12,7 @@ pub mod flake8_comprehensions;
pub mod flake8_datetimez;
pub mod flake8_debugger;
pub mod flake8_errmsg;
pub mod flake8_executable;
pub mod flake8_implicit_str_concat;
pub mod flake8_import_conventions;
pub mod flake8_no_pep420;
@@ -22,6 +23,7 @@ pub mod flake8_quotes;
pub mod flake8_return;
pub mod flake8_simplify;
pub mod flake8_tidy_imports;
pub mod flake8_type_checking;
pub mod flake8_unused_arguments;
pub mod isort;
pub mod mccabe;
@@ -34,3 +36,4 @@ pub mod pygrep_hooks;
pub mod pylint;
pub mod pyupgrade;
pub mod ruff;
pub mod tryceratops;

View File

@@ -12,14 +12,14 @@ mod tests {
use textwrap::dedent;
use crate::linter::check_path;
use crate::registry::{Rule, RuleCodePrefix};
use crate::registry::{Rule, RuleSelector};
use crate::settings::flags;
use crate::source_code::{Indexer, Locator, Stylist};
use crate::{directives, rustpython_helpers, settings};
fn rule_code(contents: &str, expected: &[Rule]) -> Result<()> {
let contents = dedent(contents);
let settings = settings::Settings::for_rules(RuleCodePrefix::PD.codes());
let settings = settings::Settings::for_rules(RuleSelector::PD.codes());
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
let locator = Locator::new(&contents);
let stylist = Stylist::from_contents(&contents, &locator);

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