Compare commits

...

108 Commits

Author SHA1 Message Date
Charlie Marsh
2fe22a223b Bump version to 0.0.147 2022-11-29 20:17:58 -05:00
Charlie Marsh
e762dec677 Add one more note to README 2022-11-29 20:17:46 -05:00
Charlie Marsh
19baa50003 Remove extraneous key in pyproject.toml 2022-11-29 20:13:28 -05:00
Charlie Marsh
ab0df03a05 Fix pyproject tests to include pyupgrade 2022-11-29 20:11:16 -05:00
Charlie Marsh
808b348c5f Add W to pycodestyle list in README 2022-11-29 20:09:07 -05:00
Charlie Marsh
e55daa89e6 Uses dashes for README options (#966) 2022-11-29 20:08:03 -05:00
Charlie Marsh
b8e7d86696 Add pyupgrade's --keep-runtime-typing option (#965) 2022-11-29 20:05:32 -05:00
Charlie Marsh
ced7868559 Add format setting to pyproject.toml (#964) 2022-11-29 19:22:23 -05:00
Ramazan Elsunakev
7c344e8e4c feat: use more precise ranges for imports (#958) 2022-11-29 19:01:39 -05:00
Hayden
ca38c7ac48 Grouped format implementation (#954) 2022-11-29 18:45:16 -05:00
Guillaume Andreu Sabater
602291c0c2 README: fixed conf section typo (#959) 2022-11-29 09:27:02 -05:00
Charlie Marsh
d4cf376e9b Fix failing pyproject test 2022-11-29 00:00:43 -05:00
Charlie Marsh
0e6a38e6d8 Bump version to 0.0.146 2022-11-28 22:27:41 -05:00
Charlie Marsh
058fd8748d Re-generate check code prefix and rules table 2022-11-28 22:26:32 -05:00
Charlie Marsh
e8247e3ed9 Run cargo fmt 2022-11-28 22:25:09 -05:00
Charlie Marsh
ea73c717be Remove pre-commit note in README.md (#956) 2022-11-28 22:18:59 -05:00
Charlie Marsh
427e0c3158 Allow preservation of external check codes (#955) 2022-11-28 22:16:17 -05:00
Charlie Marsh
dca994d05f Bump version to 0.0.145 2022-11-28 20:57:58 -05:00
Charlie Marsh
9944246f98 Rewrite type annotations on Python 3.7 when __future__ enabled (#953) 2022-11-28 20:57:38 -05:00
Charlie Marsh
82b0b7941a Implement eradicate (#947) 2022-11-28 20:54:33 -05:00
Charlie Marsh
72453695d6 Bump version to 0.0.144 2022-11-28 20:11:08 -05:00
Charlie Marsh
1617d715f2 Allow long lines that consist of only a URL (#952) 2022-11-28 20:10:21 -05:00
pwoolvett
c4a7344791 fix(flake8_boolean_trap): add allowlist for dict methods (#943) 2022-11-28 16:17:01 -05:00
Charlie Marsh
ea9acda732 Bump version to 0.0.143 2022-11-28 15:42:25 -05:00
Anders Kaseorg
6c8021e970 Fix clippy::manual_let_else (pedantic) (#939) 2022-11-28 09:52:59 -05:00
Charlie Marsh
61b6ad46ea Allow @override methods to be undocumented (#941) 2022-11-28 09:52:12 -05:00
Anders Kaseorg
041d8108e6 Don’t require files with --explain or --generate-shell-completion (#937) 2022-11-28 00:40:20 -05:00
Charlie Marsh
e2c4a098de Bump version to 0.0.142 2022-11-28 00:19:27 -05:00
Charlie Marsh
e865f58426 Add all plugin options to README reference (#936) 2022-11-28 00:19:14 -05:00
messense
23b4e16b1d Add shell completions support (#935) 2022-11-27 23:59:36 -05:00
Charlie Marsh
ae2ac905dc Document all top-level configuration options (#934) 2022-11-27 23:50:24 -05:00
Charlie Marsh
55619b321a Run cargo fmt 2022-11-27 22:58:42 -05:00
Harutaka Kawamura
6f31b002f8 Do not enforce line length limit for comments ending with a URL (#920) 2022-11-27 22:36:17 -05:00
Charlie Marsh
1a79965aa0 Allow varargs and kwargs to be prefixed with stars (#933) 2022-11-27 22:08:27 -05:00
Charlie Marsh
16da183f8e Add some user testimonials (#932) 2022-11-27 21:55:01 -05:00
Charlie Marsh
3f689917cb Use alternative TOML format for per-file-ignores in README (#931) 2022-11-27 21:38:43 -05:00
Charlie Marsh
a4a215e8a3 Add Homebrew installation to README (#930) 2022-11-27 21:37:34 -05:00
Charlie Marsh
aa1c884910 Tweak Flake8 parity in README 2022-11-27 21:34:47 -05:00
Oliver Margetts
7fb55c6d99 F50x implementation (#919) 2022-11-27 21:30:55 -05:00
Charlie Marsh
04ea523ad8 Track aliased import-from members (#929) 2022-11-27 17:27:27 -05:00
Charlie Marsh
9897f81cf3 Bump version to 0.0.141 2022-11-26 16:33:08 -05:00
Charlie Marsh
1a2559b001 Avoid flagging redundant open modes when open is rebound (#918) 2022-11-26 16:24:41 -05:00
Denis Gavrilyuk
721a1e9443 Add flake8-debugger (#909) 2022-11-26 16:21:03 -05:00
Charlie Marsh
f38bba18ee Fix clippy warnings 2022-11-26 15:56:33 -05:00
Charlie Marsh
14cf36f922 Bump version to 0.0.140 2022-11-26 15:05:46 -05:00
Charlie Marsh
d28e026525 Preserve existing noqa codes in --add-noqa (#913) 2022-11-26 14:42:19 -05:00
Jonathan Plasse
d19a8aa54d Auto-generate CheckCodePrefix::fixables() (#916) 2022-11-26 14:10:30 -05:00
Charlie Marsh
f299940452 Respect noqa comments in U009 (#917) 2022-11-26 14:03:18 -05:00
Charlie Marsh
e1ab7163ac Respect f-string locations in B023 check (#914) 2022-11-26 10:31:23 -05:00
Jonathan Plasse
9edc479c6c Fix F821 false positive (#911) 2022-11-26 10:12:07 -05:00
Charlie Marsh
560558b814 Bump version to 0.0.139 2022-11-25 18:38:26 -05:00
Andri Bergsson
bef601b994 Add keyword argument handling for redundant open modes. (#906) 2022-11-25 18:38:05 -05:00
Charlie Marsh
7445d00b88 Implement B023 (function uses loop variable) (#907) 2022-11-25 18:29:54 -05:00
Charlie Marsh
7c78d4e103 Use docstring comment for CheckCode 2022-11-25 13:15:43 -05:00
Oliver Margetts
8b14f1b8cc Implement F522-F525 (#899) 2022-11-25 13:14:31 -05:00
Xuan (Sean) Hu
5a6b51e623 Minor changes in README. (#903) 2022-11-25 09:49:16 -05:00
Charlie Marsh
0b60242fb7 Bump version to 0.0.138 2022-11-25 00:05:41 -05:00
Charlie Marsh
65b77feeb8 Bump LibCST version 2022-11-25 00:05:03 -05:00
Charlie Marsh
04b9c0a31d Fix cargo clippy 2022-11-24 23:40:43 -05:00
Harutaka Kawamura
49dc8231be Fix typo (#902) 2022-11-24 23:38:45 -05:00
Charlie Marsh
92ca114882 Move some main.rs subcommands to a new module (#901) 2022-11-24 22:43:43 -05:00
Charlie Marsh
553bc7443a Remove UserConfiguration struct (#900) 2022-11-24 22:39:07 -05:00
CelebrateVC
a3af6c1ea5 Implement GlobSet optimization for file path exclusions (#883) 2022-11-24 22:31:55 -05:00
Charlie Marsh
b50016fe89 Regenerate README.md 2022-11-24 18:10:07 -05:00
Oliver Margetts
2cf2805848 Implement F521 (#898) 2022-11-24 18:09:36 -05:00
Harutaka Kawamura
33fbef7700 Implement B904 (#892) 2022-11-24 09:49:57 -05:00
Charlie Marsh
68668a584b Bump version to 0.0.137 2022-11-23 20:28:45 -05:00
Charlie Marsh
6cd8655d29 Treat withitem variables as bindings (#897) 2022-11-23 20:28:37 -05:00
Charlie Marsh
72a9bd3cfb Revert "Upload wheels back to GitHub Releases (#884)"
This reverts commit bd08fc359d.
2022-11-23 20:27:33 -05:00
Charlie Marsh
58aac21a36 Bump version to 0.0.136 2022-11-23 17:41:17 -05:00
Charlie Marsh
77e0be3464 Visit iter prior to target in comprehensions (#895) 2022-11-23 10:13:21 -05:00
Charlie Marsh
bd08fc359d Upload wheels back to GitHub Releases (#884) 2022-11-23 00:06:36 -05:00
Harutaka Kawamura
19ad6ab4f5 Add --explain (#887) 2022-11-23 00:06:25 -05:00
Charlie Marsh
4b2df99e78 Set rust-version in Cargo.toml (#886) 2022-11-22 23:33:39 -05:00
Charlie Marsh
66975876b2 Bump version to 0.0.135 2022-11-22 19:21:53 -05:00
Charlie Marsh
7316b120ba Log errors in add_noqa and autoformat calls (#881) 2022-11-22 19:21:36 -05:00
Charlie Marsh
fec887e481 Apply a limit to the number of fix iterations (#882) 2022-11-22 19:21:31 -05:00
Charlie Marsh
bdd32c0850 Enforce most pedantic lints on CI (#878) 2022-11-22 18:55:57 -05:00
Charlie Marsh
10dcd5fd0a Remove unused imports 2022-11-22 18:45:24 -05:00
Charlie Marsh
b922e6ecc8 Fix clippy::unnecessary_wraps (pedantic) (#880)
https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_wraps
2022-11-22 18:25:30 -05:00
Charlie Marsh
f59799e0c4 Remove lingering empty lines 2022-11-22 17:54:24 -05:00
Charlie Marsh
814ddeb7ea Remove always-inline (#879) 2022-11-22 17:13:25 -05:00
Charlie Marsh
9315b9f459 Remove Mode from various internal checkers (#877) 2022-11-22 16:57:47 -05:00
Charlie Marsh
113b5a10bf Return Vec<Check> from check_tokens (#876) 2022-11-22 16:43:32 -05:00
Charlie Marsh
fe77fb70a1 Apply autofixes iteratively until code is stabilized (#875) 2022-11-22 16:37:52 -05:00
Charlie Marsh
c3f6170503 Update README with list of projects (#874) 2022-11-22 14:28:02 -05:00
Anders Kaseorg
a46160f0e2 Fix clippy::unreadable_literal (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#unreadable_literal

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
9a66cf2ffb Fix clippy::uninlined_format_args (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
3205473612 Ignore clippy::struct_excessive_bools (pedantic)
“consider using a state machine or refactoring bools into two-variant
enums”

https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
0bb8b14ae1 Fix clippy::single_match_else (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
9cf4621071 Fix clippy::redundant_else (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#redundant_else

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
bbc9ed1b21 Fix clippy::redundant_closure_for_method_calls (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
517ca2604a Fix clippy::needless_pass_by_value (pedantic)
“this argument is passed by value, but not consumed in the function
body”

https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
348ff509c0 Fix clippy::mut_mut (pedantic)
“this expression mutably borrows a mutable reference. Consider
reborrowing”

https://rust-lang.github.io/rust-clippy/master/index.html#mut_mut

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
fb545551f8 Fix clippy::match_bool (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#match_bool

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
3b33a431d6 Fix clippy::map_unwrap_or (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
bc95690725 Fix clippy::manual_string_new (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#manual_string_new

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
9dc788d089 Fix clippy::let_underscore_drop (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#let_underscore_drop

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
15f63494a7 Fix clippy::items_after_statements (pedantic)
“adding items after statements is confusing, since items exist from
the start of the scope”

https://rust-lang.github.io/rust-clippy/master/index.html#items_after_statements

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
68668c20e8 Fix clippy::if_not_else (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#if_not_else

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
9462335371 Fix clippy::from_iter_instead_of_collect (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
3dd6522b4f Fix clippy::explicit_iter_loop (pedantic)
“it is more concise to loop over references to containers instead of
using explicit iteration methods”

https://rust-lang.github.io/rust-clippy/master/index.html#explicit_iter_loop

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
6b935121a7 Fix clippy::explicit_deref_methods (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#explicit_deref_methods

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
8d9d9b3204 Fix clippy::documentation_markdown (pedantic)
https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
0a506eff34 Fix clippy::cloned_instead_of_copied (pedantic)
“used `cloned` where `copied` could be used instead”

https://rust-lang.github.io/rust-clippy/master/index.html#cloned_instead_of_copied

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
7c7489c1dd Ignore clippy::cast_possible_truncation (pedantic)
“casting `usize` to `u8` may truncate the value”

https://rust-lang.github.io/rust-clippy/master/index.html#cast_possible_truncation

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
ae90eccb7f Fix clippy::cast_lossless (pedantic)
“casting `bool` to `u8` is more cleanly stated with `u8::from(_)`”

https://rust-lang.github.io/rust-clippy/master/index.html#cast_lossless

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2022-11-21 23:22:28 -05:00
Anders Kaseorg
7a61edbe46 Fix clippy::default-trait-access (pedantic) (#867) 2022-11-21 21:00:38 -05:00
294 changed files with 8460 additions and 3176 deletions

View File

@@ -82,7 +82,7 @@ jobs:
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy::pedantic
cargo_test:
name: "cargo test"

View File

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

56
Cargo.lock generated
View File

@@ -290,6 +290,36 @@ dependencies = [
"termcolor",
]
[[package]]
name = "clap_complete"
version = "4.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7b3c9eae0de7bf8e3f904a5e40612b21fb2e2e566456d177809a48b892d24da"
dependencies = [
"clap 4.0.22",
]
[[package]]
name = "clap_complete_command"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4160b4a4f72ef58bd766bad27c09e6ef1cc9d82a22f6a0f55d152985a4a48e31"
dependencies = [
"clap 4.0.22",
"clap_complete",
"clap_complete_fig",
]
[[package]]
name = "clap_complete_fig"
version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46b30e010e669cd021e5004f3be26cff6b7c08d2a8a0d65b48d43a8cc0efd6c3"
dependencies = [
"clap 4.0.22",
"clap_complete",
]
[[package]]
name = "clap_derive"
version = "4.0.21"
@@ -670,7 +700,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.134-dev.0"
version = "0.0.147-dev.0"
dependencies = [
"anyhow",
"clap 4.0.22",
@@ -769,10 +799,17 @@ dependencies = [
]
[[package]]
name = "glob"
version = "0.3.0"
name = "globset"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
dependencies = [
"aho-corasick",
"bstr 0.2.17",
"fnv",
"log",
"regex",
]
[[package]]
name = "half"
@@ -1029,7 +1066,7 @@ checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
[[package]]
name = "libcst"
version = "0.1.0"
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
source = "git+https://github.com/charliermarsh/LibCST?rev=f2f0b7a487a8725d161fe8b3ed73a6758b21e177#f2f0b7a487a8725d161fe8b3ed73a6758b21e177"
dependencies = [
"chic",
"itertools",
@@ -1044,7 +1081,7 @@ dependencies = [
[[package]]
name = "libcst_derive"
version = "0.1.0"
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
source = "git+https://github.com/charliermarsh/LibCST?rev=f2f0b7a487a8725d161fe8b3ed73a6758b21e177#f2f0b7a487a8725d161fe8b3ed73a6758b21e177"
dependencies = [
"quote",
"syn",
@@ -1768,7 +1805,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.134"
version = "0.0.147"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1779,6 +1816,7 @@ dependencies = [
"cachedir",
"chrono",
"clap 4.0.22",
"clap_complete_command",
"clearscreen",
"colored",
"common-path",
@@ -1787,7 +1825,7 @@ dependencies = [
"fern",
"filetime",
"getrandom 0.2.8",
"glob",
"globset",
"insta",
"itertools",
"libcst",
@@ -1818,7 +1856,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.134"
version = "0.0.147"
dependencies = [
"anyhow",
"clap 4.0.22",

View File

@@ -6,8 +6,9 @@ members = [
[package]
name = "ruff"
version = "0.0.134"
version = "0.0.147"
edition = "2021"
rust-version = "1.65.0"
[lib]
name = "ruff"
@@ -21,14 +22,15 @@ bitflags = { version = "1.3.2" }
cachedir = { version = "0.3.0" }
chrono = { version = "0.4.21", default-features = false, features = ["clock"] }
clap = { version = "4.0.1", features = ["derive"] }
clap_complete_command = "0.4.0"
colored = { version = "2.0.0" }
common-path = { version = "1.0.0" }
dirs = { version = "4.0.0" }
fern = { version = "0.6.1" }
filetime = { version = "0.2.17" }
glob = { version = "0.3.0" }
globset = {version = "0.4.9" }
itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "a13ec97dd4eb925bde4d426c6e422582793b260c" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
log = { version = "0.4.17" }
nohash-hasher = { version = "0.2.0" }
notify = { version = "4.0.17" }

75
LICENSE
View File

@@ -242,6 +242,56 @@ are:
SOFTWARE.
"""
- flake8-debugger, licensed as follows:
"""
MIT License
Copyright (c) 2016 Joseph Kahn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- flake8-eradicate, licensed as follows:
"""
MIT License
Copyright (c) 2018 Nikita Sobolev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- flake8-tidy-imports, licensed as follows:
"""
MIT License
@@ -443,3 +493,28 @@ are:
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
- RustPython, licensed as follows:
"""
MIT License
Copyright (c) 2020 RustPython Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

866
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,7 @@ fn criterion_benchmark(c: &mut Criterion) {
b.iter(|| {
let rope = Rope::from_str(black_box(&contents));
rope.line_to_char(black_box(4));
})
});
});
}

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.134"
version = "0.0.147"
dependencies = [
"anyhow",
"clap",
@@ -1265,7 +1265,7 @@ checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
[[package]]
name = "libcst"
version = "0.1.0"
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
source = "git+https://github.com/charliermarsh/LibCST?rev=f2f0b7a487a8725d161fe8b3ed73a6758b21e177#f2f0b7a487a8725d161fe8b3ed73a6758b21e177"
dependencies = [
"chic",
"itertools",
@@ -1280,7 +1280,7 @@ dependencies = [
[[package]]
name = "libcst_derive"
version = "0.1.0"
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
source = "git+https://github.com/charliermarsh/LibCST?rev=f2f0b7a487a8725d161fe8b3ed73a6758b21e177#f2f0b7a487a8725d161fe8b3ed73a6758b21e177"
dependencies = [
"quote",
"syn",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.134"
version = "0.0.147"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.134-dev.0"
version = "0.0.147-dev.0"
edition = "2021"
[lib]

View File

@@ -18,7 +18,7 @@ pub fn convert(
plugins: Option<Vec<Plugin>>,
) -> Result<Pyproject> {
// Extract all referenced check code prefixes, to power plugin inference.
let mut referenced_codes: BTreeSet<CheckCodePrefix> = Default::default();
let mut referenced_codes: BTreeSet<CheckCodePrefix> = BTreeSet::default();
for (key, value) in flake8 {
if let Some(value) = value {
match key.as_str() {
@@ -70,13 +70,13 @@ pub fn convert(
.unwrap_or_default();
// Parse each supported option.
let mut options: Options = Default::default();
let mut flake8_annotations: flake8_annotations::settings::Options = Default::default();
let mut flake8_bugbear: flake8_bugbear::settings::Options = Default::default();
let mut flake8_quotes: flake8_quotes::settings::Options = Default::default();
let mut flake8_tidy_imports: flake8_tidy_imports::settings::Options = Default::default();
let mut mccabe: mccabe::settings::Options = Default::default();
let mut pep8_naming: pep8_naming::settings::Options = Default::default();
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_quotes = flake8_quotes::settings::Options::default();
let mut flake8_tidy_imports = flake8_tidy_imports::settings::Options::default();
let mut mccabe = mccabe::settings::Options::default();
let mut pep8_naming = pep8_naming::settings::Options::default();
for (key, value) in flake8 {
if let Some(value) = value {
match key.as_str() {
@@ -110,7 +110,7 @@ pub fn convert(
match parser::parse_files_to_codes_mapping(value.as_ref()) {
Ok(per_file_ignores) => {
options.per_file_ignores =
Some(parser::collect_per_file_ignores(per_file_ignores))
Some(parser::collect_per_file_ignores(per_file_ignores));
}
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
@@ -181,7 +181,7 @@ pub fn convert(
"ban-relative-imports" | "ban_relative_imports" => match value.trim() {
"true" => flake8_tidy_imports.ban_relative_imports = Some(Strictness::All),
"parents" => {
flake8_tidy_imports.ban_relative_imports = Some(Strictness::Parents)
flake8_tidy_imports.ban_relative_imports = Some(Strictness::Parents);
}
_ => eprintln!("Unexpected '{key}' value: {value}"),
},
@@ -203,22 +203,22 @@ pub fn convert(
// Deduplicate and sort.
options.select = Some(Vec::from_iter(select));
options.ignore = Some(Vec::from_iter(ignore));
if flake8_annotations != Default::default() {
if flake8_annotations != flake8_annotations::settings::Options::default() {
options.flake8_annotations = Some(flake8_annotations);
}
if flake8_bugbear != Default::default() {
if flake8_bugbear != flake8_bugbear::settings::Options::default() {
options.flake8_bugbear = Some(flake8_bugbear);
}
if flake8_quotes != Default::default() {
if flake8_quotes != flake8_quotes::settings::Options::default() {
options.flake8_quotes = Some(flake8_quotes);
}
if flake8_tidy_imports != Default::default() {
if flake8_tidy_imports != flake8_tidy_imports::settings::Options::default() {
options.flake8_tidy_imports = Some(flake8_tidy_imports);
}
if mccabe != Default::default() {
if mccabe != mccabe::settings::Options::default() {
options.mccabe = Some(mccabe);
}
if pep8_naming != Default::default() {
if pep8_naming != pep8_naming::settings::Options::default() {
options.pep8_naming = Some(pep8_naming);
}
@@ -248,8 +248,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
@@ -269,6 +271,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -287,8 +290,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: Some(100),
per_file_ignores: None,
@@ -308,6 +313,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -326,8 +332,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: Some(100),
per_file_ignores: None,
@@ -347,6 +355,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -365,8 +374,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
@@ -386,6 +397,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -404,8 +416,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
@@ -430,6 +444,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -451,8 +466,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
@@ -507,6 +524,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);
@@ -525,8 +543,10 @@ mod tests {
extend_exclude: None,
extend_ignore: None,
extend_select: None,
external: None,
fix: None,
fixable: None,
format: None,
ignore: Some(vec![]),
line_length: None,
per_file_ignores: None,
@@ -552,6 +572,7 @@ mod tests {
isort: None,
mccabe: None,
pep8_naming: None,
pyupgrade: None,
});
assert_eq!(actual, expected);

View File

@@ -1,4 +1,15 @@
#![allow(clippy::collapsible_if, clippy::collapsible_else_if)]
#![allow(
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::implicit_hasher,
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::similar_names,
clippy::too_many_lines
)]
pub mod converter;
mod parser;

View File

@@ -1,4 +1,16 @@
//! Utility to generate Ruff's pyproject.toml section from a Flake8 INI file.
#![allow(
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::implicit_hasher,
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::similar_names,
clippy::too_many_lines
)]
use std::path::PathBuf;

View File

@@ -31,7 +31,7 @@ pub fn parse_prefix_codes(value: &str) -> Vec<CheckCodePrefix> {
pub fn parse_strings(value: &str) -> Vec<String> {
COMMA_SEPARATED_LIST_RE
.split(value)
.map(|part| part.trim())
.map(str::trim)
.filter(|part| !part.is_empty())
.map(String::from)
.collect()
@@ -92,7 +92,7 @@ impl State {
});
}
}
Err(_) => eprintln!("Skipping unrecognized prefix: {}", code),
Err(_) => eprintln!("Skipping unrecognized prefix: {code}"),
}
}
codes
@@ -129,14 +129,14 @@ fn tokenize_files_to_codes_mapping(value: &str) -> Vec<Token> {
}
tokens.push(Token {
token_name: TokenType::Eof,
src: "".to_string(),
src: String::new(),
});
tokens
}
/// Parse a 'files-to-codes' mapping, mimicking Flake8's internal logic.
///
/// See: https://github.com/PyCQA/flake8/blob/7dfe99616fc2f07c0017df2ba5fa884158f3ea8a/src/flake8/utils.py#L45
/// See: <https://github.com/PyCQA/flake8/blob/7dfe99616fc2f07c0017df2ba5fa884158f3ea8a/src/flake8/utils.py#L45>
pub fn parse_files_to_codes_mapping(value: &str) -> Result<Vec<PatternPrefixPair>> {
if value.trim().is_empty() {
return Ok(vec![]);

View File

@@ -6,17 +6,19 @@ use ruff::checks_gen::CheckCodePrefix;
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum Plugin {
Flake8Annotations,
Flake8Bandit,
Flake8BlindExcept,
Flake8Bugbear,
Flake8Builtins,
Flake8Comprehensions,
Flake8Debugger,
Flake8Docstrings,
Flake8TidyImports,
Flake8Eradicate,
Flake8Print,
Flake8Quotes,
Flake8Annotations,
Flake8TidyImports,
McCabe,
Flake8BlindExcept,
PEP8Naming,
Pyupgrade,
}
@@ -26,16 +28,18 @@ impl FromStr for Plugin {
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
"flake8-annotations" => Ok(Plugin::Flake8Annotations),
"flake8-bandit" => Ok(Plugin::Flake8Bandit),
"flake8-blind-except" => Ok(Plugin::Flake8BlindExcept),
"flake8-bugbear" => Ok(Plugin::Flake8Bugbear),
"flake8-builtins" => Ok(Plugin::Flake8Builtins),
"flake8-comprehensions" => Ok(Plugin::Flake8Comprehensions),
"flake8-debugger" => Ok(Plugin::Flake8Debugger),
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
"flake8-tidy-imports" => Ok(Plugin::Flake8TidyImports),
"flake8-eradicate" => Ok(Plugin::Flake8BlindExcept),
"flake8-print" => Ok(Plugin::Flake8Print),
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
"flake8-annotations" => Ok(Plugin::Flake8Annotations),
"flake8-blind-except" => Ok(Plugin::Flake8BlindExcept),
"flake8-tidy-imports" => Ok(Plugin::Flake8TidyImports),
"mccabe" => Ok(Plugin::McCabe),
"pep8-naming" => Ok(Plugin::PEP8Naming),
"pyupgrade" => Ok(Plugin::Pyupgrade),
@@ -47,16 +51,18 @@ impl FromStr for Plugin {
impl Plugin {
pub fn default(&self) -> CheckCodePrefix {
match self {
Plugin::Flake8Annotations => CheckCodePrefix::ANN,
Plugin::Flake8Bandit => CheckCodePrefix::S,
Plugin::Flake8BlindExcept => CheckCodePrefix::BLE,
Plugin::Flake8Bugbear => CheckCodePrefix::B,
Plugin::Flake8Builtins => CheckCodePrefix::A,
Plugin::Flake8Comprehensions => CheckCodePrefix::C4,
Plugin::Flake8Debugger => CheckCodePrefix::T1,
Plugin::Flake8Docstrings => CheckCodePrefix::D,
Plugin::Flake8TidyImports => CheckCodePrefix::I25,
Plugin::Flake8Print => CheckCodePrefix::T,
Plugin::Flake8Eradicate => CheckCodePrefix::ERA,
Plugin::Flake8Print => CheckCodePrefix::T2,
Plugin::Flake8Quotes => CheckCodePrefix::Q,
Plugin::Flake8Annotations => CheckCodePrefix::ANN,
Plugin::Flake8BlindExcept => CheckCodePrefix::BLE,
Plugin::Flake8TidyImports => CheckCodePrefix::I25,
Plugin::McCabe => CheckCodePrefix::C9,
Plugin::PEP8Naming => CheckCodePrefix::N,
Plugin::Pyupgrade => CheckCodePrefix::U,
@@ -65,10 +71,13 @@ impl Plugin {
pub fn select(&self, flake8: &HashMap<String, Option<String>>) -> Vec<CheckCodePrefix> {
match self {
Plugin::Flake8Annotations => vec![CheckCodePrefix::ANN],
Plugin::Flake8Bandit => vec![CheckCodePrefix::S],
Plugin::Flake8BlindExcept => vec![CheckCodePrefix::BLE],
Plugin::Flake8Bugbear => vec![CheckCodePrefix::B],
Plugin::Flake8Builtins => vec![CheckCodePrefix::A],
Plugin::Flake8Comprehensions => vec![CheckCodePrefix::C4],
Plugin::Flake8Debugger => vec![CheckCodePrefix::T1],
Plugin::Flake8Docstrings => {
// Use the user-provided docstring.
for key in ["docstring-convention", "docstring_convention"] {
@@ -85,11 +94,10 @@ impl Plugin {
// Default to PEP8.
DocstringConvention::PEP8.select()
}
Plugin::Flake8TidyImports => vec![CheckCodePrefix::I25],
Plugin::Flake8Print => vec![CheckCodePrefix::T],
Plugin::Flake8Eradicate => vec![CheckCodePrefix::ERA],
Plugin::Flake8Print => vec![CheckCodePrefix::T2],
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
Plugin::Flake8Annotations => vec![CheckCodePrefix::ANN],
Plugin::Flake8BlindExcept => vec![CheckCodePrefix::BLE],
Plugin::Flake8TidyImports => vec![CheckCodePrefix::I25],
Plugin::McCabe => vec![CheckCodePrefix::C9],
Plugin::PEP8Naming => vec![CheckCodePrefix::N],
Plugin::Pyupgrade => vec![CheckCodePrefix::U],
@@ -277,31 +285,6 @@ pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> V
let mut plugins = BTreeSet::new();
for key in flake8.keys() {
match key.as_str() {
// flake8-docstrings
"docstring-convention" | "docstring_convention" => {
plugins.insert(Plugin::Flake8Docstrings);
}
// flake8-bugbear
"extend-immutable-calls" | "extend_immutable_calls" => {
plugins.insert(Plugin::Flake8Bugbear);
}
// flake8-builtins
"builtins-ignorelist" | "builtins_ignorelist" => {
plugins.insert(Plugin::Flake8Builtins);
}
// flake8-quotes
"quotes" | "inline-quotes" | "inline_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"multiline-quotes" | "multiline_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"docstring-quotes" | "docstring_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"avoid-escape" | "avoid_escape" => {
plugins.insert(Plugin::Flake8Quotes);
}
// flake8-annotations
"suppress-none-returning" | "suppress_none_returning" => {
plugins.insert(Plugin::Flake8Annotations);
@@ -327,6 +310,41 @@ pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> V
"allow-star-arg-any" | "allow_star_arg_any" => {
plugins.insert(Plugin::Flake8Annotations);
}
// flake8-bugbear
"extend-immutable-calls" | "extend_immutable_calls" => {
plugins.insert(Plugin::Flake8Bugbear);
}
// flake8-builtins
"builtins-ignorelist" | "builtins_ignorelist" => {
plugins.insert(Plugin::Flake8Builtins);
}
// flake8-docstrings
"docstring-convention" | "docstring_convention" => {
plugins.insert(Plugin::Flake8Docstrings);
}
// flake8-eradicate
"eradicate-aggressive" | "eradicate_aggressive" => {
plugins.insert(Plugin::Flake8Eradicate);
}
"eradicate-whitelist" | "eradicate_whitelist" => {
plugins.insert(Plugin::Flake8Eradicate);
}
"eradicate-whitelist-extend" | "eradicate_whitelist_extend" => {
plugins.insert(Plugin::Flake8Eradicate);
}
// flake8-quotes
"quotes" | "inline-quotes" | "inline_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"multiline-quotes" | "multiline_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"docstring-quotes" | "docstring_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"avoid-escape" | "avoid_escape" => {
plugins.insert(Plugin::Flake8Quotes);
}
// flake8-tidy-imports
"ban-relative-imports" | "ban_relative_imports" => {
plugins.insert(Plugin::Flake8TidyImports);
@@ -360,16 +378,18 @@ pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> V
/// `flake8-annotations` is active.
pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin> {
[
Plugin::Flake8Annotations,
Plugin::Flake8Bandit,
Plugin::Flake8BlindExcept,
Plugin::Flake8Bugbear,
Plugin::Flake8Builtins,
Plugin::Flake8Comprehensions,
Plugin::Flake8Debugger,
Plugin::Flake8Docstrings,
Plugin::Flake8TidyImports,
Plugin::Flake8Eradicate,
Plugin::Flake8Print,
Plugin::Flake8Quotes,
Plugin::Flake8Annotations,
Plugin::Flake8BlindExcept,
Plugin::Flake8TidyImports,
Plugin::PEP8Naming,
Plugin::Pyupgrade,
]

View File

@@ -31,7 +31,3 @@ build-backend = "maturin"
[tool.maturin]
bindings = "bin"
strip = true
[tool.isort]
profile = "black"
known_third_party = ["fastapi", "pydantic", "starlette"]

82
resources/test/fixtures/B023.py vendored Normal file
View File

@@ -0,0 +1,82 @@
"""
Should emit:
B023 - on lines 12, 13, 16, 28, 29, 30, 31, 40, 42, 50, 51, 52, 53, 61, 68.
"""
functions = []
z = 0
for x in range(3):
y = x + 1
# Subject to late-binding problems
functions.append(lambda: x)
functions.append(lambda: y) # not just the loop var
def f_bad_1():
return x
# Actually OK
functions.append(lambda x: x * 2)
functions.append(lambda x=x: x)
functions.append(lambda: z) # OK because not assigned in the loop
def f_ok_1(x):
return x * 2
def check_inside_functions_too():
ls = [lambda: x for x in range(2)]
st = {lambda: x for x in range(2)}
gn = (lambda: x for x in range(2))
dt = {x: lambda: x for x in range(2)}
async def pointless_async_iterable():
yield 1
async def container_for_problems():
async for x in pointless_async_iterable():
functions.append(lambda: x)
[lambda: x async for x in pointless_async_iterable()]
a = 10
b = 0
while True:
a = a_ = a - 1
b += 1
functions.append(lambda: a)
functions.append(lambda: a_)
functions.append(lambda: b)
functions.append(lambda: c) # not a name error because of late binding!
c: bool = a > 3
if not c:
break
# Nested loops should not duplicate reports
for j in range(2):
for k in range(3):
lambda: j * k
for j, k, l in [(1, 2, 3)]:
def f():
j = None # OK because it's an assignment
[l for k in range(2)] # error for l, not for k
assert a and functions
a.attribute = 1 # modifying an attribute doesn't make it a loop variable
functions[0] = lambda: None # same for an element
for var in range(2):
def explicit_capture(captured=var):
return captured
for i in range(3):
lambda: f"{i}"

55
resources/test/fixtures/B904.py vendored Normal file
View File

@@ -0,0 +1,55 @@
"""
Should emit:
B904 - on lines 10, 11 and 16
"""
try:
raise ValueError
except ValueError:
if "abc":
raise TypeError
raise UserWarning
except AssertionError:
raise # Bare `raise` should not be an error
except Exception as err:
assert err
raise Exception("No cause here...")
except BaseException as base_err:
# Might use this instead of bare raise with the `.with_traceback()` method
raise base_err
finally:
raise Exception("Nothing to chain from, so no warning here")
try:
raise ValueError
except ValueError:
# should not emit, since we are not raising something
def proxy():
raise NameError
try:
from preferred_library import Thing
except ImportError:
try:
from fallback_library import Thing
except ImportError:
class Thing:
def __getattr__(self, name):
# same as the case above, should not emit.
raise AttributeError
try:
from preferred_library import Thing
except ImportError:
try:
from fallback_library import Thing
except ImportError:
def context_switch():
try:
raise ValueError
except ValueError:
raise

View File

@@ -3,6 +3,7 @@ from functools import wraps
import os
from .expected import Expectation
from typing import overload
from typing_extensions import override
expectation = Expectation()
@@ -42,9 +43,13 @@ class class_:
"D418: Function/ Method decorated with @overload"
" shouldn't contain a docstring")
@override
def overridden_method(a):
return str(a)
@property
def foo(self):
"""The foo of the thing, which isn't in imperitive mood."""
"""The foo of the thing, which isn't in imperative mood."""
return "hello"
@expect('D102: Missing docstring in public method')

View File

@@ -49,3 +49,9 @@ sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labor
sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
""", # noqa: E501
}
# OK
# A very long URL: https://loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong.url.com
# OK
# https://loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong.url.com

13
resources/test/fixtures/ERA001.py vendored Normal file
View File

@@ -0,0 +1,13 @@
#import os
# from foo import junk
#a = 3
a = 4
#foo(1, 2, 3)
def foo(x, y, z):
contentet = 1 # print('hello')
print(x, y, z)
# This is a real comment.
#return True
return False

13
resources/test/fixtures/F502.py vendored Normal file
View File

@@ -0,0 +1,13 @@
dog = {"bob": "bob"}
"%(bob)s" % dog
"%(bob)s" % {"bob": "bob"}
"%(bob)s" % {**{"bob": "bob"}}
"%(bob)s" % ["bob"] # F202
"%(bob)s" % ("bob",) # F202
"%(bob)s" % {"bob"} # F202
"%(bob)s" % [*["bob"]] # F202
"%(bob)s" % {"bob": "bob" for _ in range(1)}
"%(bob)s" % ["bob" for _ in range(1)] # F202
"%(bob)s" % ("bob" for _ in range(1)) # F202
"%(bob)s" % {"bob" for _ in range(1)} # F202

26
resources/test/fixtures/F503.py vendored Normal file
View File

@@ -0,0 +1,26 @@
dog = {"bob": "bob"}
# Single placeholder always fine
"%s" % dog
"%s" % {"bob": "bob"}
"%s" % {**{"bob": "bob"}}
"%s" % ["bob"]
"%s" % ("bob",)
"%s" % {"bob"}
"%s" % [*["bob"]]
"%s" % {"bob": "bob" for _ in range(1)}
"%s" % ["bob" for _ in range(1)]
"%s" % ("bob" for _ in range(1))
"%s" % {"bob" for _ in range(1)}
# Multiple placeholders
"%s %s" % dog
"%s %s" % {"bob": "bob"} # F503
"%s %s" % {**{"bob": "bob"}} # F503
"%s %s" % ["bob"]
"%s %s" % ("bob",)
"%s %s" % {"bob"}
"%s %s" % [*["bob"]]
"%s %s" % {"bob": "bob" for _ in range(1)} # F503
"%s %s" % ["bob" for _ in range(1)]
"%s %s" % ("bob" for _ in range(1))
"%s %s" % {"bob" for _ in range(1)}

6
resources/test/fixtures/F504.py vendored Normal file
View File

@@ -0,0 +1,6 @@
# Ruff has no way of knowing if the following are F505s
a = "wrong"
"%(a)s %(c)s" % {a: "?", "b": "!"} # F504 ("b" not used)
hidden = {"a": "!"}
"%(a)s %(c)s" % {"x": 1, **hidden} # Ok (cannot see through splat)

25
resources/test/fixtures/F50x.py vendored Normal file
View File

@@ -0,0 +1,25 @@
'%(foo)' % {'foo': 'bar'} # F501
'%s %(foo)s' % {'foo': 'bar'} # F506
'%(foo)s %s' % {'foo': 'bar'} # F506
'%j' % (1,) # F509
'%s %s' % (1,) # F507
'%s %s' % (1, 2, 3) # F507
'%(bar)s' % {} # F505
'%(bar)s' % {'bar': 1, 'baz': 2} # F504
'%(bar)s' % (1, 2, 3) # F502
'%s %s' % {'k': 'v'} # F503
'%(bar)*s' % {'bar': 'baz'} # F506, F508
# ok: single %s with mapping
'%s' % {'foo': 'bar', 'baz': 'womp'}
# ok: %% should not count towards placeholder count
'%% %s %% %s' % (1, 2)
# ok: * consumes one positional argument
'%.*f' % (2, 1.1234)
'%*.*f' % (5, 2, 3.1234)
# ok *args and **kwargs
a = []
'%s %s' % [*a]
'%s %s' % (*a,)
k = {}
'%(k)s' % {**k}

21
resources/test/fixtures/F521.py vendored Normal file
View File

@@ -0,0 +1,21 @@
"{".format(1)
"}".format(1)
"{foo[}".format(foo=1)
# too much string recursion (placeholder-in-placeholder)
"{:{:{}}}".format(1, 2, 3)
# ruff picks these issues up, but flake8 doesn't
"{foo[]}".format(foo={"": 1})
"{foo..}".format(foo=1)
"{foo..bar}".format(foo=1)
# The following are all "good" uses of .format
"{.__class__}".format("")
"{foo[bar]}".format(foo={"bar": "barv"})
"{[bar]}".format({"bar": "barv"})
"{:{}} {}".format(1, 15, 2)
"{:2}".format(1)
"{foo}-{}".format(1, foo=2)
a = ()
"{}".format(*a)
k = {}
"{foo}".format(**k)

4
resources/test/fixtures/F522.py vendored Normal file
View File

@@ -0,0 +1,4 @@
"{}".format(1, bar=2) # F522
"{bar}{}".format(1, bar=2, spam=3) # F522
"{bar:{spam}}".format(bar=2, spam=3) # No issues
"{bar:{spam}}".format(bar=2, spam=3, eggs=4, ham=5) # F522

12
resources/test/fixtures/F523.py vendored Normal file
View File

@@ -0,0 +1,12 @@
# With indexes
"{0}".format(1, 2) # F523
"{1}".format(1, 2, 3) # F523
"{1:{0}}".format(1, 2) # No issues
"{1:{0}}".format(1, 2, 3) # F523
"{0}{2}".format(1, 2) # F523, # F524
# With no indexes
"{}".format(1, 2) # F523
"{}".format(1, 2, 3) # F523
"{:{}}".format(1, 2) # No issues
"{:{}}".format(1, 2, 3) # F523

6
resources/test/fixtures/F524.py vendored Normal file
View File

@@ -0,0 +1,6 @@
"{} {}".format(1) # F524
"{2}".format() # F524
"{bar}".format() # F524
"{0} {bar}".format(1) # F524
"{0} {bar}".format() # F524
"{bar} {0}".format() # F524

2
resources/test/fixtures/F525.py vendored Normal file
View File

@@ -0,0 +1,2 @@
"{} {1}".format(1, 2) # F525
"{0} {}".format(1, 2) # F523, F525

View File

@@ -12,3 +12,13 @@ x: dict["key", "value"]
# OK
x: dict[str, str]
# OK
def unimportant(name):
pass
def dang(dict, set, list):
unimportant(name=dict["name"])
unimportant(name=set["name"])
unimportant(name=list["name"])

View File

@@ -52,3 +52,16 @@ def f5():
def f7():
nonlocal b
def f6():
annotations = []
assert len([annotations for annotations in annotations])
def f7():
def connect():
return None, None
with connect() as (connection, cursor):
cursor.execute("SELECT * FROM users")

View File

@@ -38,5 +38,20 @@ def function(
def used(do):
return do
used("a", True)
used(do=True)
# Avoid FBT003 for explicitly allowed methods.
"""
FBT003 Boolean positional value on dict
"""
a = {"a": "b"}
a.get("hello", False)
{}.get("hello", False)
{}.setdefault("hello", True)
{}.pop("hello", False)
{}.pop(True, False)
dict.fromkeys(("world",), True)
{}.deploy(True, False)

View File

@@ -18,6 +18,9 @@ def f() -> None:
# Invalid (and unimplemented)
d = 1 # noqa: F841, W191
# Invalid (but external)
d = 1 # noqa: F841, V101
# fmt: off
# Invalid - no space before #
d = 1# noqa: E501

14
resources/test/fixtures/T100.py vendored Normal file
View File

@@ -0,0 +1,14 @@
breakpoint()
import pdb
from builtins import breakpoint
from pdb import set_trace as st
from celery.contrib.rdb import set_trace
from celery.contrib import rdb
import celery.contrib.rdb
breakpoint()
st()
set_trace()

View File

@@ -1,3 +1,3 @@
# coding=utf8
print('Hello world')
print("Hello world")

3
resources/test/fixtures/U009_4.py vendored Normal file
View File

@@ -0,0 +1,3 @@
# coding=utf8 # noqa: U009
print("Hello world")

View File

@@ -36,3 +36,45 @@ with open("foo", "U") as fa, open("bar", "U") as fb:
pass
with open("foo", "Ub") as fa, open("bar", "Ub") as fb:
pass
open("foo", mode="U")
open(name="foo", mode="U")
open(mode="U", name="foo")
with open("foo", mode="U") as f:
pass
with open(name="foo", mode="U") as f:
pass
with open(mode="U", name="foo") as f:
pass
open("foo", mode="Ub")
open(name="foo", mode="Ub")
open(mode="Ub", name="foo")
with open("foo", mode="Ub") as f:
pass
with open(name="foo", mode="Ub") as f:
pass
with open(mode="Ub", name="foo") as f:
pass
open(file="foo", mode='U', buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
open(file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='U')
open(file="foo", buffering=- 1, encoding=None, errors=None, mode='U', newline=None, closefd=True, opener=None)
open(mode='U', file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
open(file="foo", mode='Ub', buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
open(file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None, mode='Ub')
open(file="foo", buffering=- 1, encoding=None, errors=None, mode='Ub', newline=None, closefd=True, opener=None)
open(mode='Ub', file="foo", buffering=- 1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
open = 1
open("foo", "U")
open("foo", "Ur")
open("foo", "Ub")
open("foo", "rUb")
open("foo", "r")
open("foo", "rt")
open("f", "r", encoding="UTF-8")
open("f", "wt")

View File

@@ -44,7 +44,7 @@ expectation.expected.add((
@expect("D407: Missing dashed underline after section ('Returns')",
arg_count=3)
@expect("D413: Missing blank line after last section ('Raises')", arg_count=3)
def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
def fetch_bigtable_rows(big_table, keys, other_silly_variable=None, **kwargs):
"""Fetches rows from a Bigtable.
Retrieves rows pertaining to the given keys from the Table instance
@@ -57,6 +57,7 @@ def fetch_bigtable_rows(big_table, keys, other_silly_variable=None):
to fetch.
other_silly_variable: Another optional variable, that has a much
longer name than the other args, and which does nothing.
**kwargs: More keyword arguments.
Returns:
A dict mapping keys to the corresponding table row data

View File

@@ -73,7 +73,7 @@ expectation.expected.add((
"(found 'A')", arg_count=3)
@expect("D413: Missing blank line after last section ('Examples')",
arg_count=3)
def foo(var1, var2, long_var_name='hi'):
def foo(var1, var2, long_var_name='hi', **kwargs):
r"""A one-line summary that does not use variable names.
Several sentences providing an extended description. Refer to
@@ -91,6 +91,8 @@ def foo(var1, var2, long_var_name='hi'):
detail, e.g. ``(N,) ndarray`` or ``array_like``.
long_var_name : {'hi', 'ho'}, optional
Choices in brackets, default first when optional.
**kwargs : int
More keyword arguments.
Returns
-------

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import List, Optional
from models import (
Fruit,
@@ -28,3 +29,12 @@ class Foo:
@classmethod
def d(cls) -> Fruit:
return cls(x=0, y=0)
def f(x: int) -> List[int]:
y = List[int]()
y.append(x)
return y
x: Optional[int] = None

View File

@@ -5,6 +5,7 @@ extend-exclude = [
"migrations",
"with_excluded_file/other_excluded_file.py",
]
external = ["V101"]
per-file-ignores = { "__init__.py" = ["F401"] }
[tool.ruff.flake8-bugbear]

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.134"
version = "0.0.147"
edition = "2021"
[dependencies]
@@ -8,7 +8,7 @@ anyhow = { version = "1.0.66" }
clap = { version = "4.0.1", features = ["derive"] }
codegen = { version = "0.2.0" }
itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "a13ec97dd4eb925bde4d426c6e422582793b260c" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
ruff = { path = ".." }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }

View File

@@ -24,7 +24,7 @@ pub struct Cli {
pub fn main(cli: &Cli) -> Result<()> {
// Build up a map from prefix to matching CheckCodes.
let mut prefix_to_codes: BTreeMap<String, BTreeSet<CheckCode>> = Default::default();
let mut prefix_to_codes: BTreeMap<String, BTreeSet<CheckCode>> = BTreeMap::default();
for check_code in CheckCode::iter() {
let as_ref: String = check_code.as_ref().to_string();
let prefix_len = as_ref
@@ -106,7 +106,7 @@ pub fn main(cli: &Cli) -> Result<()> {
2 => "Tens",
1 => "Hundreds",
0 => "Category",
_ => panic!("Invalid prefix: {}", prefix),
_ => panic!("Invalid prefix: {prefix}"),
};
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => PrefixSpecificity::{},",
@@ -117,7 +117,8 @@ pub fn main(cli: &Cli) -> Result<()> {
// Construct the output contents.
let mut output = String::new();
output.push_str("//! File automatically generated by examples/generate_check_code_prefix.rs.");
output
.push_str("//! File automatically generated by `examples/generate_check_code_prefix.rs`.");
output.push('\n');
output.push('\n');
output.push_str("use serde::{{Serialize, Deserialize}};");
@@ -129,13 +130,28 @@ pub fn main(cli: &Cli) -> Result<()> {
output.push('\n');
output.push('\n');
output.push_str(&scope.to_string());
output.push('\n');
output.push('\n');
// Add the list of output categories (not generated).
output.push_str("pub const CATEGORIES: &[CheckCodePrefix] = &[");
output.push('\n');
for prefix in prefix_to_codes.keys() {
if prefix.chars().all(char::is_alphabetic) {
output.push_str(&format!("CheckCodePrefix::{prefix},"));
output.push('\n');
}
}
output.push_str("];");
output.push('\n');
output.push('\n');
// Write the output to `src/checks_gen.rs` (or stdout).
if cli.dry_run {
println!("{}", output);
println!("{output}");
} else {
let mut f = OpenOptions::new().write(true).truncate(true).open(FILE)?;
write!(f, "{}", output)?;
write!(f, "{output}")?;
}
Ok(())

View File

@@ -61,7 +61,7 @@ pub fn main(cli: &Cli) -> Result<()> {
}
if cli.dry_run {
print!("{}", output);
print!("{output}");
} else {
// Read the existing file.
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
@@ -84,9 +84,9 @@ pub fn main(cli: &Cli) -> Result<()> {
// Write the prefix, new contents, and suffix.
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
write!(f, "{}\n\n", prefix)?;
write!(f, "{}", output)?;
write!(f, "{}", suffix)?;
write!(f, "{prefix}\n\n")?;
write!(f, "{output}")?;
write!(f, "{suffix}")?;
}
Ok(())

View File

@@ -19,7 +19,7 @@ pub fn main(cli: &Cli) -> Result<()> {
let contents = fs::read_to_string(&cli.file)?;
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
let mut generator = SourceGenerator::new();
generator.unparse_suite(&python_ast)?;
generator.unparse_suite(&python_ast);
println!("{}", generator.generate()?);
Ok(())
}

View File

@@ -1,3 +1,16 @@
#![allow(
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::implicit_hasher,
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::similar_names,
clippy::too_many_lines
)]
pub mod generate_check_code_prefix;
pub mod generate_rules_table;
pub mod generate_source_code;

View File

@@ -1,3 +1,16 @@
#![allow(
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::implicit_hasher,
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::similar_names,
clippy::too_many_lines
)]
use anyhow::Result;
use clap::{Parser, Subcommand};
use ruff_dev::{

View File

@@ -17,6 +17,6 @@ pub struct Cli {
pub fn main(cli: &Cli) -> Result<()> {
let contents = fs::read_to_string(&cli.file)?;
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
println!("{:#?}", python_ast);
println!("{python_ast:#?}");
Ok(())
}

View File

@@ -1,4 +1,4 @@
//! Print the LibCST CST for a given Python file.
//! Print the `LibCST` CST for a given Python file.
use std::fs;
use std::path::PathBuf;
@@ -17,7 +17,7 @@ pub fn main(cli: &Cli) -> Result<()> {
let contents = fs::read_to_string(&cli.file)?;
match libcst_native::parse_module(&contents, None) {
Ok(python_cst) => {
println!("{:#?}", python_cst);
println!("{python_cst:#?}");
Ok(())
}
Err(_) => Err(anyhow::anyhow!("Failed to parse CST")),

View File

@@ -17,7 +17,7 @@ pub struct Cli {
pub fn main(cli: &Cli) -> Result<()> {
let contents = fs::read_to_string(&cli.file)?;
for (_, tok, _) in lexer::make_tokenizer(&contents).flatten() {
println!("{:#?}", tok);
println!("{tok:#?}");
}
Ok(())
}

View File

@@ -1,12 +1,13 @@
use once_cell::sync::Lazy;
use regex::Regex;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, Location, Stmt, StmtKind};
use rustpython_ast::{
Arguments, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Location, Stmt, StmtKind,
};
use crate::ast::types::Range;
use crate::SourceCodeLocator;
#[inline(always)]
fn collect_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
match &expr.node {
ExprKind::Call { func, .. } => {
@@ -24,7 +25,6 @@ fn collect_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
}
/// Convert an `Expr` to its call path (like `List`, or `typing.List`).
#[inline(always)]
pub fn compose_call_path(expr: &Expr) -> Option<String> {
let segments = collect_call_paths(expr);
if segments.is_empty() {
@@ -35,7 +35,6 @@ pub fn compose_call_path(expr: &Expr) -> Option<String> {
}
/// Convert an `Expr` to its call path segments (like ["typing", "List"]).
#[inline(always)]
pub fn collect_call_paths(expr: &Expr) -> Vec<&str> {
let mut segments = vec![];
collect_call_path_inner(expr, &mut segments);
@@ -121,10 +120,9 @@ pub fn match_call_path(
// `Match`).
if num_segments == 0 {
module.is_empty()
|| from_imports
.get(module)
.map(|imports| imports.contains(member) || imports.contains("*"))
.unwrap_or(false)
|| from_imports.get(module).map_or(false, |imports| {
imports.contains(member) || imports.contains("*")
})
} else {
let components: Vec<&str> = module.split('.').collect();
@@ -148,8 +146,7 @@ pub fn match_call_path(
let member = components[cut];
if from_imports
.get(&module.as_str())
.map(|imports| imports.contains(member))
.unwrap_or(false)
.map_or(false, |imports| imports.contains(member))
{
return true;
}
@@ -218,6 +215,27 @@ pub fn extract_handler_names(handlers: &[Excepthandler]) -> Vec<Vec<&str>> {
handler_names
}
/// Return the set of all bound argument names.
pub fn collect_arg_names<'a>(arguments: &'a Arguments) -> FxHashSet<&'a str> {
let mut arg_names: FxHashSet<&'a str> = FxHashSet::default();
for arg in &arguments.posonlyargs {
arg_names.insert(arg.node.arg.as_str());
}
for arg in &arguments.args {
arg_names.insert(arg.node.arg.as_str());
}
if let Some(arg) = &arguments.vararg {
arg_names.insert(arg.node.arg.as_str());
}
for arg in &arguments.kwonlyargs {
arg_names.insert(arg.node.arg.as_str());
}
if let Some(arg) = &arguments.kwarg {
arg_names.insert(arg.node.arg.as_str());
}
arg_names
}
/// Returns `true` if a call is an argumented `super` invocation.
pub fn is_super_call_with_arguments(func: &Expr, args: &[Expr]) -> bool {
// Check: is this a `super` call?

View File

@@ -4,8 +4,6 @@ use crate::ast::types::{BindingKind, Scope};
/// Extract the names bound to a given __all__ assignment.
pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
let mut names: Vec<String> = vec![];
fn add_to_names(names: &mut Vec<String>, elts: &[Expr]) {
for elt in elts {
if let ExprKind::Constant {
@@ -13,11 +11,13 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
..
} = &elt.node
{
names.push(value.to_string())
names.push(value.to_string());
}
}
}
let mut names: Vec<String> = vec![];
// Grab the existing bound __all__ values.
if let StmtKind::AugAssign { .. } = &stmt.node {
if let Some(binding) = scope.values.get("__all__") {
@@ -35,7 +35,7 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
} {
match &value.node {
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
add_to_names(&mut names, elts)
add_to_names(&mut names, elts);
}
ExprKind::BinOp { left, right, .. } => {
let mut current_left = left;

View File

@@ -1,7 +1,7 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use rustc_hash::FxHashMap;
use rustpython_ast::{Expr, Keyword};
use rustpython_ast::{Expr, Keyword, Stmt};
use rustpython_parser::ast::{Located, Location};
fn id() -> usize {
@@ -9,6 +9,12 @@ fn id() -> usize {
COUNTER.fetch_add(1, Ordering::Relaxed)
}
#[derive(Clone)]
pub enum Node<'a> {
Stmt(&'a Stmt),
Expr(&'a Expr),
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Range {
pub location: Location,
@@ -101,9 +107,3 @@ pub struct Binding {
/// the binding was last used.
pub used: Option<(usize, Range)>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ImportKind {
Import,
ImportFrom,
}

View File

@@ -19,8 +19,8 @@ pub trait Visitor<'a> {
fn visit_constant(&mut self, constant: &'a Constant) {
walk_constant(self, constant);
}
fn visit_expr_context(&mut self, expr_content: &'a ExprContext) {
walk_expr_context(self, expr_content);
fn visit_expr_context(&mut self, expr_context: &'a ExprContext) {
walk_expr_context(self, expr_context);
}
fn visit_boolop(&mut self, boolop: &'a Boolop) {
walk_boolop(self, boolop);
@@ -249,7 +249,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
visitor.visit_stmt(stmt);
}
for excepthandler in handlers {
visitor.visit_excepthandler(excepthandler)
visitor.visit_excepthandler(excepthandler);
}
for stmt in orelse {
visitor.visit_stmt(stmt);
@@ -447,7 +447,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
pub fn walk_constant<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, constant: &'a Constant) {
if let Constant::Tuple(constants) = constant {
for constant in constants {
visitor.visit_constant(constant)
visitor.visit_constant(constant);
}
}
}
@@ -456,8 +456,8 @@ pub fn walk_comprehension<'a, V: Visitor<'a> + ?Sized>(
visitor: &mut V,
comprehension: &'a Comprehension,
) {
visitor.visit_expr(&comprehension.target);
visitor.visit_expr(&comprehension.iter);
visitor.visit_expr(&comprehension.target);
for expr in &comprehension.ifs {
visitor.visit_expr(expr);
}
@@ -577,7 +577,6 @@ pub fn walk_pattern<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, pattern: &'a P
}
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_expr_context<'a, V: Visitor<'a> + ?Sized>(
visitor: &mut V,
expr_context: &'a ExprContext,
@@ -585,21 +584,16 @@ pub fn walk_expr_context<'a, V: Visitor<'a> + ?Sized>(
}
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_boolop<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, boolop: &'a Boolop) {}
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_operator<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, operator: &'a Operator) {}
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_unaryop<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, unaryop: &'a Unaryop) {}
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_cmpop<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, cmpop: &'a Cmpop) {}
#[allow(unused_variables)]
#[inline(always)]
pub fn walk_alias<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, alias: &'a Alias) {}

View File

@@ -19,7 +19,7 @@ pub fn leading_space(line: &str) -> String {
}
/// Extract the leading indentation from a line.
pub fn indentation<'a, T>(checker: &'a Checker, located: &Located<T>) -> String {
pub fn indentation<T>(checker: &Checker, located: &Located<T>) -> String {
let range = Range::from_located(located);
checker
.locator

View File

@@ -10,8 +10,6 @@ use crate::autofix::{Fix, Patch};
use crate::checks::Check;
use crate::source_code_locator::SourceCodeLocator;
// TODO(charlie): The model here is awkward because `Apply` is only relevant at
// higher levels in the execution flow.
#[derive(Hash)]
pub enum Mode {
Generate,
@@ -19,55 +17,55 @@ pub enum Mode {
None,
}
impl Mode {
/// Return `true` if a patch should be generated under the given `Mode`.
pub fn patch(&self) -> bool {
match &self {
Mode::Generate => true,
Mode::Apply => true,
Mode::None => false,
impl From<bool> for Mode {
fn from(value: bool) -> Self {
if value {
Mode::Apply
} else {
Mode::None
}
}
}
impl From<bool> for Mode {
fn from(value: bool) -> Self {
impl From<&Mode> for bool {
fn from(value: &Mode) -> Self {
match value {
true => Mode::Apply,
false => Mode::None,
Mode::Generate | Mode::Apply => true,
Mode::None => false,
}
}
}
/// Auto-fix errors in a file, and write the fixed source code to disk.
pub fn fix_file<'a>(
checks: &'a mut [Check],
checks: &'a [Check],
locator: &'a SourceCodeLocator<'a>,
) -> Option<Cow<'a, str>> {
) -> Option<(Cow<'a, str>, usize)> {
if checks.iter().all(|check| check.fix.is_none()) {
return None;
}
Some(apply_fixes(
checks.iter_mut().filter_map(|check| check.fix.as_mut()),
checks.iter().filter_map(|check| check.fix.as_ref()),
locator,
))
}
/// Apply a series of fixes.
fn apply_fixes<'a>(
fixes: impl Iterator<Item = &'a mut Fix>,
fixes: impl Iterator<Item = &'a Fix>,
locator: &'a SourceCodeLocator<'a>,
) -> Cow<'a, str> {
) -> (Cow<'a, str>, usize) {
let mut output = RopeBuilder::new();
let mut last_pos: Location = Location::new(1, 0);
let mut applied: BTreeSet<&Patch> = BTreeSet::default();
let mut num_fixed: usize = 0;
for fix in fixes.sorted_by_key(|fix| fix.patch.location) {
// If we already applied an identical fix as part of another correction, skip
// any re-application.
if applied.contains(&fix.patch) {
fix.applied = true;
num_fixed += 1;
continue;
}
@@ -90,19 +88,18 @@ fn apply_fixes<'a>(
// Track that the fix was applied.
last_pos = fix.patch.end_location;
applied.insert(&fix.patch);
fix.applied = true;
num_fixed += 1;
}
// Add the remaining content.
let slice = locator.slice_source_code_at(last_pos);
output.append(&slice);
Cow::from(output.finish())
(Cow::from(output.finish()), num_fixed)
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use rustpython_parser::ast::Location;
use crate::autofix::fixer::apply_fixes;
@@ -110,115 +107,117 @@ mod tests {
use crate::SourceCodeLocator;
#[test]
fn empty_file() -> Result<()> {
let mut fixes = vec![];
let locator = SourceCodeLocator::new("");
let actual = apply_fixes(fixes.iter_mut(), &locator);
let expected = "";
assert_eq!(actual, expected);
Ok(())
fn empty_file() {
let fixes = vec![];
let locator = SourceCodeLocator::new(r#""#);
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
assert_eq!(contents, "");
assert_eq!(fixed, 0);
}
#[test]
fn apply_single_replacement() -> Result<()> {
let mut fixes = vec![Fix {
fn apply_single_replacement() {
let fixes = vec![Fix {
patch: Patch {
content: "Bar".to_string(),
location: Location::new(1, 8),
end_location: Location::new(1, 14),
},
applied: false,
}];
let locator = SourceCodeLocator::new(
"class A(object):
...
",
r#"
class A(object):
...
"#
.trim(),
);
let actual = apply_fixes(fixes.iter_mut(), &locator);
let expected = "class A(Bar):
...
";
assert_eq!(actual, expected);
Ok(())
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
assert_eq!(
contents,
r#"
class A(Bar):
...
"#
.trim(),
);
assert_eq!(fixed, 1);
}
#[test]
fn apply_single_removal() -> Result<()> {
let mut fixes = vec![Fix {
fn apply_single_removal() {
let fixes = vec![Fix {
patch: Patch {
content: "".to_string(),
content: String::new(),
location: Location::new(1, 7),
end_location: Location::new(1, 15),
},
applied: false,
}];
let locator = SourceCodeLocator::new(
"class A(object):
...
",
r#"
class A(object):
...
"#
.trim(),
);
let actual = apply_fixes(fixes.iter_mut(), &locator);
let expected = "class A:
...
";
assert_eq!(actual, expected);
Ok(())
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
assert_eq!(
contents,
r#"
class A:
...
"#
.trim()
);
assert_eq!(fixed, 1);
}
#[test]
fn apply_double_removal() -> Result<()> {
let mut fixes = vec![
fn apply_double_removal() {
let fixes = vec![
Fix {
patch: Patch {
content: "".to_string(),
content: String::new(),
location: Location::new(1, 7),
end_location: Location::new(1, 16),
},
applied: false,
},
Fix {
patch: Patch {
content: "".to_string(),
content: String::new(),
location: Location::new(1, 16),
end_location: Location::new(1, 23),
},
applied: false,
},
];
let locator = SourceCodeLocator::new(
"class A(object, object):
...
",
r#"
class A(object, object):
...
"#
.trim(),
);
let actual = apply_fixes(fixes.iter_mut(), &locator);
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
let expected = "class A:
...
";
assert_eq!(actual, expected);
Ok(())
assert_eq!(
contents,
r#"
class A:
...
"#
.trim()
);
assert_eq!(fixed, 2);
}
#[test]
fn ignore_overlapping_fixes() -> Result<()> {
let mut fixes = vec![
fn ignore_overlapping_fixes() {
let fixes = vec![
Fix {
patch: Patch {
content: "".to_string(),
content: String::new(),
location: Location::new(1, 7),
end_location: Location::new(1, 15),
},
applied: false,
},
Fix {
patch: Patch {
@@ -226,22 +225,24 @@ mod tests {
location: Location::new(1, 9),
end_location: Location::new(1, 11),
},
applied: false,
},
];
let locator = SourceCodeLocator::new(
"class A(object):
r#"
class A(object):
...
",
"#
.trim(),
);
let actual = apply_fixes(fixes.iter_mut(), &locator);
let expected = "class A:
let (contents, fixed) = apply_fixes(fixes.iter(), &locator);
assert_eq!(
contents,
r#"
class A:
...
";
assert_eq!(actual, expected);
Ok(())
"#
.trim(),
);
assert_eq!(fixed, 1);
}
}

View File

@@ -4,28 +4,26 @@ use serde::{Deserialize, Serialize};
pub mod fixer;
pub mod helpers;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Patch {
pub content: String,
pub location: Location,
pub end_location: Location,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Fix {
pub patch: Patch,
pub applied: bool,
}
impl Fix {
pub fn deletion(start: Location, end: Location) -> Self {
Self {
patch: Patch {
content: "".to_string(),
content: String::new(),
location: start,
end_location: end,
},
applied: false,
}
}
@@ -36,7 +34,6 @@ impl Fix {
location: start,
end_location: end,
},
applied: false,
}
}
@@ -47,18 +44,16 @@ impl Fix {
location: at,
end_location: at,
},
applied: false,
}
}
pub fn dummy(location: Location) -> Self {
Self {
patch: Patch {
content: "".to_string(),
content: String::new(),
location,
end_location: location,
},
applied: false,
}
}
}

View File

@@ -63,9 +63,10 @@ impl Mode {
impl From<bool> for Mode {
fn from(value: bool) -> Self {
match value {
true => Mode::ReadWrite,
false => Mode::None,
if value {
Mode::ReadWrite
} else {
Mode::None
}
}
}
@@ -177,6 +178,6 @@ pub fn set(
cache_key(path, settings, autofix),
&bincode::serialize(&check_result).unwrap(),
) {
error!("Failed to write to cache: {e:?}")
error!("Failed to write to cache: {e:?}");
}
}

View File

@@ -1,12 +1,11 @@
//! Lint rules based on AST traversal.
use std::collections::BTreeMap;
use std::ops::Deref;
use std::path::Path;
use itertools::Itertools;
use log::error;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::Withitem;
use rustpython_parser::ast::{
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
KeywordData, Operator, Stmt, StmtKind, Suite,
@@ -19,11 +18,10 @@ use crate::ast::helpers::{
use crate::ast::operations::extract_all_names;
use crate::ast::relocate::relocate_expr;
use crate::ast::types::{
Binding, BindingContext, BindingKind, ClassScope, ImportKind, Range, Scope, ScopeKind,
Binding, BindingContext, BindingKind, ClassScope, FunctionScope, Node, Range, Scope, ScopeKind,
};
use crate::ast::visitor::{walk_excepthandler, Visitor};
use crate::ast::visitor::{walk_excepthandler, walk_withitem, Visitor};
use crate::ast::{helpers, operations, visitor};
use crate::autofix::fixer;
use crate::checks::{Check, CheckCode, CheckKind};
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
@@ -33,19 +31,22 @@ use crate::python::typing::SubscriptKind;
use crate::settings::types::PythonVersion;
use crate::settings::Settings;
use crate::source_code_locator::SourceCodeLocator;
use crate::vendored::cformat::{CFormatError, CFormatErrorType};
use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope};
use crate::{
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_print,
flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pyupgrade, rules,
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_debugger,
flake8_print, flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes,
pyupgrade, rules,
};
const GLOBAL_SCOPE_INDEX: usize = 0;
#[allow(clippy::struct_excessive_bools)]
pub struct Checker<'a> {
// Input data.
path: &'a Path,
autofix: &'a fixer::Mode,
autofix: bool,
pub(crate) settings: &'a Settings,
pub(crate) locator: &'a SourceCodeLocator<'a>,
// Computed checks.
@@ -75,18 +76,22 @@ pub struct Checker<'a> {
in_f_string: Option<Range>,
in_annotation: bool,
in_deferred_string_annotation: bool,
in_deferred_annotation: bool,
in_literal: bool,
in_subscript: bool,
in_withitem: bool,
seen_import_boundary: bool,
futures_allowed: bool,
annotations_future_enabled: bool,
except_handlers: Vec<Vec<Vec<&'a str>>>,
// Check-specific state.
pub(crate) seen_b023: Vec<&'a Expr>,
}
impl<'a> Checker<'a> {
pub fn new(
settings: &'a Settings,
autofix: &'a fixer::Mode,
autofix: bool,
path: &'a Path,
locator: &'a SourceCodeLocator,
) -> Checker<'a> {
@@ -95,40 +100,43 @@ impl<'a> Checker<'a> {
autofix,
path,
locator,
checks: Default::default(),
definitions: Default::default(),
deletions: Default::default(),
from_imports: Default::default(),
import_aliases: Default::default(),
parents: Default::default(),
parent_stack: Default::default(),
scopes: Default::default(),
scope_stack: Default::default(),
dead_scopes: Default::default(),
deferred_string_annotations: Default::default(),
deferred_annotations: Default::default(),
deferred_functions: Default::default(),
deferred_lambdas: Default::default(),
deferred_assignments: Default::default(),
checks: vec![],
definitions: vec![],
deletions: FxHashSet::default(),
from_imports: FxHashMap::default(),
import_aliases: FxHashMap::default(),
parents: vec![],
parent_stack: vec![],
scopes: vec![],
scope_stack: vec![],
dead_scopes: vec![],
deferred_string_annotations: vec![],
deferred_annotations: vec![],
deferred_functions: vec![],
deferred_lambdas: vec![],
deferred_assignments: vec![],
// Internal, derivative state.
visible_scope: VisibleScope {
modifier: Modifier::Module,
visibility: module_visibility(path),
},
in_f_string: Default::default(),
in_annotation: Default::default(),
in_deferred_string_annotation: Default::default(),
in_literal: Default::default(),
in_subscript: Default::default(),
seen_import_boundary: Default::default(),
in_f_string: None,
in_annotation: false,
in_deferred_string_annotation: false,
in_deferred_annotation: false,
in_literal: false,
in_subscript: false,
in_withitem: false,
seen_import_boundary: false,
futures_allowed: true,
annotations_future_enabled: Default::default(),
except_handlers: Default::default(),
annotations_future_enabled: false,
except_handlers: vec![],
// Check-specific state.
seen_b023: vec![],
}
}
/// Add a `Check` to the `Checker`.
#[inline(always)]
pub(crate) fn add_check(&mut self, check: Check) {
// If we're in an f-string, override the location. RustPython doesn't produce
// reliable locations for expressions within f-strings, so we use the
@@ -145,7 +153,6 @@ impl<'a> Checker<'a> {
}
/// Add multiple `Check` items to the `Checker`.
#[inline(always)]
pub(crate) fn add_checks(&mut self, checks: impl Iterator<Item = Check>) {
for check in checks {
self.add_check(check);
@@ -157,7 +164,7 @@ impl<'a> Checker<'a> {
pub fn patch(&self, code: &CheckCode) -> bool {
// TODO(charlie): We can't fix errors in f-strings until RustPython adds
// location data.
self.autofix.patch() && self.in_f_string.is_none() && self.settings.fixable.contains(code)
self.autofix && self.in_f_string.is_none() && self.settings.fixable.contains(code)
}
/// Return `true` if the `Expr` is a reference to `typing.${target}`.
@@ -172,6 +179,15 @@ impl<'a> Checker<'a> {
|| (typing::in_extensions(target)
&& match_call_path(call_path, "typing_extensions", target, &self.from_imports))
}
/// Return `true` if `member` is bound as a builtin.
pub fn is_builtin(&self, member: &str) -> bool {
self.current_scopes()
.find_map(|scope| scope.values.get(member))
.map_or(false, |binding| {
matches!(binding.kind, BindingKind::Builtin)
})
}
}
impl<'a, 'b> Visitor<'b> for Checker<'a>
@@ -536,20 +552,20 @@ where
self.check_builtin_shadowing(name, Range::from_located(stmt), false);
for expr in bases {
self.visit_expr(expr)
self.visit_expr(expr);
}
for keyword in keywords {
self.visit_keyword(keyword)
self.visit_keyword(keyword);
}
for expr in decorator_list {
self.visit_expr(expr)
self.visit_expr(expr);
}
self.push_scope(Scope::new(ScopeKind::Class(ClassScope {
name,
bases,
keywords,
decorator_list,
})))
})));
}
StmtKind::Import { names } => {
if self.settings.enabled.contains(&CheckCode::E402) {
@@ -576,9 +592,9 @@ where
self.binding_context(),
),
used: None,
range: Range::from_located(stmt),
range: Range::from_located(alias),
},
)
);
} else {
if let Some(asname) = &alias.node.asname {
self.check_builtin_shadowing(asname, Range::from_located(stmt), false);
@@ -603,8 +619,7 @@ where
.node
.asname
.as_ref()
.map(|asname| asname == &alias.node.name)
.unwrap_or(false)
.map_or(false, |asname| asname == &alias.node.name)
{
Some((
self.scopes[*(self
@@ -612,14 +627,23 @@ where
.last()
.expect("No current scope found."))]
.id,
Range::from_located(stmt),
Range::from_located(alias),
))
} else {
None
},
range: Range::from_located(stmt),
range: Range::from_located(alias),
},
)
);
}
// flake8-debugger
if self.settings.enabled.contains(&CheckCode::T100) {
if let Some(check) =
flake8_debugger::checks::debugger_import(stmt, None, &alias.node.name)
{
self.add_check(check);
}
}
if let Some(asname) = &alias.node.asname {
@@ -690,12 +714,7 @@ where
self.from_imports
.entry(module)
.or_insert_with(FxHashSet::default)
.extend(
names
.iter()
.filter(|alias| alias.node.asname.is_none())
.map(|alias| alias.node.name.as_str()),
)
.extend(names.iter().map(|alias| alias.node.name.as_str()));
}
for alias in names {
if let Some(asname) = &alias.node.asname {
@@ -733,9 +752,9 @@ where
.last()
.expect("No current scope found."))]
.id,
Range::from_located(stmt),
Range::from_located(alias),
)),
range: Range::from_located(stmt),
range: Range::from_located(alias),
},
);
@@ -744,10 +763,10 @@ where
}
if self.settings.enabled.contains(&CheckCode::F407) {
if !ALL_FEATURE_NAMES.contains(&alias.node.name.deref()) {
if !ALL_FEATURE_NAMES.contains(&&*alias.node.name) {
self.add_check(Check::new(
CheckKind::FutureFeatureNotDefined(alias.node.name.to_string()),
Range::from_located(stmt),
Range::from_located(alias),
));
}
}
@@ -807,8 +826,9 @@ where
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
let full_name = match module {
None => alias.node.name.to_string(),
Some(parent) => format!("{}.{}", parent, alias.node.name),
Some(parent) => format!("{parent}.{}", alias.node.name),
};
let range = Range::from_located(alias);
self.add_binding(
name,
Binding {
@@ -823,8 +843,7 @@ where
.node
.asname
.as_ref()
.map(|asname| asname == &alias.node.name)
.unwrap_or(false)
.map_or(false, |asname| asname == &alias.node.name)
{
Some((
self.scopes[*(self
@@ -832,14 +851,14 @@ where
.last()
.expect("No current scope found."))]
.id,
Range::from_located(stmt),
range,
))
} else {
None
},
range: Range::from_located(stmt),
range,
},
)
);
}
if self.settings.enabled.contains(&CheckCode::I252) {
@@ -852,6 +871,17 @@ where
}
}
// flake8-debugger
if self.settings.enabled.contains(&CheckCode::T100) {
if let Some(check) = flake8_debugger::checks::debugger_import(
stmt,
module.as_ref().map(String::as_str),
&alias.node.name,
) {
self.add_check(check);
}
}
if let Some(asname) = &alias.node.asname {
if self.settings.enabled.contains(&CheckCode::N811) {
if let Some(check) =
@@ -940,7 +970,7 @@ where
self,
stmt,
test,
msg.as_ref().map(|expr| expr.deref()),
msg.as_ref().map(|expr| &**expr),
);
}
if self.settings.enabled.contains(&CheckCode::S101) {
@@ -952,8 +982,16 @@ where
flake8_bugbear::plugins::assert_raises_exception(self, stmt, items);
}
}
StmtKind::While { .. } => {
if self.settings.enabled.contains(&CheckCode::B023) {
flake8_bugbear::plugins::function_uses_loop_variable(self, &Node::Stmt(stmt));
}
}
StmtKind::For {
target, body, iter, ..
}
| StmtKind::AsyncFor {
target, body, iter, ..
} => {
if self.settings.enabled.contains(&CheckCode::B007) {
flake8_bugbear::plugins::unused_loop_control_variable(self, target, body);
@@ -961,6 +999,9 @@ where
if self.settings.enabled.contains(&CheckCode::B020) {
flake8_bugbear::plugins::loop_variable_overrides_iterator(self, target, iter);
}
if self.settings.enabled.contains(&CheckCode::B023) {
flake8_bugbear::plugins::function_uses_loop_variable(self, &Node::Stmt(stmt));
}
}
StmtKind::Try { handlers, .. } => {
if self.settings.enabled.contains(&CheckCode::F707) {
@@ -983,7 +1024,7 @@ where
StmtKind::Assign { targets, value, .. } => {
if self.settings.enabled.contains(&CheckCode::E731) {
if let [target] = &targets[..] {
pycodestyle::plugins::do_not_assign_lambda(self, target, value, stmt)
pycodestyle::plugins::do_not_assign_lambda(self, target, value, stmt);
}
}
if self.settings.enabled.contains(&CheckCode::U001) {
@@ -1020,7 +1061,7 @@ where
StmtKind::Delete { .. } => {}
StmtKind::Expr { value, .. } => {
if self.settings.enabled.contains(&CheckCode::B015) {
flake8_bugbear::plugins::useless_comparison(self, value)
flake8_bugbear::plugins::useless_comparison(self, value);
}
}
_ => {}
@@ -1085,7 +1126,7 @@ where
}
self.except_handlers.pop();
for excepthandler in handlers {
self.visit_excepthandler(excepthandler)
self.visit_excepthandler(excepthandler);
}
for stmt in orelse {
self.visit_stmt(stmt);
@@ -1151,10 +1192,14 @@ where
// Pre-visit.
match &expr.node {
ExprKind::Subscript { value, slice, .. } => {
// Ex) typing.List[...]
// Ex) Optional[...]
if !self.in_deferred_string_annotation
&& self.settings.enabled.contains(&CheckCode::U007)
&& self.settings.target_version >= PythonVersion::Py310
&& (self.settings.target_version >= PythonVersion::Py310
|| (self.settings.target_version >= PythonVersion::Py37
&& !self.settings.pyupgrade.keep_runtime_typing
&& self.annotations_future_enabled
&& self.in_deferred_annotation))
{
pyupgrade::plugins::use_pep604_annotation(self, expr, value, slice);
}
@@ -1193,7 +1238,11 @@ where
// Ex) List[...]
if !self.in_deferred_string_annotation
&& self.settings.enabled.contains(&CheckCode::U006)
&& self.settings.target_version >= PythonVersion::Py39
&& (self.settings.target_version >= PythonVersion::Py39
|| (self.settings.target_version >= PythonVersion::Py37
&& !self.settings.pyupgrade.keep_runtime_typing
&& self.annotations_future_enabled
&& self.in_deferred_annotation))
&& typing::is_pep585_builtin(
expr,
&self.from_imports,
@@ -1228,8 +1277,12 @@ where
}
ExprKind::Attribute { attr, .. } => {
// Ex) typing.List[...]
if self.settings.enabled.contains(&CheckCode::U006)
&& self.settings.target_version >= PythonVersion::Py39
if !self.in_deferred_string_annotation
&& self.settings.enabled.contains(&CheckCode::U006)
&& (self.settings.target_version >= PythonVersion::Py39
|| (self.settings.target_version >= PythonVersion::Py37
&& self.annotations_future_enabled
&& self.in_deferred_annotation))
&& typing::is_pep585_builtin(expr, &self.from_imports, &self.import_aliases)
{
pyupgrade::plugins::use_pep585_annotation(self, expr, attr);
@@ -1244,6 +1297,81 @@ where
args,
keywords,
} => {
// pyflakes
if self.settings.enabled.contains(&CheckCode::F521)
|| self.settings.enabled.contains(&CheckCode::F522)
|| self.settings.enabled.contains(&CheckCode::F523)
|| self.settings.enabled.contains(&CheckCode::F524)
|| self.settings.enabled.contains(&CheckCode::F525)
{
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if let ExprKind::Constant {
value: Constant::Str(value),
..
} = &value.node
{
if attr == "format" {
// "...".format(...) call
let location = Range::from_located(expr);
match pyflakes::format::FormatSummary::try_from(value.as_ref()) {
Err(e) => {
if self.settings.enabled.contains(&CheckCode::F521) {
self.add_check(Check::new(
CheckKind::StringDotFormatInvalidFormat(
e.to_string(),
),
location,
));
}
}
Ok(summary) => {
if self.settings.enabled.contains(&CheckCode::F522) {
if let Some(check) =
pyflakes::checks::string_dot_format_extra_named_arguments(
&summary, keywords, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F523) {
if let Some(check) =
pyflakes::checks::string_dot_format_extra_positional_arguments(
&summary, args, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F524) {
if let Some(check) =
pyflakes::checks::string_dot_format_missing_argument(
&summary, args, keywords, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F525) {
if let Some(check) =
pyflakes::checks::string_dot_format_mixing_automatic(
&summary, location,
)
{
self.add_check(check);
}
}
}
}
}
}
}
}
// pyupgrade
if self.settings.enabled.contains(&CheckCode::U005) {
pyupgrade::plugins::deprecated_unittest_alias(self, func);
}
@@ -1523,7 +1651,7 @@ where
// flake8-boolean-trap
if self.settings.enabled.contains(&CheckCode::FBT003) {
flake8_boolean_trap::plugins::check_boolean_positional_value_in_function_call(
self, args,
self, args, func,
);
}
if let ExprKind::Name { id, ctx } = &func.node {
@@ -1536,6 +1664,18 @@ where
}
}
// flake8-debugger
if self.settings.enabled.contains(&CheckCode::T100) {
if let Some(check) = flake8_debugger::checks::debugger_call(
expr,
func,
&self.from_imports,
&self.import_aliases,
) {
self.add_check(check);
}
}
// Ruff
if self.settings.enabled.contains(&CheckCode::RUF101) {
rules::plugins::convert_exit_to_sys_exit(self, func);
@@ -1590,6 +1730,116 @@ where
pyflakes::plugins::invalid_print_syntax(self, left);
}
}
ExprKind::BinOp {
left,
op: Operator::Mod,
right,
} => {
if let ExprKind::Constant {
value: Constant::Str(value),
..
} = &left.node
{
if self.settings.enabled.contains(&CheckCode::F501)
|| self.settings.enabled.contains(&CheckCode::F502)
|| self.settings.enabled.contains(&CheckCode::F503)
|| self.settings.enabled.contains(&CheckCode::F504)
|| self.settings.enabled.contains(&CheckCode::F505)
|| self.settings.enabled.contains(&CheckCode::F506)
|| self.settings.enabled.contains(&CheckCode::F507)
|| self.settings.enabled.contains(&CheckCode::F508)
|| self.settings.enabled.contains(&CheckCode::F509)
{
let location = Range::from_located(expr);
match pyflakes::cformat::CFormatSummary::try_from(value.as_ref()) {
Err(CFormatError {
typ: CFormatErrorType::UnsupportedFormatChar(c),
..
}) => {
if self.settings.enabled.contains(&CheckCode::F509) {
self.add_check(Check::new(
CheckKind::PercentFormatUnsupportedFormatCharacter(c),
location,
));
}
}
Err(e) => {
if self.settings.enabled.contains(&CheckCode::F501) {
self.add_check(Check::new(
CheckKind::PercentFormatInvalidFormat(e.to_string()),
location,
));
}
}
Ok(summary) => {
if self.settings.enabled.contains(&CheckCode::F502) {
if let Some(check) =
pyflakes::checks::percent_format_expected_mapping(
&summary, right, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F503) {
if let Some(check) =
pyflakes::checks::percent_format_expected_sequence(
&summary, right, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F504) {
if let Some(check) =
pyflakes::checks::percent_format_extra_named_arguments(
&summary, right, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F505) {
if let Some(check) =
pyflakes::checks::percent_format_missing_arguments(
&summary, right, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F506) {
if let Some(check) =
pyflakes::checks::percent_format_mixed_positional_and_named(
&summary, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F507) {
if let Some(check) =
pyflakes::checks::percent_format_positional_count_mismatch(
&summary, right, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F508) {
if let Some(check) =
pyflakes::checks::percent_format_star_requires_sequence(
&summary, right, location,
)
{
self.add_check(check);
}
}
}
}
}
}
}
ExprKind::UnaryOp { op, operand } => {
let check_not_in = self.settings.enabled.contains(&CheckCode::E713);
let check_not_is = self.settings.enabled.contains(&CheckCode::E714);
@@ -1624,7 +1874,7 @@ where
comparators,
check_none_comparisons,
check_true_false_comparisons,
)
);
}
if self.settings.enabled.contains(&CheckCode::F632) {
@@ -1721,7 +1971,7 @@ where
for expr in &args.defaults {
self.visit_expr(expr);
}
self.push_scope(Scope::new(ScopeKind::Lambda))
self.push_scope(Scope::new(ScopeKind::Lambda));
}
ExprKind::ListComp { elt, generators } | ExprKind::SetComp { elt, generators } => {
if self.settings.enabled.contains(&CheckCode::C416) {
@@ -1736,10 +1986,16 @@ where
self.add_check(check);
};
}
self.push_scope(Scope::new(ScopeKind::Generator))
if self.settings.enabled.contains(&CheckCode::B023) {
flake8_bugbear::plugins::function_uses_loop_variable(self, &Node::Expr(expr));
}
self.push_scope(Scope::new(ScopeKind::Generator));
}
ExprKind::GeneratorExp { .. } | ExprKind::DictComp { .. } => {
self.push_scope(Scope::new(ScopeKind::Generator))
if self.settings.enabled.contains(&CheckCode::B023) {
flake8_bugbear::plugins::function_uses_loop_variable(self, &Node::Expr(expr));
}
self.push_scope(Scope::new(ScopeKind::Generator));
}
_ => {}
};
@@ -1869,6 +2125,7 @@ where
value,
&self.from_imports,
&self.import_aliases,
|member| self.is_builtin(member),
) {
Some(subscript) => {
match subscript {
@@ -1898,7 +2155,7 @@ where
error!(
"Found non-ExprKind::Tuple argument to PEP 593 \
Annotation."
)
);
}
}
}
@@ -1930,13 +2187,20 @@ where
fn visit_excepthandler(&mut self, excepthandler: &'b Excepthandler) {
match &excepthandler.node {
ExcepthandlerKind::ExceptHandler { type_, name, .. } => {
ExcepthandlerKind::ExceptHandler {
type_, name, body, ..
} => {
if self.settings.enabled.contains(&CheckCode::E722) && type_.is_none() {
self.add_check(Check::new(
CheckKind::DoNotUseBareExcept,
Range::from_located(excepthandler),
));
}
if self.settings.enabled.contains(&CheckCode::B904) {
{
flake8_bugbear::plugins::raise_without_from_inside_except(self, body);
}
}
match name {
Some(name) => {
if self.settings.enabled.contains(&CheckCode::E741) {
@@ -2018,10 +2282,10 @@ where
.extend(pyflakes::checks::duplicate_arguments(arguments));
}
if self.settings.enabled.contains(&CheckCode::B006) {
flake8_bugbear::plugins::mutable_argument_default(self, arguments)
flake8_bugbear::plugins::mutable_argument_default(self, arguments);
}
if self.settings.enabled.contains(&CheckCode::B008) {
flake8_bugbear::plugins::function_call_argument_default(self, arguments)
flake8_bugbear::plugins::function_call_argument_default(self, arguments);
}
// flake8-boolean-trap
@@ -2084,6 +2348,13 @@ where
self.check_builtin_arg_shadowing(&arg.node.arg, Range::from_located(arg));
}
fn visit_withitem(&mut self, withitem: &'b Withitem) {
let prev_in_withitem = self.in_withitem;
self.in_withitem = true;
walk_withitem(self, withitem);
self.in_withitem = prev_in_withitem;
}
}
fn try_mark_used(scope: &mut Scope, scope_id: usize, id: &str, expr: &Expr) -> bool {
@@ -2161,7 +2432,7 @@ impl<'a> Checker<'a> {
builtin,
Binding {
kind: BindingKind::Builtin,
range: Default::default(),
range: Range::default(),
used: None,
},
);
@@ -2171,7 +2442,7 @@ impl<'a> Checker<'a> {
builtin,
Binding {
kind: BindingKind::Builtin,
range: Default::default(),
range: Range::default(),
used: None,
},
);
@@ -2193,7 +2464,7 @@ impl<'a> Checker<'a> {
pub fn binding_context(&self) -> BindingContext {
let mut rev = self.parent_stack.iter().rev().fuse();
let defined_by = *rev.next().expect("Expected to bind within a statement.");
let defined_in = rev.next().cloned();
let defined_in = rev.next().copied();
BindingContext {
defined_by,
defined_in,
@@ -2314,7 +2585,7 @@ impl<'a> Checker<'a> {
self.add_check(Check::new(
CheckKind::UndefinedName(id.clone()),
Range::from_located(expr),
))
));
}
}
}
@@ -2341,23 +2612,24 @@ impl<'a> Checker<'a> {
.current_scope()
.values
.get(id)
.map(|binding| matches!(binding.kind, BindingKind::Global))
.unwrap_or(false)
.map_or(false, |binding| matches!(binding.kind, BindingKind::Global))
{
pep8_naming::plugins::non_lowercase_variable_in_function(self, expr, parent, id)
pep8_naming::plugins::non_lowercase_variable_in_function(
self, expr, parent, id,
);
}
}
}
if self.settings.enabled.contains(&CheckCode::N815) {
if matches!(self.current_scope().kind, ScopeKind::Class(..)) {
pep8_naming::plugins::mixed_case_variable_in_class_scope(self, expr, parent, id)
pep8_naming::plugins::mixed_case_variable_in_class_scope(self, expr, parent, id);
}
}
if self.settings.enabled.contains(&CheckCode::N816) {
if matches!(self.current_scope().kind, ScopeKind::Module) {
pep8_naming::plugins::mixed_case_variable_in_global_scope(self, expr, parent, id)
pep8_naming::plugins::mixed_case_variable_in_global_scope(self, expr, parent, id);
}
}
@@ -2389,7 +2661,7 @@ impl<'a> Checker<'a> {
return;
}
if operations::is_unpacking_assignment(parent) {
if self.in_withitem || operations::is_unpacking_assignment(parent) {
self.add_binding(
id,
Binding {
@@ -2453,7 +2725,7 @@ impl<'a> Checker<'a> {
self.add_check(Check::new(
CheckKind::UndefinedName(id.to_string()),
Range::from_located(expr),
))
));
}
}
}
@@ -2484,7 +2756,9 @@ impl<'a> Checker<'a> {
while let Some((expr, scopes, parents)) = self.deferred_annotations.pop() {
self.scope_stack = scopes;
self.parent_stack = parents;
self.in_deferred_annotation = true;
self.visit_expr(expr);
self.in_deferred_annotation = false;
}
}
@@ -2510,9 +2784,9 @@ impl<'a> Checker<'a> {
}
}
for (expr, (scopes, parents)) in allocator.iter().zip(stacks) {
self.in_deferred_string_annotation = true;
self.scope_stack = scopes;
self.parent_stack = parents;
self.in_deferred_string_annotation = true;
self.visit_expr(expr);
self.in_deferred_string_annotation = false;
}
@@ -2523,7 +2797,7 @@ impl<'a> Checker<'a> {
self.parent_stack = parents;
self.scope_stack = scopes;
self.visible_scope = visibility;
self.push_scope(Scope::new(ScopeKind::Function(Default::default())));
self.push_scope(Scope::new(ScopeKind::Function(FunctionScope::default())));
match &stmt.node {
StmtKind::FunctionDef { body, args, .. }
@@ -2588,9 +2862,7 @@ impl<'a> Checker<'a> {
let all_binding: Option<&Binding> = scope.values.get("__all__");
let all_names: Option<Vec<&str>> =
all_binding.and_then(|binding| match &binding.kind {
BindingKind::Export(names) => {
Some(names.iter().map(|name| name.as_str()).collect())
}
BindingKind::Export(names) => Some(names.iter().map(String::as_str).collect()),
_ => None,
});
@@ -2645,68 +2917,54 @@ impl<'a> Checker<'a> {
if self.settings.enabled.contains(&CheckCode::F401) {
// Collect all unused imports by location. (Multiple unused imports at the same
// location indicates an `import from`.)
let mut unused: BTreeMap<(ImportKind, usize, Option<usize>), Vec<&str>> =
type UnusedImport<'a> = (&'a String, &'a Range);
let mut unused: BTreeMap<(usize, Option<usize>), Vec<UnusedImport>> =
BTreeMap::new();
for (name, binding) in scope.values.iter() {
if !matches!(
binding.kind,
BindingKind::Importation(..)
| BindingKind::SubmoduleImportation(..)
| BindingKind::FromImportation(..)
) {
continue;
}
for (name, binding) in &scope.values {
let (full_name, context) = match &binding.kind {
BindingKind::Importation(_, full_name, context)
| BindingKind::SubmoduleImportation(_, full_name, context)
| BindingKind::FromImportation(_, full_name, context) => {
(full_name, context)
}
_ => continue,
};
let used = binding.used.is_some()
// Skip used exports from `__all__`
if binding.used.is_some()
|| all_names
.as_ref()
.map(|names| names.contains(name))
.unwrap_or_default();
if !used {
match &binding.kind {
BindingKind::FromImportation(_, full_name, context) => {
unused
.entry((
ImportKind::ImportFrom,
context.defined_by,
context.defined_in,
))
.or_default()
.push(full_name);
}
BindingKind::Importation(_, full_name, context)
| BindingKind::SubmoduleImportation(_, full_name, context) => {
unused
.entry((
ImportKind::Import,
context.defined_by,
context.defined_in,
))
.or_default()
.push(full_name);
}
_ => unreachable!("Already filtered on BindingKind."),
}
.unwrap_or_default()
{
continue;
}
unused
.entry((context.defined_by, context.defined_in))
.or_default()
.push((full_name, &binding.range));
}
for ((kind, defined_by, defined_in), full_names) in unused {
for ((defined_by, defined_in), unused_imports) in unused {
let child = self.parents[defined_by];
let parent = defined_in.map(|defined_in| self.parents[defined_in]);
let fix = if self.patch(&CheckCode::F401) {
let in_init_py = self.path.ends_with("__init__.py");
let fix = if !in_init_py && self.patch(&CheckCode::F401) {
let deleted: Vec<&Stmt> = self
.deletions
.iter()
.map(|index| self.parents[*index])
.collect();
match match kind {
ImportKind::Import => pyflakes::fixes::remove_unused_imports,
ImportKind::ImportFrom => pyflakes::fixes::remove_unused_import_froms,
}(
self.locator, &full_names, child, parent, &deleted
match pyflakes::fixes::remove_unused_imports(
self.locator,
&unused_imports,
child,
parent,
&deleted,
) {
Ok(fix) => {
if fix.patch.content.is_empty() || fix.patch.content == "pass" {
@@ -2723,24 +2981,13 @@ impl<'a> Checker<'a> {
None
};
if self.path.ends_with("__init__.py") {
checks.push(Check::new(
CheckKind::UnusedImport(
full_names.into_iter().sorted().map(String::from).collect(),
true,
),
Range::from_located(child),
));
} else {
for (full_name, range) in unused_imports {
let mut check = Check::new(
CheckKind::UnusedImport(
full_names.into_iter().sorted().map(String::from).collect(),
false,
),
Range::from_located(child),
CheckKind::UnusedImport(full_name.clone(), in_init_py),
*range,
);
if let Some(fix) = fix {
check.amend(fix);
if let Some(fix) = fix.as_ref() {
check.amend(fix.clone());
}
checks.push(check);
}
@@ -2892,7 +3139,7 @@ pub fn check_ast(
python_ast: &Suite,
locator: &SourceCodeLocator,
settings: &Settings,
autofix: &fixer::Mode,
autofix: bool,
path: &Path,
) -> Vec<Check> {
let mut checker = Checker::new(settings, autofix, path, locator);

View File

@@ -4,7 +4,6 @@ use nohash_hasher::IntSet;
use rustpython_parser::ast::Suite;
use crate::ast::visitor::Visitor;
use crate::autofix::fixer;
use crate::checks::Check;
use crate::isort;
use crate::isort::track::ImportTracker;
@@ -15,12 +14,12 @@ fn check_import_blocks(
tracker: ImportTracker,
locator: &SourceCodeLocator,
settings: &Settings,
autofix: &fixer::Mode,
autofix: bool,
) -> Vec<Check> {
let mut checks = vec![];
for block in tracker.into_iter() {
if !block.is_empty() {
if let Some(check) = isort::plugins::check_imports(block, locator, settings, autofix) {
if let Some(check) = isort::plugins::check_imports(&block, locator, settings, autofix) {
checks.push(check);
}
}
@@ -33,7 +32,7 @@ pub fn check_imports(
locator: &SourceCodeLocator,
exclusions: &IntSet<usize>,
settings: &Settings,
autofix: &fixer::Mode,
autofix: bool,
) -> Vec<Check> {
let mut tracker = ImportTracker::new(exclusions);
for stmt in python_ast {

View File

@@ -6,7 +6,7 @@ use regex::Regex;
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::autofix::{fixer, Fix};
use crate::autofix::Fix;
use crate::checks::{Check, CheckCode, CheckKind};
use crate::noqa;
use crate::noqa::Directive;
@@ -14,15 +14,18 @@ use crate::settings::Settings;
// Regex from PEP263
static CODING_COMMENT_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^[ \t\f]*#.*?coding[:=][ \t]*utf-?8").expect("Invalid regex"));
Lazy::new(|| Regex::new(r"^[ \t\f]*#.*?coding[:=][ \t]*utf-?8").unwrap());
static URL_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^https?://\S+$").unwrap());
/// Whether the given line is too long and should be reported.
fn should_enforce_line_length(line: &str, length: usize, limit: usize) -> bool {
if length > limit {
let mut chunks = line.split_whitespace();
if let (Some(first), Some(_)) = (chunks.next(), chunks.next()) {
// Do not enforce the line length for commented lines with a single word
!(first == "#" && chunks.next().is_none())
// Do not enforce the line length for commented lines that end with a URL
// or contain only a single word.
!(first == "#" && chunks.last().map_or(true, |c| URL_REGEX.is_match(c)))
} else {
// Single word / no printable chars - no way to make the line shorter
false
@@ -37,7 +40,8 @@ pub fn check_lines(
contents: &str,
noqa_line_for: &IntMap<usize, usize>,
settings: &Settings,
autofix: &fixer::Mode,
autofix: bool,
ignore_noqa: bool,
) {
let enforce_unnecessary_coding_comment = settings.enabled.contains(&CheckCode::U009);
let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501);
@@ -53,6 +57,30 @@ pub fn check_lines(
assert!(check.location.row() >= 1);
}
macro_rules! add_if {
($check:expr, $noqa:expr) => {{
match $noqa {
(Directive::All(..), matches) => {
matches.push($check.kind.code().as_ref());
if ignore_noqa {
line_checks.push($check);
}
}
(Directive::Codes(.., codes), matches) => {
if codes.contains(&$check.kind.code().as_ref()) {
matches.push($check.kind.code().as_ref());
if ignore_noqa {
line_checks.push($check);
}
} else {
line_checks.push($check);
}
}
(Directive::None, ..) => line_checks.push($check),
}
}};
}
let lines: Vec<&str> = contents.lines().collect();
for (lineno, line) in lines.iter().enumerate() {
// Grab the noqa (logical) line number for the current (physical) line.
@@ -65,21 +93,24 @@ pub fn check_lines(
if lineno < 2 {
// PEP3120 makes utf-8 the default encoding.
if CODING_COMMENT_REGEX.is_match(line) {
let line_length = line.len();
let mut check = Check::new(
CheckKind::PEP3120UnnecessaryCodingComment,
Range {
location: Location::new(lineno + 1, 0),
end_location: Location::new(lineno + 1, line_length + 1),
end_location: Location::new(lineno + 2, 0),
},
);
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
if autofix && settings.fixable.contains(check.kind.code()) {
check.amend(Fix::deletion(
Location::new(lineno + 1, 0),
Location::new(lineno + 1, line_length + 1),
Location::new(lineno + 2, 0),
));
}
line_checks.push(check);
let noqa = noqa_directives.entry(noqa_lineno).or_insert_with(|| {
(noqa::extract_noqa_directive(lines[noqa_lineno]), vec![])
});
add_if!(check, noqa);
}
}
}
@@ -101,7 +132,7 @@ pub fn check_lines(
match noqa {
(Directive::All(..), matches) => {
matches.push(check.kind.code().as_ref());
ignored.push(index)
ignored.push(index);
}
(Directive::Codes(.., codes), matches) => {
if codes.contains(&check.kind.code().as_ref()) {
@@ -109,7 +140,7 @@ pub fn check_lines(
ignored.push(index);
}
}
(Directive::None, _) => {}
(Directive::None, ..) => {}
}
}
@@ -117,10 +148,6 @@ pub fn check_lines(
if enforce_line_too_long {
let line_length = line.chars().count();
if should_enforce_line_length(line, line_length, settings.line_length) {
let noqa = noqa_directives
.entry(noqa_lineno)
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
let check = Check::new(
CheckKind::LineTooLong(line_length, settings.line_length),
Range {
@@ -129,35 +156,19 @@ pub fn check_lines(
},
);
match noqa {
(Directive::All(..), matches) => {
matches.push(check.kind.code().as_ref());
}
(Directive::Codes(.., codes), matches) => {
if codes.contains(&check.kind.code().as_ref()) {
matches.push(check.kind.code().as_ref());
} else {
line_checks.push(check);
}
}
(Directive::None, _) => line_checks.push(check),
}
let noqa = noqa_directives
.entry(noqa_lineno)
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
add_if!(check, noqa);
}
}
}
// Enforce newlines at end of files.
// Enforce newlines at end of files (W292).
if settings.enabled.contains(&CheckCode::W292) && !contents.ends_with('\n') {
// Note: if `lines.last()` is `None`, then `contents` is empty (and so we don't
// want to raise W292 anyway).
if let Some(line) = lines.last() {
let lineno = lines.len() - 1;
let noqa_lineno = noqa_line_for.get(&(lineno + 1)).unwrap_or(&(lineno + 1)) - 1;
let noqa = noqa_directives
.entry(noqa_lineno)
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
let check = Check::new(
CheckKind::NoNewLineAtEndOfFile,
Range {
@@ -166,23 +177,16 @@ pub fn check_lines(
},
);
match noqa {
(Directive::All(..), matches) => {
matches.push(check.kind.code().as_ref());
}
(Directive::Codes(.., codes), matches) => {
if codes.contains(&check.kind.code().as_ref()) {
matches.push(check.kind.code().as_ref());
} else {
line_checks.push(check);
}
}
(Directive::None, _) => line_checks.push(check),
}
let lineno = lines.len() - 1;
let noqa_lineno = noqa_line_for.get(&(lineno + 1)).unwrap_or(&(lineno + 1)) - 1;
let noqa = noqa_directives
.entry(noqa_lineno)
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
add_if!(check, noqa);
}
}
// Enforce that the noqa directive was actually used.
// Enforce that the noqa directive was actually used (M001).
if enforce_noqa {
for (row, (directive, matches)) in noqa_directives {
match directive {
@@ -195,7 +199,7 @@ pub fn check_lines(
end_location: Location::new(row + 1, end),
},
);
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
if autofix && settings.fixable.contains(check.kind.code()) {
check.amend(Fix::deletion(
Location::new(row + 1, start - spaces),
Location::new(row + 1, lines[row].chars().count()),
@@ -208,10 +212,10 @@ pub fn check_lines(
let mut invalid_codes = vec![];
let mut valid_codes = vec![];
for code in codes {
if !matches.contains(&code) {
invalid_codes.push(code.to_string());
} else {
if matches.contains(&code) || settings.external.contains(code) {
valid_codes.push(code.to_string());
} else {
invalid_codes.push(code.to_string());
}
}
@@ -223,7 +227,7 @@ pub fn check_lines(
end_location: Location::new(row + 1, end),
},
);
if autofix.patch() && settings.fixable.contains(check.kind.code()) {
if autofix && settings.fixable.contains(check.kind.code()) {
if valid_codes.is_empty() {
check.amend(Fix::deletion(
Location::new(row + 1, start - spaces),
@@ -245,9 +249,11 @@ pub fn check_lines(
}
}
ignored.sort_unstable();
for index in ignored.iter().rev() {
checks.swap_remove(*index);
if !ignore_noqa {
ignored.sort_unstable();
for index in ignored.iter().rev() {
checks.swap_remove(*index);
}
}
checks.extend(line_checks);
}
@@ -257,14 +263,13 @@ mod tests {
use nohash_hasher::IntMap;
use super::check_lines;
use crate::autofix::fixer;
use crate::checks::{Check, CheckCode};
use crate::settings::Settings;
#[test]
fn e501_non_ascii_char() {
let line = "'\u{4e9c}' * 2"; // 7 in UTF-32, 9 in UTF-8.
let noqa_line_for: IntMap<usize, usize> = Default::default();
let noqa_line_for: IntMap<usize, usize> = IntMap::default();
let check_with_max_line_length = |line_length: usize| {
let mut checks: Vec<Check> = vec![];
check_lines(
@@ -275,7 +280,8 @@ mod tests {
line_length,
..Settings::for_rule(CheckCode::E501)
},
&fixer::Mode::Generate,
true,
false,
);
checks
};

View File

@@ -2,20 +2,20 @@
use rustpython_parser::lexer::{LexResult, Tok};
use crate::autofix::fixer;
use crate::checks::{Check, CheckCode};
use crate::lex::docstring_detection::StateMachine;
use crate::rules::checks::Context;
use crate::source_code_locator::SourceCodeLocator;
use crate::{flake8_quotes, pycodestyle, rules, Settings};
use crate::{eradicate, flake8_quotes, pycodestyle, rules, Settings};
pub fn check_tokens(
checks: &mut Vec<Check>,
locator: &SourceCodeLocator,
tokens: &[LexResult],
settings: &Settings,
autofix: &fixer::Mode,
) {
autofix: bool,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
let enforce_ambiguous_unicode_character = settings.enabled.contains(&CheckCode::RUF001)
|| settings.enabled.contains(&CheckCode::RUF002)
|| settings.enabled.contains(&CheckCode::RUF003);
@@ -23,9 +23,10 @@ pub fn check_tokens(
|| settings.enabled.contains(&CheckCode::Q001)
|| settings.enabled.contains(&CheckCode::Q002)
|| settings.enabled.contains(&CheckCode::Q003);
let enforce_commented_out_code = settings.enabled.contains(&CheckCode::ERA001);
let enforce_invalid_escape_sequence = settings.enabled.contains(&CheckCode::W605);
let mut state_machine: StateMachine = Default::default();
let mut state_machine = StateMachine::default();
for &(start, ref tok, end) in tokens.iter().flatten() {
let is_docstring = if enforce_ambiguous_unicode_character || enforce_quotes {
state_machine.consume(tok)
@@ -72,6 +73,17 @@ pub fn check_tokens(
}
}
// eradicate
if enforce_commented_out_code {
if matches!(tok, Tok::Comment) {
if let Some(check) =
eradicate::checks::commented_out_code(locator, start, end, settings, autofix)
{
checks.push(check);
}
}
}
// W605
if enforce_invalid_escape_sequence {
if matches!(tok, Tok::String { .. }) {
@@ -81,4 +93,6 @@ pub fn check_tokens(
}
}
}
checks
}

View File

@@ -7,6 +7,7 @@ use strum_macros::{AsRefStr, EnumIter, EnumString};
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::flake8_debugger::types::DebuggerUsingType;
use crate::flake8_quotes::settings::Quote;
use crate::flake8_tidy_imports::settings::Strictness;
use crate::pyupgrade::types::Primitive;
@@ -52,6 +53,20 @@ pub enum CheckCode {
F405,
F406,
F407,
F501,
F502,
F503,
F504,
F505,
F506,
F507,
F508,
F509,
F521,
F522,
F523,
F524,
F525,
F541,
F601,
F602,
@@ -99,10 +114,12 @@ pub enum CheckCode {
B020,
B021,
B022,
B023,
B024,
B025,
B026,
B027,
B904,
// flake8-blind-except
BLE001,
// flake8-comprehensions
@@ -122,6 +139,8 @@ pub enum CheckCode {
C415,
C416,
C417,
// flake8-debugger
T100,
// mccabe
C901,
// flake8-tidy-imports
@@ -235,6 +254,8 @@ pub enum CheckCode {
N818,
// isort
I001,
// eradicate
ERA001,
// flake8-bandit
S101,
S102,
@@ -263,8 +284,10 @@ pub enum CheckCategory {
Pydocstyle,
Pyupgrade,
PEP8Naming,
Eradicate,
Flake8Bandit,
Flake8Comprehensions,
Flake8Debugger,
Flake8BooleanTrap,
Flake8Bugbear,
Flake8Builtins,
@@ -282,65 +305,71 @@ pub enum CheckCategory {
impl CheckCategory {
pub fn title(&self) -> &'static str {
match self {
CheckCategory::Pycodestyle => "pycodestyle",
CheckCategory::Pyflakes => "Pyflakes",
CheckCategory::Isort => "isort",
CheckCategory::Eradicate => "eradicate",
CheckCategory::Flake82020 => "flake8-2020",
CheckCategory::Flake8Annotations => "flake8-annotations",
CheckCategory::Flake8Bandit => "flake8-bandit",
CheckCategory::Flake8BlindExcept => "flake8-blind-except",
CheckCategory::Flake8BooleanTrap => "flake8-boolean-trap",
CheckCategory::Flake8Builtins => "flake8-builtins",
CheckCategory::Flake8Bugbear => "flake8-bugbear",
CheckCategory::Flake8Builtins => "flake8-builtins",
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
CheckCategory::Flake8TidyImports => "flake8-tidy-imports",
CheckCategory::Flake8Debugger => "flake8-debugger",
CheckCategory::Flake8Print => "flake8-print",
CheckCategory::Flake8Quotes => "flake8-quotes",
CheckCategory::Flake8Annotations => "flake8-annotations",
CheckCategory::Flake82020 => "flake8-2020",
CheckCategory::Flake8BlindExcept => "flake8-blind-except",
CheckCategory::Pyupgrade => "pyupgrade",
CheckCategory::Pydocstyle => "pydocstyle",
CheckCategory::PEP8Naming => "pep8-naming",
CheckCategory::Flake8TidyImports => "flake8-tidy-imports",
CheckCategory::Isort => "isort",
CheckCategory::McCabe => "mccabe",
CheckCategory::Ruff => "Ruff-specific rules",
CheckCategory::Meta => "Meta rules",
CheckCategory::PEP8Naming => "pep8-naming",
CheckCategory::Pycodestyle => "pycodestyle",
CheckCategory::Pydocstyle => "pydocstyle",
CheckCategory::Pyflakes => "Pyflakes",
CheckCategory::Pyupgrade => "pyupgrade",
CheckCategory::Ruff => "Ruff-specific rules",
}
}
pub fn url(&self) -> Option<&'static str> {
match self {
CheckCategory::Pycodestyle => Some("https://pypi.org/project/pycodestyle/2.9.1/"),
CheckCategory::Pyflakes => Some("https://pypi.org/project/pyflakes/2.5.0/"),
CheckCategory::Isort => Some("https://pypi.org/project/isort/5.10.1/"),
CheckCategory::Flake8Builtins => {
Some("https://pypi.org/project/flake8-builtins/2.0.1/")
}
CheckCategory::Flake8Bugbear => {
Some("https://pypi.org/project/flake8-bugbear/22.10.27/")
}
CheckCategory::Flake8Comprehensions => {
Some("https://pypi.org/project/flake8-comprehensions/3.10.1/")
}
CheckCategory::Flake8TidyImports => {
Some("https://pypi.org/project/flake8-tidy-imports/4.8.0/")
}
CheckCategory::Flake8Print => Some("https://pypi.org/project/flake8-print/5.0.0/"),
CheckCategory::Flake8Quotes => Some("https://pypi.org/project/flake8-quotes/3.3.1/"),
CheckCategory::Eradicate => Some("https://pypi.org/project/eradicate/2.1.0/"),
CheckCategory::Flake82020 => Some("https://pypi.org/project/flake8-2020/1.7.0/"),
CheckCategory::Flake8Annotations => {
Some("https://pypi.org/project/flake8-annotations/2.9.1/")
}
CheckCategory::Flake82020 => Some("https://pypi.org/project/flake8-2020/1.7.0/"),
CheckCategory::Pyupgrade => Some("https://pypi.org/project/pyupgrade/3.2.0/"),
CheckCategory::Pydocstyle => Some("https://pypi.org/project/pydocstyle/6.1.1/"),
CheckCategory::PEP8Naming => Some("https://pypi.org/project/pep8-naming/0.13.2/"),
CheckCategory::Flake8Bandit => Some("https://pypi.org/project/flake8-bandit/4.1.1/"),
CheckCategory::Flake8BlindExcept => {
Some("https://pypi.org/project/flake8-blind-except/0.2.1/")
}
CheckCategory::McCabe => Some("https://pypi.org/project/mccabe/0.7.0/"),
CheckCategory::Flake8BooleanTrap => {
Some("https://pypi.org/project/flake8-boolean-trap/0.1.0/")
}
CheckCategory::Ruff => None,
CheckCategory::Flake8Bugbear => {
Some("https://pypi.org/project/flake8-bugbear/22.10.27/")
}
CheckCategory::Flake8Builtins => {
Some("https://pypi.org/project/flake8-builtins/2.0.1/")
}
CheckCategory::Flake8Comprehensions => {
Some("https://pypi.org/project/flake8-comprehensions/3.10.1/")
}
CheckCategory::Flake8Debugger => {
Some("https://pypi.org/project/flake8-debugger/4.1.2/")
}
CheckCategory::Flake8Print => Some("https://pypi.org/project/flake8-print/5.0.0/"),
CheckCategory::Flake8Quotes => Some("https://pypi.org/project/flake8-quotes/3.3.1/"),
CheckCategory::Flake8TidyImports => {
Some("https://pypi.org/project/flake8-tidy-imports/4.8.0/")
}
CheckCategory::Isort => Some("https://pypi.org/project/isort/5.10.1/"),
CheckCategory::McCabe => Some("https://pypi.org/project/mccabe/0.7.0/"),
CheckCategory::Meta => None,
CheckCategory::PEP8Naming => Some("https://pypi.org/project/pep8-naming/0.13.2/"),
CheckCategory::Pycodestyle => Some("https://pypi.org/project/pycodestyle/2.9.1/"),
CheckCategory::Pydocstyle => Some("https://pypi.org/project/pydocstyle/6.1.1/"),
CheckCategory::Pyflakes => Some("https://pypi.org/project/pyflakes/2.5.0/"),
CheckCategory::Pyupgrade => Some("https://pypi.org/project/pyupgrade/3.2.0/"),
CheckCategory::Ruff => None,
}
}
}
@@ -400,13 +429,27 @@ pub enum CheckKind {
LateFutureImport,
MultiValueRepeatedKeyLiteral,
MultiValueRepeatedKeyVariable(String),
PercentFormatExpectedMapping,
PercentFormatExpectedSequence,
PercentFormatExtraNamedArguments(Vec<String>),
PercentFormatInvalidFormat(String),
PercentFormatMissingArgument(Vec<String>),
PercentFormatMixedPositionalAndNamed,
PercentFormatPositionalCountMismatch(usize, usize),
PercentFormatStarRequiresSequence,
PercentFormatUnsupportedFormatCharacter(char),
RaiseNotImplemented,
ReturnOutsideFunction,
StringDotFormatExtraNamedArguments(Vec<String>),
StringDotFormatExtraPositionalArguments(Vec<String>),
StringDotFormatInvalidFormat(String),
StringDotFormatMissingArguments(Vec<String>),
StringDotFormatMixingAutomatic,
TwoStarredExpressions,
UndefinedExport(String),
UndefinedLocal(String),
UndefinedName(String),
UnusedImport(Vec<String>, bool),
UnusedImport(String, bool),
UnusedVariable(String),
YieldOutsideFunction,
// flake8-builtins
@@ -416,31 +459,33 @@ pub enum CheckKind {
// flake8-blind-except
BlindExcept,
// flake8-bugbear
UnaryPrefixIncrement,
AssignmentToOsEnviron,
UnreliableCallableCheck,
StripWithMultiCharacters,
MutableArgumentDefault,
UnusedLoopControlVariable(String),
FunctionCallArgumentDefault(Option<String>),
GetAttrWithConstant,
SetAttrWithConstant,
DoNotAssertFalse,
JumpStatementInFinally(String),
RedundantTupleInExceptionHandler(String),
DuplicateHandlerException(Vec<String>),
UselessComparison,
CannotRaiseLiteral,
NoAssertRaisesException,
UselessExpression,
CachedInstanceMethod,
LoopVariableOverridesIterator(String),
FStringDocstring,
UselessContextlibSuppress,
AbstractBaseClassWithoutAbstractMethod(String),
AssignmentToOsEnviron,
CachedInstanceMethod,
CannotRaiseLiteral,
DoNotAssertFalse,
DuplicateHandlerException(Vec<String>),
DuplicateTryBlockException(String),
StarArgUnpackingAfterKeywordArg,
EmptyMethodWithoutAbstractDecorator(String),
FStringDocstring,
FunctionCallArgumentDefault(Option<String>),
FunctionUsesLoopVariable(String),
GetAttrWithConstant,
JumpStatementInFinally(String),
LoopVariableOverridesIterator(String),
MutableArgumentDefault,
NoAssertRaisesException,
RaiseWithoutFromInsideExcept,
RedundantTupleInExceptionHandler(String),
SetAttrWithConstant,
StarArgUnpackingAfterKeywordArg,
StripWithMultiCharacters,
UnaryPrefixIncrement,
UnreliableCallableCheck,
UnusedLoopControlVariable(String),
UselessComparison,
UselessContextlibSuppress,
UselessExpression,
// flake8-comprehensions
UnnecessaryGeneratorList,
UnnecessaryGeneratorSet,
@@ -458,6 +503,8 @@ pub enum CheckKind {
UnnecessarySubscriptReversal(String),
UnnecessaryComprehension(String),
UnnecessaryMap(String),
// flake8-debugger
Debugger(DebuggerUsingType),
// flake8-tidy-imports
BannedRelativeImport(Strictness),
// flake8-print
@@ -569,6 +616,8 @@ pub enum CheckKind {
ErrorSuffixOnExceptionName(String),
// isort
UnsortedImports,
// eradicate
CommentedOutCode,
// flake8-bandit
AssertUsed,
ExecUsed,
@@ -599,7 +648,8 @@ impl CheckCode {
CheckCode::E501 | CheckCode::W292 | CheckCode::M001 | CheckCode::U009 => {
&LintSource::Lines
}
CheckCode::Q000
CheckCode::ERA001
| CheckCode::Q000
| CheckCode::Q001
| CheckCode::Q002
| CheckCode::Q003
@@ -613,7 +663,7 @@ impl CheckCode {
}
}
/// A placeholder representation of the CheckKind for the check.
/// A placeholder representation of the `CheckKind` for the check.
pub fn kind(&self) -> CheckKind {
match self {
// pycodestyle errors
@@ -635,7 +685,7 @@ impl CheckCode {
CheckCode::W292 => CheckKind::NoNewLineAtEndOfFile,
CheckCode::W605 => CheckKind::InvalidEscapeSequence('c'),
// pyflakes
CheckCode::F401 => CheckKind::UnusedImport(vec!["...".to_string()], false),
CheckCode::F401 => CheckKind::UnusedImport("...".to_string(), false),
CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1),
CheckCode::F403 => CheckKind::ImportStarUsed("...".to_string()),
CheckCode::F404 => CheckKind::LateFutureImport,
@@ -644,6 +694,24 @@ impl CheckCode {
}
CheckCode::F406 => CheckKind::ImportStarNotPermitted("...".to_string()),
CheckCode::F407 => CheckKind::FutureFeatureNotDefined("...".to_string()),
CheckCode::F501 => CheckKind::PercentFormatInvalidFormat("...".to_string()),
CheckCode::F502 => CheckKind::PercentFormatExpectedMapping,
CheckCode::F503 => CheckKind::PercentFormatExpectedSequence,
CheckCode::F504 => CheckKind::PercentFormatExtraNamedArguments(vec!["...".to_string()]),
CheckCode::F505 => CheckKind::PercentFormatMissingArgument(vec!["...".to_string()]),
CheckCode::F506 => CheckKind::PercentFormatMixedPositionalAndNamed,
CheckCode::F507 => CheckKind::PercentFormatPositionalCountMismatch(4, 2),
CheckCode::F508 => CheckKind::PercentFormatStarRequiresSequence,
CheckCode::F509 => CheckKind::PercentFormatUnsupportedFormatCharacter('c'),
CheckCode::F521 => CheckKind::StringDotFormatInvalidFormat("...".to_string()),
CheckCode::F522 => {
CheckKind::StringDotFormatExtraNamedArguments(vec!["...".to_string()])
}
CheckCode::F523 => {
CheckKind::StringDotFormatExtraPositionalArguments(vec!["...".to_string()])
}
CheckCode::F524 => CheckKind::StringDotFormatMissingArguments(vec!["...".to_string()]),
CheckCode::F525 => CheckKind::StringDotFormatMixingAutomatic,
CheckCode::F541 => CheckKind::FStringMissingPlaceholders,
CheckCode::F601 => CheckKind::MultiValueRepeatedKeyLiteral,
CheckCode::F602 => CheckKind::MultiValueRepeatedKeyVariable("...".to_string()),
@@ -695,10 +763,12 @@ impl CheckCode {
CheckCode::B020 => CheckKind::LoopVariableOverridesIterator("...".to_string()),
CheckCode::B021 => CheckKind::FStringDocstring,
CheckCode::B022 => CheckKind::UselessContextlibSuppress,
CheckCode::B023 => CheckKind::FunctionUsesLoopVariable("...".to_string()),
CheckCode::B024 => CheckKind::AbstractBaseClassWithoutAbstractMethod("...".to_string()),
CheckCode::B025 => CheckKind::DuplicateTryBlockException("Exception".to_string()),
CheckCode::B026 => CheckKind::StarArgUnpackingAfterKeywordArg,
CheckCode::B027 => CheckKind::EmptyMethodWithoutAbstractDecorator("...".to_string()),
CheckCode::B904 => CheckKind::RaiseWithoutFromInsideExcept,
// flake8-comprehensions
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
CheckCode::C401 => CheckKind::UnnecessaryGeneratorSet,
@@ -729,6 +799,8 @@ impl CheckCode {
}
CheckCode::C416 => CheckKind::UnnecessaryComprehension("(list|set)".to_string()),
CheckCode::C417 => CheckKind::UnnecessaryMap("(list|set|dict)".to_string()),
// flake8-debugger
CheckCode::T100 => CheckKind::Debugger(DebuggerUsingType::Import("...".to_string())),
// flake8-tidy-imports
CheckCode::I252 => CheckKind::BannedRelativeImport(Strictness::All),
// flake8-print
@@ -861,6 +933,8 @@ impl CheckCode {
CheckCode::N818 => CheckKind::ErrorSuffixOnExceptionName("...".to_string()),
// isort
CheckCode::I001 => CheckKind::UnsortedImports,
// eradicate
CheckCode::ERA001 => CheckKind::CommentedOutCode,
// flake8-bandit
CheckCode::S101 => CheckKind::AssertUsed,
CheckCode::S102 => CheckKind::ExecUsed,
@@ -886,53 +960,20 @@ impl CheckCode {
pub fn category(&self) -> CheckCategory {
#[allow(clippy::match_same_arms)]
match self {
CheckCode::E402 => CheckCategory::Pycodestyle,
CheckCode::E501 => CheckCategory::Pycodestyle,
CheckCode::E711 => CheckCategory::Pycodestyle,
CheckCode::E712 => CheckCategory::Pycodestyle,
CheckCode::E713 => CheckCategory::Pycodestyle,
CheckCode::E714 => CheckCategory::Pycodestyle,
CheckCode::E721 => CheckCategory::Pycodestyle,
CheckCode::E722 => CheckCategory::Pycodestyle,
CheckCode::E731 => CheckCategory::Pycodestyle,
CheckCode::E741 => CheckCategory::Pycodestyle,
CheckCode::E742 => CheckCategory::Pycodestyle,
CheckCode::E743 => CheckCategory::Pycodestyle,
CheckCode::E902 => CheckCategory::Pycodestyle,
CheckCode::E999 => CheckCategory::Pycodestyle,
CheckCode::W292 => CheckCategory::Pycodestyle,
CheckCode::W605 => CheckCategory::Pycodestyle,
CheckCode::F401 => CheckCategory::Pyflakes,
CheckCode::F402 => CheckCategory::Pyflakes,
CheckCode::F403 => CheckCategory::Pyflakes,
CheckCode::F404 => CheckCategory::Pyflakes,
CheckCode::F405 => CheckCategory::Pyflakes,
CheckCode::F406 => CheckCategory::Pyflakes,
CheckCode::F407 => CheckCategory::Pyflakes,
CheckCode::F541 => CheckCategory::Pyflakes,
CheckCode::F601 => CheckCategory::Pyflakes,
CheckCode::F602 => CheckCategory::Pyflakes,
CheckCode::F621 => CheckCategory::Pyflakes,
CheckCode::F622 => CheckCategory::Pyflakes,
CheckCode::F631 => CheckCategory::Pyflakes,
CheckCode::F632 => CheckCategory::Pyflakes,
CheckCode::F633 => CheckCategory::Pyflakes,
CheckCode::F634 => CheckCategory::Pyflakes,
CheckCode::F701 => CheckCategory::Pyflakes,
CheckCode::F702 => CheckCategory::Pyflakes,
CheckCode::F704 => CheckCategory::Pyflakes,
CheckCode::F706 => CheckCategory::Pyflakes,
CheckCode::F707 => CheckCategory::Pyflakes,
CheckCode::F722 => CheckCategory::Pyflakes,
CheckCode::F821 => CheckCategory::Pyflakes,
CheckCode::F822 => CheckCategory::Pyflakes,
CheckCode::F823 => CheckCategory::Pyflakes,
CheckCode::F831 => CheckCategory::Pyflakes,
CheckCode::F841 => CheckCategory::Pyflakes,
CheckCode::F901 => CheckCategory::Pyflakes,
CheckCode::A001 => CheckCategory::Flake8Builtins,
CheckCode::A002 => CheckCategory::Flake8Builtins,
CheckCode::A003 => CheckCategory::Flake8Builtins,
CheckCode::ANN001 => CheckCategory::Flake8Annotations,
CheckCode::ANN002 => CheckCategory::Flake8Annotations,
CheckCode::ANN003 => CheckCategory::Flake8Annotations,
CheckCode::ANN101 => CheckCategory::Flake8Annotations,
CheckCode::ANN102 => CheckCategory::Flake8Annotations,
CheckCode::ANN201 => CheckCategory::Flake8Annotations,
CheckCode::ANN202 => CheckCategory::Flake8Annotations,
CheckCode::ANN204 => CheckCategory::Flake8Annotations,
CheckCode::ANN205 => CheckCategory::Flake8Annotations,
CheckCode::ANN206 => CheckCategory::Flake8Annotations,
CheckCode::ANN401 => CheckCategory::Flake8Annotations,
CheckCode::B002 => CheckCategory::Flake8Bugbear,
CheckCode::B003 => CheckCategory::Flake8Bugbear,
CheckCode::B004 => CheckCategory::Flake8Bugbear,
@@ -954,10 +995,12 @@ impl CheckCode {
CheckCode::B020 => CheckCategory::Flake8Bugbear,
CheckCode::B021 => CheckCategory::Flake8Bugbear,
CheckCode::B022 => CheckCategory::Flake8Bugbear,
CheckCode::B023 => CheckCategory::Flake8Bugbear,
CheckCode::B024 => CheckCategory::Flake8Bugbear,
CheckCode::B025 => CheckCategory::Flake8Bugbear,
CheckCode::B026 => CheckCategory::Flake8Bugbear,
CheckCode::B027 => CheckCategory::Flake8Bugbear,
CheckCode::B904 => CheckCategory::Flake8Bugbear,
CheckCode::BLE001 => CheckCategory::Flake8BlindExcept,
CheckCode::C400 => CheckCategory::Flake8Comprehensions,
CheckCode::C401 => CheckCategory::Flake8Comprehensions,
@@ -975,48 +1018,7 @@ impl CheckCode {
CheckCode::C415 => CheckCategory::Flake8Comprehensions,
CheckCode::C416 => CheckCategory::Flake8Comprehensions,
CheckCode::C417 => CheckCategory::Flake8Comprehensions,
CheckCode::I252 => CheckCategory::Flake8TidyImports,
CheckCode::T201 => CheckCategory::Flake8Print,
CheckCode::T203 => CheckCategory::Flake8Print,
CheckCode::Q000 => CheckCategory::Flake8Quotes,
CheckCode::Q001 => CheckCategory::Flake8Quotes,
CheckCode::Q002 => CheckCategory::Flake8Quotes,
CheckCode::Q003 => CheckCategory::Flake8Quotes,
CheckCode::ANN001 => CheckCategory::Flake8Annotations,
CheckCode::ANN002 => CheckCategory::Flake8Annotations,
CheckCode::ANN003 => CheckCategory::Flake8Annotations,
CheckCode::ANN101 => CheckCategory::Flake8Annotations,
CheckCode::ANN102 => CheckCategory::Flake8Annotations,
CheckCode::ANN201 => CheckCategory::Flake8Annotations,
CheckCode::ANN202 => CheckCategory::Flake8Annotations,
CheckCode::ANN204 => CheckCategory::Flake8Annotations,
CheckCode::ANN205 => CheckCategory::Flake8Annotations,
CheckCode::ANN206 => CheckCategory::Flake8Annotations,
CheckCode::ANN401 => CheckCategory::Flake8Annotations,
CheckCode::YTT101 => CheckCategory::Flake82020,
CheckCode::YTT102 => CheckCategory::Flake82020,
CheckCode::YTT103 => CheckCategory::Flake82020,
CheckCode::YTT201 => CheckCategory::Flake82020,
CheckCode::YTT202 => CheckCategory::Flake82020,
CheckCode::YTT203 => CheckCategory::Flake82020,
CheckCode::YTT204 => CheckCategory::Flake82020,
CheckCode::YTT301 => CheckCategory::Flake82020,
CheckCode::YTT302 => CheckCategory::Flake82020,
CheckCode::YTT303 => CheckCategory::Flake82020,
CheckCode::U001 => CheckCategory::Pyupgrade,
CheckCode::U003 => CheckCategory::Pyupgrade,
CheckCode::U004 => CheckCategory::Pyupgrade,
CheckCode::U005 => CheckCategory::Pyupgrade,
CheckCode::U006 => CheckCategory::Pyupgrade,
CheckCode::U007 => CheckCategory::Pyupgrade,
CheckCode::U008 => CheckCategory::Pyupgrade,
CheckCode::U009 => CheckCategory::Pyupgrade,
CheckCode::U010 => CheckCategory::Pyupgrade,
CheckCode::U011 => CheckCategory::Pyupgrade,
CheckCode::U012 => CheckCategory::Pyupgrade,
CheckCode::U013 => CheckCategory::Pyupgrade,
CheckCode::U014 => CheckCategory::Pyupgrade,
CheckCode::U015 => CheckCategory::Pyupgrade,
CheckCode::C901 => CheckCategory::McCabe,
CheckCode::D100 => CheckCategory::Pydocstyle,
CheckCode::D101 => CheckCategory::Pydocstyle,
CheckCode::D102 => CheckCategory::Pydocstyle,
@@ -1061,6 +1063,69 @@ impl CheckCode {
CheckCode::D417 => CheckCategory::Pydocstyle,
CheckCode::D418 => CheckCategory::Pydocstyle,
CheckCode::D419 => CheckCategory::Pydocstyle,
CheckCode::E402 => CheckCategory::Pycodestyle,
CheckCode::E501 => CheckCategory::Pycodestyle,
CheckCode::E711 => CheckCategory::Pycodestyle,
CheckCode::E712 => CheckCategory::Pycodestyle,
CheckCode::E713 => CheckCategory::Pycodestyle,
CheckCode::E714 => CheckCategory::Pycodestyle,
CheckCode::E721 => CheckCategory::Pycodestyle,
CheckCode::E722 => CheckCategory::Pycodestyle,
CheckCode::E731 => CheckCategory::Pycodestyle,
CheckCode::E741 => CheckCategory::Pycodestyle,
CheckCode::E742 => CheckCategory::Pycodestyle,
CheckCode::E743 => CheckCategory::Pycodestyle,
CheckCode::E902 => CheckCategory::Pycodestyle,
CheckCode::E999 => CheckCategory::Pycodestyle,
CheckCode::ERA001 => CheckCategory::Eradicate,
CheckCode::F401 => CheckCategory::Pyflakes,
CheckCode::F402 => CheckCategory::Pyflakes,
CheckCode::F403 => CheckCategory::Pyflakes,
CheckCode::F404 => CheckCategory::Pyflakes,
CheckCode::F405 => CheckCategory::Pyflakes,
CheckCode::F406 => CheckCategory::Pyflakes,
CheckCode::F407 => CheckCategory::Pyflakes,
CheckCode::F501 => CheckCategory::Pyflakes,
CheckCode::F502 => CheckCategory::Pyflakes,
CheckCode::F503 => CheckCategory::Pyflakes,
CheckCode::F504 => CheckCategory::Pyflakes,
CheckCode::F505 => CheckCategory::Pyflakes,
CheckCode::F506 => CheckCategory::Pyflakes,
CheckCode::F507 => CheckCategory::Pyflakes,
CheckCode::F508 => CheckCategory::Pyflakes,
CheckCode::F509 => CheckCategory::Pyflakes,
CheckCode::F521 => CheckCategory::Pyflakes,
CheckCode::F522 => CheckCategory::Pyflakes,
CheckCode::F523 => CheckCategory::Pyflakes,
CheckCode::F524 => CheckCategory::Pyflakes,
CheckCode::F525 => CheckCategory::Pyflakes,
CheckCode::F541 => CheckCategory::Pyflakes,
CheckCode::F601 => CheckCategory::Pyflakes,
CheckCode::F602 => CheckCategory::Pyflakes,
CheckCode::F621 => CheckCategory::Pyflakes,
CheckCode::F622 => CheckCategory::Pyflakes,
CheckCode::F631 => CheckCategory::Pyflakes,
CheckCode::F632 => CheckCategory::Pyflakes,
CheckCode::F633 => CheckCategory::Pyflakes,
CheckCode::F634 => CheckCategory::Pyflakes,
CheckCode::F701 => CheckCategory::Pyflakes,
CheckCode::F702 => CheckCategory::Pyflakes,
CheckCode::F704 => CheckCategory::Pyflakes,
CheckCode::F706 => CheckCategory::Pyflakes,
CheckCode::F707 => CheckCategory::Pyflakes,
CheckCode::F722 => CheckCategory::Pyflakes,
CheckCode::F821 => CheckCategory::Pyflakes,
CheckCode::F822 => CheckCategory::Pyflakes,
CheckCode::F823 => CheckCategory::Pyflakes,
CheckCode::F831 => CheckCategory::Pyflakes,
CheckCode::F841 => CheckCategory::Pyflakes,
CheckCode::F901 => CheckCategory::Pyflakes,
CheckCode::FBT001 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT002 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT003 => CheckCategory::Flake8BooleanTrap,
CheckCode::I001 => CheckCategory::Isort,
CheckCode::I252 => CheckCategory::Flake8TidyImports,
CheckCode::M001 => CheckCategory::Meta,
CheckCode::N801 => CheckCategory::PEP8Naming,
CheckCode::N802 => CheckCategory::PEP8Naming,
CheckCode::N803 => CheckCategory::PEP8Naming,
@@ -1076,22 +1141,49 @@ impl CheckCode {
CheckCode::N816 => CheckCategory::PEP8Naming,
CheckCode::N817 => CheckCategory::PEP8Naming,
CheckCode::N818 => CheckCategory::PEP8Naming,
CheckCode::I001 => CheckCategory::Isort,
CheckCode::Q000 => CheckCategory::Flake8Quotes,
CheckCode::Q001 => CheckCategory::Flake8Quotes,
CheckCode::Q002 => CheckCategory::Flake8Quotes,
CheckCode::Q003 => CheckCategory::Flake8Quotes,
CheckCode::RUF001 => CheckCategory::Ruff,
CheckCode::RUF002 => CheckCategory::Ruff,
CheckCode::RUF003 => CheckCategory::Ruff,
CheckCode::RUF101 => CheckCategory::Ruff,
CheckCode::S101 => CheckCategory::Flake8Bandit,
CheckCode::S102 => CheckCategory::Flake8Bandit,
CheckCode::S104 => CheckCategory::Flake8Bandit,
CheckCode::S105 => CheckCategory::Flake8Bandit,
CheckCode::S106 => CheckCategory::Flake8Bandit,
CheckCode::S107 => CheckCategory::Flake8Bandit,
CheckCode::C901 => CheckCategory::McCabe,
CheckCode::FBT001 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT002 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT003 => CheckCategory::Flake8BooleanTrap,
CheckCode::RUF001 => CheckCategory::Ruff,
CheckCode::RUF002 => CheckCategory::Ruff,
CheckCode::RUF003 => CheckCategory::Ruff,
CheckCode::RUF101 => CheckCategory::Ruff,
CheckCode::M001 => CheckCategory::Meta,
CheckCode::T100 => CheckCategory::Flake8Debugger,
CheckCode::T201 => CheckCategory::Flake8Print,
CheckCode::T203 => CheckCategory::Flake8Print,
CheckCode::U001 => CheckCategory::Pyupgrade,
CheckCode::U003 => CheckCategory::Pyupgrade,
CheckCode::U004 => CheckCategory::Pyupgrade,
CheckCode::U005 => CheckCategory::Pyupgrade,
CheckCode::U006 => CheckCategory::Pyupgrade,
CheckCode::U007 => CheckCategory::Pyupgrade,
CheckCode::U008 => CheckCategory::Pyupgrade,
CheckCode::U009 => CheckCategory::Pyupgrade,
CheckCode::U010 => CheckCategory::Pyupgrade,
CheckCode::U011 => CheckCategory::Pyupgrade,
CheckCode::U012 => CheckCategory::Pyupgrade,
CheckCode::U013 => CheckCategory::Pyupgrade,
CheckCode::U014 => CheckCategory::Pyupgrade,
CheckCode::U015 => CheckCategory::Pyupgrade,
CheckCode::W292 => CheckCategory::Pycodestyle,
CheckCode::W605 => CheckCategory::Pycodestyle,
CheckCode::YTT101 => CheckCategory::Flake82020,
CheckCode::YTT102 => CheckCategory::Flake82020,
CheckCode::YTT103 => CheckCategory::Flake82020,
CheckCode::YTT201 => CheckCategory::Flake82020,
CheckCode::YTT202 => CheckCategory::Flake82020,
CheckCode::YTT203 => CheckCategory::Flake82020,
CheckCode::YTT204 => CheckCategory::Flake82020,
CheckCode::YTT301 => CheckCategory::Flake82020,
CheckCode::YTT302 => CheckCategory::Flake82020,
CheckCode::YTT303 => CheckCategory::Flake82020,
}
}
}
@@ -1130,8 +1222,22 @@ impl CheckKind {
CheckKind::NoneComparison(_) => &CheckCode::E711,
CheckKind::NotInTest => &CheckCode::E713,
CheckKind::NotIsTest => &CheckCode::E714,
CheckKind::PercentFormatExpectedMapping => &CheckCode::F502,
CheckKind::PercentFormatExpectedSequence => &CheckCode::F503,
CheckKind::PercentFormatExtraNamedArguments(_) => &CheckCode::F504,
CheckKind::PercentFormatInvalidFormat(_) => &CheckCode::F501,
CheckKind::PercentFormatMissingArgument(_) => &CheckCode::F505,
CheckKind::PercentFormatMixedPositionalAndNamed => &CheckCode::F506,
CheckKind::PercentFormatPositionalCountMismatch(..) => &CheckCode::F507,
CheckKind::PercentFormatStarRequiresSequence => &CheckCode::F508,
CheckKind::PercentFormatUnsupportedFormatCharacter(_) => &CheckCode::F509,
CheckKind::RaiseNotImplemented => &CheckCode::F901,
CheckKind::ReturnOutsideFunction => &CheckCode::F706,
CheckKind::StringDotFormatExtraNamedArguments(_) => &CheckCode::F522,
CheckKind::StringDotFormatExtraPositionalArguments(_) => &CheckCode::F523,
CheckKind::StringDotFormatInvalidFormat(_) => &CheckCode::F521,
CheckKind::StringDotFormatMissingArguments(_) => &CheckCode::F524,
CheckKind::StringDotFormatMixingAutomatic => &CheckCode::F525,
CheckKind::SyntaxError(_) => &CheckCode::E999,
CheckKind::ExpressionsInStarAssignment => &CheckCode::F621,
CheckKind::TrueFalseComparison(..) => &CheckCode::E712,
@@ -1151,31 +1257,33 @@ impl CheckKind {
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
// flake8-bugbear
CheckKind::UnaryPrefixIncrement => &CheckCode::B002,
CheckKind::AssignmentToOsEnviron => &CheckCode::B003,
CheckKind::UnreliableCallableCheck => &CheckCode::B004,
CheckKind::StripWithMultiCharacters => &CheckCode::B005,
CheckKind::MutableArgumentDefault => &CheckCode::B006,
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
CheckKind::FunctionCallArgumentDefault(_) => &CheckCode::B008,
CheckKind::GetAttrWithConstant => &CheckCode::B009,
CheckKind::SetAttrWithConstant => &CheckCode::B010,
CheckKind::DoNotAssertFalse => &CheckCode::B011,
CheckKind::JumpStatementInFinally(_) => &CheckCode::B012,
CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013,
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
CheckKind::UselessComparison => &CheckCode::B015,
CheckKind::CannotRaiseLiteral => &CheckCode::B016,
CheckKind::NoAssertRaisesException => &CheckCode::B017,
CheckKind::UselessExpression => &CheckCode::B018,
CheckKind::CachedInstanceMethod => &CheckCode::B019,
CheckKind::LoopVariableOverridesIterator(_) => &CheckCode::B020,
CheckKind::FStringDocstring => &CheckCode::B021,
CheckKind::UselessContextlibSuppress => &CheckCode::B022,
CheckKind::AbstractBaseClassWithoutAbstractMethod(_) => &CheckCode::B024,
CheckKind::AssignmentToOsEnviron => &CheckCode::B003,
CheckKind::CachedInstanceMethod => &CheckCode::B019,
CheckKind::CannotRaiseLiteral => &CheckCode::B016,
CheckKind::DoNotAssertFalse => &CheckCode::B011,
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
CheckKind::DuplicateTryBlockException(_) => &CheckCode::B025,
CheckKind::StarArgUnpackingAfterKeywordArg => &CheckCode::B026,
CheckKind::EmptyMethodWithoutAbstractDecorator(_) => &CheckCode::B027,
CheckKind::FStringDocstring => &CheckCode::B021,
CheckKind::FunctionCallArgumentDefault(_) => &CheckCode::B008,
CheckKind::FunctionUsesLoopVariable(_) => &CheckCode::B023,
CheckKind::GetAttrWithConstant => &CheckCode::B009,
CheckKind::JumpStatementInFinally(_) => &CheckCode::B012,
CheckKind::LoopVariableOverridesIterator(_) => &CheckCode::B020,
CheckKind::MutableArgumentDefault => &CheckCode::B006,
CheckKind::NoAssertRaisesException => &CheckCode::B017,
CheckKind::RaiseWithoutFromInsideExcept => &CheckCode::B904,
CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013,
CheckKind::SetAttrWithConstant => &CheckCode::B010,
CheckKind::StarArgUnpackingAfterKeywordArg => &CheckCode::B026,
CheckKind::StripWithMultiCharacters => &CheckCode::B005,
CheckKind::UnaryPrefixIncrement => &CheckCode::B002,
CheckKind::UnreliableCallableCheck => &CheckCode::B004,
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
CheckKind::UselessComparison => &CheckCode::B015,
CheckKind::UselessContextlibSuppress => &CheckCode::B022,
CheckKind::UselessExpression => &CheckCode::B018,
// flake8-blind-except
CheckKind::BlindExcept => &CheckCode::BLE001,
// flake8-comprehensions
@@ -1195,6 +1303,8 @@ impl CheckKind {
CheckKind::UnnecessarySubscriptReversal(_) => &CheckCode::C415,
CheckKind::UnnecessaryComprehension(..) => &CheckCode::C416,
CheckKind::UnnecessaryMap(_) => &CheckCode::C417,
// flake8-debugger
CheckKind::Debugger(_) => &CheckCode::T100,
// flake8-tidy-imports
CheckKind::BannedRelativeImport(_) => &CheckCode::I252,
// flake8-print
@@ -1306,6 +1416,8 @@ impl CheckKind {
CheckKind::ErrorSuffixOnExceptionName(..) => &CheckCode::N818,
// isort
CheckKind::UnsortedImports => &CheckCode::I001,
// eradicate
CheckKind::CommentedOutCode => &CheckCode::ERA001,
// flake8-bandit
CheckKind::AssertUsed => &CheckCode::S101,
CheckKind::ExecUsed => &CheckCode::S102,
@@ -1334,13 +1446,13 @@ impl CheckKind {
match self {
// pycodestyle errors
CheckKind::AmbiguousClassName(name) => {
format!("Ambiguous class name: `{}`", name)
format!("Ambiguous class name: `{name}`")
}
CheckKind::AmbiguousFunctionName(name) => {
format!("Ambiguous function name: `{}`", name)
format!("Ambiguous function name: `{name}`")
}
CheckKind::AmbiguousVariableName(name) => {
format!("Ambiguous variable name: `{}`", name)
format!("Ambiguous variable name: `{name}`")
}
CheckKind::AssertTuple => {
"Assert test is a non-empty tuple, which is always `True`".to_string()
@@ -1383,7 +1495,7 @@ impl CheckKind {
CheckKind::ImportStarUsage(name, sources) => {
let sources = sources
.iter()
.map(|source| format!("`{}`", source))
.map(|source| format!("`{source}`"))
.join(", ");
format!("`{name}` may be undefined, or defined from star imports: {sources}")
}
@@ -1411,34 +1523,75 @@ impl CheckKind {
},
CheckKind::NotInTest => "Test for membership should be `not in`".to_string(),
CheckKind::NotIsTest => "Test for object identity should be `is not`".to_string(),
CheckKind::PercentFormatInvalidFormat(message) => {
format!("'...' % ... has invalid format string: {message}")
}
CheckKind::PercentFormatUnsupportedFormatCharacter(char) => {
format!("'...' % ... has unsupported format character '{char}'")
}
CheckKind::PercentFormatExpectedMapping => {
"'...' % ... expected mapping but got sequence".to_string()
}
CheckKind::PercentFormatExpectedSequence => {
"'...' % ... expected sequence but got mapping".to_string()
}
CheckKind::PercentFormatExtraNamedArguments(missing) => {
let message = missing.join(", ");
format!("'...' % ... has unused named argument(s): {message}")
}
CheckKind::PercentFormatMissingArgument(missing) => {
let message = missing.join(", ");
format!("'...' % ... is missing argument(s) for placeholder(s): {message}")
}
CheckKind::PercentFormatMixedPositionalAndNamed => {
"'...' % ... has mixed positional and named placeholders".to_string()
}
CheckKind::PercentFormatPositionalCountMismatch(wanted, got) => {
format!("'...' % ... has {wanted} placeholder(s) but {got} substitution(s)")
}
CheckKind::PercentFormatStarRequiresSequence => {
"'...' % ... `*` specifier requires sequence".to_string()
}
CheckKind::RaiseNotImplemented => {
"`raise NotImplemented` should be `raise NotImplementedError`".to_string()
}
CheckKind::ReturnOutsideFunction => {
"`return` statement outside of a function/method".to_string()
}
CheckKind::StringDotFormatExtraNamedArguments(missing) => {
let message = missing.join(", ");
format!("'...'.format(...) has unused named argument(s): {message}")
}
CheckKind::StringDotFormatExtraPositionalArguments(missing) => {
let message = missing.join(", ");
format!("'...'.format(...) has unused arguments at position(s): {message}")
}
CheckKind::StringDotFormatInvalidFormat(message) => {
format!("'...'.format(...) has invalid format string: {message}")
}
CheckKind::StringDotFormatMissingArguments(missing) => {
let message = missing.join(", ");
format!("'...'.format(...) is missing argument(s) for placeholder(s): {message}")
}
CheckKind::StringDotFormatMixingAutomatic => {
"'...'.format(...) mixes automatic and manual numbering".to_string()
}
CheckKind::SyntaxError(message) => format!("SyntaxError: {message}"),
CheckKind::ExpressionsInStarAssignment => {
"Too many expressions in star-unpacking assignment".to_string()
}
CheckKind::TrueFalseComparison(value, op) => match *value {
true => match op {
RejectedCmpop::Eq => {
"Comparison to `True` should be `cond is True`".to_string()
}
RejectedCmpop::NotEq => {
"Comparison to `True` should be `cond is not True`".to_string()
}
},
false => match op {
RejectedCmpop::Eq => {
"Comparison to `False` should be `cond is False`".to_string()
}
RejectedCmpop::NotEq => {
"Comparison to `False` should be `cond is not False`".to_string()
}
},
},
CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq) => {
"Comparison to `True` should be `cond is True`".to_string()
}
CheckKind::TrueFalseComparison(true, RejectedCmpop::NotEq) => {
"Comparison to `True` should be `cond is not True`".to_string()
}
CheckKind::TrueFalseComparison(false, RejectedCmpop::Eq) => {
"Comparison to `False` should be `cond is False`".to_string()
}
CheckKind::TrueFalseComparison(false, RejectedCmpop::NotEq) => {
"Comparison to `False` should be `cond is not False`".to_string()
}
CheckKind::TwoStarredExpressions => "Two starred expressions in assignment".to_string(),
CheckKind::TypeComparison => "Do not compare types, use `isinstance()`".to_string(),
CheckKind::UndefinedExport(name) => {
@@ -1450,12 +1603,11 @@ impl CheckKind {
CheckKind::UndefinedName(name) => {
format!("Undefined name `{name}`")
}
CheckKind::UnusedImport(names, in_init_py) => {
let names = names.iter().map(|name| format!("`{name}`")).join(", ");
CheckKind::UnusedImport(name, in_init_py) => {
if *in_init_py {
format!("{names} imported but unused and missing from `__all__`")
format!("`{name}` imported but unused and missing from `__all__`")
} else {
format!("{names} imported but unused")
format!("`{name}` imported but unused")
}
}
CheckKind::UnusedVariable(name) => {
@@ -1508,6 +1660,9 @@ impl CheckKind {
"Do not perform function call in argument defaults".to_string()
}
}
CheckKind::FunctionUsesLoopVariable(name) => {
format!("Function definition does not bind loop variable `{name}`")
}
CheckKind::GetAttrWithConstant => "Do not call `getattr` with a constant attribute \
value. It is not any safer than normal property \
access."
@@ -1586,6 +1741,11 @@ impl CheckKind {
decorator"
)
}
CheckKind::RaiseWithoutFromInsideExcept => {
"Within an except clause, raise exceptions with raise ... from err or raise ... \
from None to distinguish them from errors in exception handling"
.to_string()
}
// flake8-comprehensions
CheckKind::UnnecessaryGeneratorList => {
"Unnecessary generator (rewrite as a `list` comprehension)".to_string()
@@ -1659,6 +1819,11 @@ impl CheckKind {
format!("Unnecessary `map` usage (rewrite using a `{obj_type}` comprehension)")
}
}
// flake8-debugger
CheckKind::Debugger(using_type) => match using_type {
DebuggerUsingType::Call(name) => format!("Trace found: `{name}` used"),
DebuggerUsingType::Import(name) => format!("Import for `{name}` found"),
},
// flake8-tidy-imports
CheckKind::BannedRelativeImport(strictness) => match strictness {
Strictness::Parents => {
@@ -1964,6 +2129,8 @@ impl CheckKind {
}
// isort
CheckKind::UnsortedImports => "Import block is un-sorted or un-formatted".to_string(),
// eradicate
CheckKind::CommentedOutCode => "Found commented-out code".to_string(),
// flake8-bandit
CheckKind::AssertUsed => "Use of `assert` detected".to_string(),
CheckKind::ExecUsed => "Use of `exec` detected".to_string(),
@@ -2068,6 +2235,7 @@ impl CheckKind {
| CheckKind::BlankLineAfterSummary
| CheckKind::BlankLineBeforeSection(..)
| CheckKind::CapitalizeSectionName(..)
| CheckKind::CommentedOutCode
| CheckKind::ConvertExitToSysExit
| CheckKind::ConvertNamedTupleFunctionalToClass(..)
| CheckKind::ConvertTypedDictFunctionalToClass(..)
@@ -2161,13 +2329,12 @@ impl Check {
mod tests {
use std::str::FromStr;
use anyhow::Result;
use strum::IntoEnumIterator;
use crate::checks::CheckCode;
#[test]
fn check_code_serialization() -> Result<()> {
fn check_code_serialization() {
for check_code in CheckCode::iter() {
assert!(
CheckCode::from_str(check_code.as_ref()).is_ok(),
@@ -2175,6 +2342,5 @@ mod tests {
check_code
);
}
Ok(())
}
}

View File

@@ -1,4 +1,4 @@
//! File automatically generated by examples/generate_check_code_prefix.rs.
//! File automatically generated by `examples/generate_check_code_prefix.rs`.
use serde::{Deserialize, Serialize};
use strum_macros::EnumString;
@@ -59,10 +59,14 @@ pub enum CheckCodePrefix {
B020,
B021,
B022,
B023,
B024,
B025,
B026,
B027,
B9,
B90,
B904,
BLE,
BLE0,
BLE00,
@@ -172,6 +176,10 @@ pub enum CheckCodePrefix {
E902,
E99,
E999,
ERA,
ERA0,
ERA00,
ERA001,
F,
F4,
F40,
@@ -183,6 +191,22 @@ pub enum CheckCodePrefix {
F406,
F407,
F5,
F50,
F501,
F502,
F503,
F504,
F505,
F506,
F507,
F508,
F509,
F52,
F521,
F522,
F523,
F524,
F525,
F54,
F541,
F6,
@@ -280,6 +304,9 @@ pub enum CheckCodePrefix {
S106,
S107,
T,
T1,
T10,
T100,
T2,
T20,
T201,
@@ -412,10 +439,12 @@ impl CheckCodePrefix {
CheckCode::B020,
CheckCode::B021,
CheckCode::B022,
CheckCode::B023,
CheckCode::B024,
CheckCode::B025,
CheckCode::B026,
CheckCode::B027,
CheckCode::B904,
],
CheckCodePrefix::B0 => vec![
CheckCode::B002,
@@ -439,6 +468,7 @@ impl CheckCodePrefix {
CheckCode::B020,
CheckCode::B021,
CheckCode::B022,
CheckCode::B023,
CheckCode::B024,
CheckCode::B025,
CheckCode::B026,
@@ -488,6 +518,7 @@ impl CheckCodePrefix {
CheckCode::B020,
CheckCode::B021,
CheckCode::B022,
CheckCode::B023,
CheckCode::B024,
CheckCode::B025,
CheckCode::B026,
@@ -496,10 +527,14 @@ impl CheckCodePrefix {
CheckCodePrefix::B020 => vec![CheckCode::B020],
CheckCodePrefix::B021 => vec![CheckCode::B021],
CheckCodePrefix::B022 => vec![CheckCode::B022],
CheckCodePrefix::B023 => vec![CheckCode::B023],
CheckCodePrefix::B024 => vec![CheckCode::B024],
CheckCodePrefix::B025 => vec![CheckCode::B025],
CheckCodePrefix::B026 => vec![CheckCode::B026],
CheckCodePrefix::B027 => vec![CheckCode::B027],
CheckCodePrefix::B9 => vec![CheckCode::B904],
CheckCodePrefix::B90 => vec![CheckCode::B904],
CheckCodePrefix::B904 => vec![CheckCode::B904],
CheckCodePrefix::BLE => vec![CheckCode::BLE001],
CheckCodePrefix::BLE0 => vec![CheckCode::BLE001],
CheckCodePrefix::BLE00 => vec![CheckCode::BLE001],
@@ -832,6 +867,10 @@ impl CheckCodePrefix {
CheckCodePrefix::E902 => vec![CheckCode::E902],
CheckCodePrefix::E99 => vec![CheckCode::E999],
CheckCodePrefix::E999 => vec![CheckCode::E999],
CheckCodePrefix::ERA => vec![CheckCode::ERA001],
CheckCodePrefix::ERA0 => vec![CheckCode::ERA001],
CheckCodePrefix::ERA00 => vec![CheckCode::ERA001],
CheckCodePrefix::ERA001 => vec![CheckCode::ERA001],
CheckCodePrefix::F => vec![
CheckCode::F401,
CheckCode::F402,
@@ -840,6 +879,20 @@ impl CheckCodePrefix {
CheckCode::F405,
CheckCode::F406,
CheckCode::F407,
CheckCode::F501,
CheckCode::F502,
CheckCode::F503,
CheckCode::F504,
CheckCode::F505,
CheckCode::F506,
CheckCode::F507,
CheckCode::F508,
CheckCode::F509,
CheckCode::F521,
CheckCode::F522,
CheckCode::F523,
CheckCode::F524,
CheckCode::F525,
CheckCode::F541,
CheckCode::F601,
CheckCode::F602,
@@ -887,7 +940,55 @@ impl CheckCodePrefix {
CheckCodePrefix::F405 => vec![CheckCode::F405],
CheckCodePrefix::F406 => vec![CheckCode::F406],
CheckCodePrefix::F407 => vec![CheckCode::F407],
CheckCodePrefix::F5 => vec![CheckCode::F541],
CheckCodePrefix::F5 => vec![
CheckCode::F501,
CheckCode::F502,
CheckCode::F503,
CheckCode::F504,
CheckCode::F505,
CheckCode::F506,
CheckCode::F507,
CheckCode::F508,
CheckCode::F509,
CheckCode::F521,
CheckCode::F522,
CheckCode::F523,
CheckCode::F524,
CheckCode::F525,
CheckCode::F541,
],
CheckCodePrefix::F50 => vec![
CheckCode::F501,
CheckCode::F502,
CheckCode::F503,
CheckCode::F504,
CheckCode::F505,
CheckCode::F506,
CheckCode::F507,
CheckCode::F508,
CheckCode::F509,
],
CheckCodePrefix::F501 => vec![CheckCode::F501],
CheckCodePrefix::F502 => vec![CheckCode::F502],
CheckCodePrefix::F503 => vec![CheckCode::F503],
CheckCodePrefix::F504 => vec![CheckCode::F504],
CheckCodePrefix::F505 => vec![CheckCode::F505],
CheckCodePrefix::F506 => vec![CheckCode::F506],
CheckCodePrefix::F507 => vec![CheckCode::F507],
CheckCodePrefix::F508 => vec![CheckCode::F508],
CheckCodePrefix::F509 => vec![CheckCode::F509],
CheckCodePrefix::F52 => vec![
CheckCode::F521,
CheckCode::F522,
CheckCode::F523,
CheckCode::F524,
CheckCode::F525,
],
CheckCodePrefix::F521 => vec![CheckCode::F521],
CheckCodePrefix::F522 => vec![CheckCode::F522],
CheckCodePrefix::F523 => vec![CheckCode::F523],
CheckCodePrefix::F524 => vec![CheckCode::F524],
CheckCodePrefix::F525 => vec![CheckCode::F525],
CheckCodePrefix::F54 => vec![CheckCode::F541],
CheckCodePrefix::F541 => vec![CheckCode::F541],
CheckCodePrefix::F6 => vec![
@@ -1107,7 +1208,10 @@ impl CheckCodePrefix {
CheckCodePrefix::S105 => vec![CheckCode::S105],
CheckCodePrefix::S106 => vec![CheckCode::S106],
CheckCodePrefix::S107 => vec![CheckCode::S107],
CheckCodePrefix::T => vec![CheckCode::T201, CheckCode::T203],
CheckCodePrefix::T => vec![CheckCode::T100, CheckCode::T201, CheckCode::T203],
CheckCodePrefix::T1 => vec![CheckCode::T100],
CheckCodePrefix::T10 => vec![CheckCode::T100],
CheckCodePrefix::T100 => vec![CheckCode::T100],
CheckCodePrefix::T2 => vec![CheckCode::T201, CheckCode::T203],
CheckCodePrefix::T20 => vec![CheckCode::T201, CheckCode::T203],
CheckCodePrefix::T201 => vec![CheckCode::T201],
@@ -1281,10 +1385,14 @@ impl CheckCodePrefix {
CheckCodePrefix::B020 => PrefixSpecificity::Explicit,
CheckCodePrefix::B021 => PrefixSpecificity::Explicit,
CheckCodePrefix::B022 => PrefixSpecificity::Explicit,
CheckCodePrefix::B023 => PrefixSpecificity::Explicit,
CheckCodePrefix::B024 => PrefixSpecificity::Explicit,
CheckCodePrefix::B025 => PrefixSpecificity::Explicit,
CheckCodePrefix::B026 => PrefixSpecificity::Explicit,
CheckCodePrefix::B027 => PrefixSpecificity::Explicit,
CheckCodePrefix::B9 => PrefixSpecificity::Hundreds,
CheckCodePrefix::B90 => PrefixSpecificity::Tens,
CheckCodePrefix::B904 => PrefixSpecificity::Explicit,
CheckCodePrefix::BLE => PrefixSpecificity::Category,
CheckCodePrefix::BLE0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::BLE00 => PrefixSpecificity::Tens,
@@ -1394,6 +1502,10 @@ impl CheckCodePrefix {
CheckCodePrefix::E902 => PrefixSpecificity::Explicit,
CheckCodePrefix::E99 => PrefixSpecificity::Tens,
CheckCodePrefix::E999 => PrefixSpecificity::Explicit,
CheckCodePrefix::ERA => PrefixSpecificity::Category,
CheckCodePrefix::ERA0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::ERA00 => PrefixSpecificity::Tens,
CheckCodePrefix::ERA001 => PrefixSpecificity::Explicit,
CheckCodePrefix::F => PrefixSpecificity::Category,
CheckCodePrefix::F4 => PrefixSpecificity::Hundreds,
CheckCodePrefix::F40 => PrefixSpecificity::Tens,
@@ -1405,6 +1517,22 @@ impl CheckCodePrefix {
CheckCodePrefix::F406 => PrefixSpecificity::Explicit,
CheckCodePrefix::F407 => PrefixSpecificity::Explicit,
CheckCodePrefix::F5 => PrefixSpecificity::Hundreds,
CheckCodePrefix::F50 => PrefixSpecificity::Tens,
CheckCodePrefix::F501 => PrefixSpecificity::Explicit,
CheckCodePrefix::F502 => PrefixSpecificity::Explicit,
CheckCodePrefix::F503 => PrefixSpecificity::Explicit,
CheckCodePrefix::F504 => PrefixSpecificity::Explicit,
CheckCodePrefix::F505 => PrefixSpecificity::Explicit,
CheckCodePrefix::F506 => PrefixSpecificity::Explicit,
CheckCodePrefix::F507 => PrefixSpecificity::Explicit,
CheckCodePrefix::F508 => PrefixSpecificity::Explicit,
CheckCodePrefix::F509 => PrefixSpecificity::Explicit,
CheckCodePrefix::F52 => PrefixSpecificity::Tens,
CheckCodePrefix::F521 => PrefixSpecificity::Explicit,
CheckCodePrefix::F522 => PrefixSpecificity::Explicit,
CheckCodePrefix::F523 => PrefixSpecificity::Explicit,
CheckCodePrefix::F524 => PrefixSpecificity::Explicit,
CheckCodePrefix::F525 => PrefixSpecificity::Explicit,
CheckCodePrefix::F54 => PrefixSpecificity::Tens,
CheckCodePrefix::F541 => PrefixSpecificity::Explicit,
CheckCodePrefix::F6 => PrefixSpecificity::Hundreds,
@@ -1502,6 +1630,9 @@ impl CheckCodePrefix {
CheckCodePrefix::S106 => PrefixSpecificity::Explicit,
CheckCodePrefix::S107 => PrefixSpecificity::Explicit,
CheckCodePrefix::T => PrefixSpecificity::Category,
CheckCodePrefix::T1 => PrefixSpecificity::Hundreds,
CheckCodePrefix::T10 => PrefixSpecificity::Tens,
CheckCodePrefix::T100 => PrefixSpecificity::Explicit,
CheckCodePrefix::T2 => PrefixSpecificity::Hundreds,
CheckCodePrefix::T20 => PrefixSpecificity::Tens,
CheckCodePrefix::T201 => PrefixSpecificity::Explicit,
@@ -1551,3 +1682,26 @@ impl CheckCodePrefix {
}
}
}
pub const CATEGORIES: &[CheckCodePrefix] = &[
CheckCodePrefix::A,
CheckCodePrefix::ANN,
CheckCodePrefix::B,
CheckCodePrefix::BLE,
CheckCodePrefix::C,
CheckCodePrefix::D,
CheckCodePrefix::E,
CheckCodePrefix::ERA,
CheckCodePrefix::F,
CheckCodePrefix::FBT,
CheckCodePrefix::I,
CheckCodePrefix::M,
CheckCodePrefix::N,
CheckCodePrefix::Q,
CheckCodePrefix::RUF,
CheckCodePrefix::S,
CheckCodePrefix::T,
CheckCodePrefix::U,
CheckCodePrefix::W,
CheckCodePrefix::YTT,
];

View File

@@ -1,20 +1,22 @@
use std::path::PathBuf;
use anyhow::Result;
use clap::{command, Parser};
use regex::Regex;
use rustc_hash::FxHashMap;
use crate::checks::CheckCode;
use crate::checks_gen::CheckCodePrefix;
use crate::logging::LogLevel;
use crate::printer::SerializationFormat;
use crate::settings::types::{PatternPrefixPair, PerFileIgnore, PythonVersion};
use crate::settings::types::{
FilePattern, PatternPrefixPair, PerFileIgnore, PythonVersion, SerializationFormat,
};
#[derive(Debug, Parser)]
#[command(author, about = "Ruff: An extremely fast Python linter.")]
#[command(version)]
#[allow(clippy::struct_excessive_bools)]
pub struct Cli {
#[arg(required = true)]
#[arg(required_unless_present_any = ["explain", "generate_shell_completion"])]
pub files: Vec<PathBuf>,
/// Path to the `pyproject.toml` file to use for configuration.
#[arg(long)]
@@ -59,11 +61,11 @@ pub struct Cli {
pub extend_ignore: Vec<CheckCodePrefix>,
/// List of paths, used to exclude files and/or directories from checks.
#[arg(long, value_delimiter = ',')]
pub exclude: Vec<String>,
pub exclude: Vec<FilePattern>,
/// Like --exclude, but adds additional files and directories on top of the
/// excluded ones.
#[arg(long, value_delimiter = ',')]
pub extend_exclude: Vec<String>,
pub extend_exclude: Vec<FilePattern>,
/// List of error codes to treat as eligible for autofix. Only applicable
/// when autofix itself is enabled (e.g., via `--fix`).
#[arg(long, value_delimiter = ',')]
@@ -76,8 +78,8 @@ pub struct Cli {
#[arg(long, value_delimiter = ',')]
pub per_file_ignores: Vec<PatternPrefixPair>,
/// Output serialization format for error messages.
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
pub format: SerializationFormat,
#[arg(long, value_enum)]
pub format: Option<SerializationFormat>,
/// Show violations with source code.
#[arg(long)]
pub show_source: bool,
@@ -110,6 +112,12 @@ pub struct Cli {
/// The name of the file when passing it through stdin.
#[arg(long)]
pub stdin_filename: Option<String>,
/// Explain a rule.
#[arg(long)]
pub explain: Option<CheckCode>,
/// Generate shell completion
#[arg(long, hide = true, value_name = "SHELL")]
pub generate_shell_completion: Option<clap_complete_command::Shell>,
}
impl Cli {
@@ -136,18 +144,13 @@ pub fn extract_log_level(cli: &Cli) -> LogLevel {
LogLevel::Quiet
} else if cli.verbose {
LogLevel::Verbose
} else if matches!(cli.format, SerializationFormat::Json) {
LogLevel::Quiet
} else {
LogLevel::Default
}
}
/// Convert a list of `PatternPrefixPair` structs to `PerFileIgnore`.
pub fn collect_per_file_ignores(
pairs: Vec<PatternPrefixPair>,
project_root: Option<&PathBuf>,
) -> Result<Vec<PerFileIgnore>> {
pub fn collect_per_file_ignores(pairs: Vec<PatternPrefixPair>) -> Vec<PerFileIgnore> {
let mut per_file_ignores: FxHashMap<String, Vec<CheckCodePrefix>> = FxHashMap::default();
for pair in pairs {
per_file_ignores
@@ -156,7 +159,7 @@ pub fn collect_per_file_ignores(
.push(pair.prefix);
}
per_file_ignores
.iter()
.map(|(pattern, prefixes)| PerFileIgnore::new(pattern, prefixes, project_root))
.into_iter()
.map(|(pattern, prefixes)| PerFileIgnore::new(pattern, &prefixes))
.collect()
}

File diff suppressed because it is too large Load Diff

66
src/commands.rs Normal file
View File

@@ -0,0 +1,66 @@
use std::path::PathBuf;
use anyhow::Result;
use serde::Serialize;
use walkdir::DirEntry;
use crate::checks::CheckCode;
use crate::fs::iter_python_files;
use crate::settings::types::SerializationFormat;
use crate::{Configuration, Settings};
/// Print the user-facing configuration settings.
pub fn show_settings(
configuration: &Configuration,
project_root: Option<&PathBuf>,
pyproject: Option<&PathBuf>,
) {
println!("Resolved configuration: {configuration:#?}");
println!("Found project root at: {project_root:?}");
println!("Found pyproject.toml at: {pyproject:?}");
}
/// Show the list of files to be checked based on current settings.
pub fn show_files(files: &[PathBuf], settings: &Settings) {
let mut entries: Vec<DirEntry> = files
.iter()
.flat_map(|path| iter_python_files(path, &settings.exclude, &settings.extend_exclude))
.flatten()
.collect();
entries.sort_by(|a, b| a.path().cmp(b.path()));
for entry in entries {
println!("{}", entry.path().to_string_lossy());
}
}
#[derive(Serialize)]
struct Explanation<'a> {
code: &'a str,
category: &'a str,
summary: &'a str,
}
/// Explain a `CheckCode` to the user.
pub fn explain(code: &CheckCode, format: SerializationFormat) -> Result<()> {
match format {
SerializationFormat::Text | SerializationFormat::Grouped => {
println!(
"{} ({}): {}",
code.as_ref(),
code.category().title(),
code.kind().summary()
);
}
SerializationFormat::Json => {
println!(
"{}",
serde_json::to_string_pretty(&Explanation {
code: code.as_ref(),
category: code.category().title(),
summary: &code.kind().summary(),
})?
);
}
};
Ok(())
}

View File

@@ -11,8 +11,8 @@ use crate::{Settings, SourceCodeLocator};
bitflags! {
pub struct Flags: u32 {
const NOQA = 0b00000001;
const ISORT = 0b00000010;
const NOQA = 0b0000_0001;
const ISORT = 0b0000_0010;
}
}
@@ -44,12 +44,12 @@ pub fn extract_directives(
noqa_line_for: if flags.contains(Flags::NOQA) {
extract_noqa_line_for(lxr)
} else {
Default::default()
IntMap::default()
},
isort_exclusions: if flags.contains(Flags::ISORT) {
extract_isort_exclusions(lxr, locator)
} else {
Default::default()
IntSet::default()
},
}
}
@@ -113,7 +113,7 @@ pub fn extract_isort_exclusions(lxr: &[LexResult], locator: &SourceCodeLocator)
#[cfg(test)]
mod tests {
use anyhow::Result;
use nohash_hasher::IntMap;
use rustpython_parser::lexer;
use rustpython_parser::lexer::LexResult;
@@ -121,8 +121,8 @@ mod tests {
use crate::directives::extract_noqa_line_for;
#[test]
fn extraction() -> Result<()> {
let empty: IntMap<usize, usize> = Default::default();
fn extraction() {
let empty: IntMap<usize, usize> = IntMap::default();
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
@@ -200,7 +200,5 @@ z = x + 1",
extract_noqa_line_for(&lxr),
IntMap::from_iter([(2, 5), (3, 5), (4, 5)])
);
Ok(())
}
}

51
src/eradicate/checks.rs Normal file
View File

@@ -0,0 +1,51 @@
use rustpython_ast::Location;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checks::{CheckCode, CheckKind};
use crate::eradicate::detection::comment_contains_code;
use crate::{Check, Settings, SourceCodeLocator};
fn is_standalone_comment(line: &str) -> bool {
for char in line.chars() {
if char == '#' {
return true;
} else if !char.is_whitespace() {
return false;
}
}
unreachable!("Comment should contain '#' character")
}
/// ERA001
pub fn commented_out_code(
locator: &SourceCodeLocator,
start: Location,
end: Location,
settings: &Settings,
autofix: bool,
) -> Option<Check> {
let location = Location::new(start.row(), 0);
let end_location = Location::new(end.row() + 1, 0);
let line = locator.slice_source_code_range(&Range {
location,
end_location,
});
// Verify that the comment is on its own line, and that it contains code.
if is_standalone_comment(&line) && comment_contains_code(&line) {
let mut check = Check::new(
CheckKind::CommentedOutCode,
Range {
location: start,
end_location: end,
},
);
if autofix && settings.fixable.contains(&CheckCode::ERA001) {
check.amend(Fix::deletion(location, end_location));
}
Some(check)
} else {
None
}
}

223
src/eradicate/detection.rs Normal file
View File

@@ -0,0 +1,223 @@
/// See: [eradicate.py](https://github.com/myint/eradicate/blob/98f199940979c94447a461d50d27862b118b282d/eradicate.py)
use once_cell::sync::Lazy;
use regex::Regex;
static ALLOWLIST_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?i)pylint|pyright|noqa|nosec|type:\s*ignore|fmt:\s*(on|off)|isort:\s*(on|off|skip|skip_file|split|dont-add-imports(:\s*\[.*?])?)|TODO|FIXME|XXX"
).unwrap()
});
static BRACKET_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[()\[\]{}\s]+$").unwrap());
static CODE_INDICATORS: &[&str] = &[
"(", ")", "[", "]", "{", "}", ":", "=", "%", "print", "return", "break", "continue", "import",
];
static CODE_KEYWORDS: Lazy<Vec<Regex>> = Lazy::new(|| {
vec![
Regex::new(r"^\s*elif\s+.*\s*:\s*$").unwrap(),
Regex::new(r"^\s*else\s*:\s*$").unwrap(),
Regex::new(r"^\s*try\s*:\s*$").unwrap(),
Regex::new(r"^\s*finally\s*:\s*$").unwrap(),
Regex::new(r"^\s*except\s+.*\s*:\s*$").unwrap(),
]
});
static CODING_COMMENT_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)").unwrap());
static HASH_NUMBER: Lazy<Regex> = Lazy::new(|| Regex::new(r"#\d").unwrap());
static MULTILINE_ASSIGNMENT_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^\s*\w+\s*=.*[(\[{]$").unwrap());
static PARTIAL_DICTIONARY_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"^\s*['"]\w+['"]\s*:.+[,{]\s*$"#).unwrap());
static PRINT_RETURN_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(print|return)\b\s*").unwrap());
/// Returns `true` if a comment contains Python code.
pub fn comment_contains_code(line: &str) -> bool {
let line = if let Some(line) = line.trim().strip_prefix('#') {
line.trim()
} else {
return false;
};
// Ignore non-comment related hashes (e.g., "# Issue #999").
if HASH_NUMBER.is_match(line) {
return false;
}
// Ignore whitelisted comments.
if ALLOWLIST_REGEX.is_match(line) {
return false;
}
if CODING_COMMENT_REGEX.is_match(line) {
return false;
}
// Check that this is possibly code.
if CODE_INDICATORS.iter().all(|symbol| !line.contains(symbol)) {
return false;
}
if multiline_case(line) {
return true;
}
if CODE_KEYWORDS.iter().any(|symbol| symbol.is_match(line)) {
return true;
}
let line = PRINT_RETURN_REGEX.replace_all(line, "");
if PARTIAL_DICTIONARY_REGEX.is_match(&line) {
return true;
}
// Finally, compile the source code.
rustpython_parser::parser::parse_program(&line, "<filename>").is_ok()
}
/// Returns `true` if a line is probably part of some multiline code.
fn multiline_case(line: &str) -> bool {
if line.ends_with('\\') {
return true;
}
if MULTILINE_ASSIGNMENT_REGEX.is_match(line) {
return true;
}
if BRACKET_REGEX.is_match(line) {
return true;
}
false
}
#[cfg(test)]
mod tests {
use crate::eradicate::detection::comment_contains_code;
#[test]
fn comment_contains_code_basic() {
assert!(comment_contains_code("# x = 1"));
assert!(comment_contains_code("#from foo import eradicate"));
assert!(comment_contains_code("#import eradicate"));
assert!(comment_contains_code(r#"#"key": value,"#));
assert!(comment_contains_code(r#"#"key": "value","#));
assert!(comment_contains_code(r#"#"key": 1 + 1,"#));
assert!(comment_contains_code("#'key': 1 + 1,"));
assert!(comment_contains_code(r#"#"key": {"#));
assert!(comment_contains_code("#}"));
assert!(comment_contains_code("#} )]"));
assert!(!comment_contains_code("#"));
assert!(!comment_contains_code("# This is a (real) comment."));
assert!(!comment_contains_code("# 123"));
assert!(!comment_contains_code("# 123.1"));
assert!(!comment_contains_code("# 1, 2, 3"));
assert!(!comment_contains_code("x = 1 # x = 1"));
assert!(!comment_contains_code(
"# pylint: disable=redefined-outer-name"
));
assert!(!comment_contains_code("# Issue #999: This is not code"));
// TODO(charlie): This should be `true` under aggressive mode.
assert!(!comment_contains_code("#},"));
}
#[test]
fn comment_contains_code_with_print() {
assert!(comment_contains_code("#print"));
assert!(comment_contains_code("#print(1)"));
assert!(comment_contains_code("#print 1"));
assert!(!comment_contains_code("#to print"));
}
#[test]
fn comment_contains_code_with_return() {
assert!(comment_contains_code("#return x"));
assert!(!comment_contains_code("#to print"));
}
#[test]
fn comment_contains_code_with_multiline() {
assert!(comment_contains_code("#else:"));
assert!(comment_contains_code("# else : "));
assert!(comment_contains_code(r#"# "foo %d" % \\"#));
assert!(comment_contains_code("#elif True:"));
assert!(comment_contains_code("#x = foo("));
assert!(comment_contains_code("#except Exception:"));
assert!(!comment_contains_code("# this is = to that :("));
assert!(!comment_contains_code("#else"));
assert!(!comment_contains_code("#or else:"));
assert!(!comment_contains_code("#else True:"));
// TODO(charlie): This should be `true` under aggressive mode.
assert!(!comment_contains_code("#def foo():"));
}
#[test]
fn comment_contains_code_with_sentences() {
assert!(!comment_contains_code("#code is good"));
}
#[test]
fn comment_contains_code_with_encoding() {
assert!(comment_contains_code("# codings=utf-8"));
assert!(!comment_contains_code("# coding=utf-8"));
assert!(!comment_contains_code("#coding= utf-8"));
assert!(!comment_contains_code("# coding: utf-8"));
assert!(!comment_contains_code("# encoding: utf8"));
}
#[test]
fn comment_contains_code_with_default_allowlist() {
assert!(!comment_contains_code("# pylint: disable=A0123"));
assert!(!comment_contains_code("# pylint:disable=A0123"));
assert!(!comment_contains_code("# pylint: disable = A0123"));
assert!(!comment_contains_code("# pylint:disable = A0123"));
assert!(!comment_contains_code("# pyright: reportErrorName=true"));
assert!(!comment_contains_code("# noqa"));
assert!(!comment_contains_code("# NOQA"));
assert!(!comment_contains_code("# noqa: A123"));
assert!(!comment_contains_code("# noqa:A123"));
assert!(!comment_contains_code("# nosec"));
assert!(!comment_contains_code("# fmt: on"));
assert!(!comment_contains_code("# fmt: off"));
assert!(!comment_contains_code("# fmt:on"));
assert!(!comment_contains_code("# fmt:off"));
assert!(!comment_contains_code("# isort: on"));
assert!(!comment_contains_code("# isort:on"));
assert!(!comment_contains_code("# isort: off"));
assert!(!comment_contains_code("# isort:off"));
assert!(!comment_contains_code("# isort: skip"));
assert!(!comment_contains_code("# isort:skip"));
assert!(!comment_contains_code("# isort: skip_file"));
assert!(!comment_contains_code("# isort:skip_file"));
assert!(!comment_contains_code("# isort: split"));
assert!(!comment_contains_code("# isort:split"));
assert!(!comment_contains_code("# isort: dont-add-imports"));
assert!(!comment_contains_code("# isort:dont-add-imports"));
assert!(!comment_contains_code(
"# isort: dont-add-imports: [\"import os\"]"
));
assert!(!comment_contains_code(
"# isort:dont-add-imports: [\"import os\"]"
));
assert!(!comment_contains_code(
"# isort: dont-add-imports:[\"import os\"]"
));
assert!(!comment_contains_code(
"# isort:dont-add-imports:[\"import os\"]"
));
assert!(!comment_contains_code("# type: ignore"));
assert!(!comment_contains_code("# type:ignore"));
assert!(!comment_contains_code("# type: ignore[import]"));
assert!(!comment_contains_code("# type:ignore[import]"));
assert!(!comment_contains_code("# TODO: Do that"));
assert!(!comment_contains_code("# FIXME: Fix that"));
assert!(!comment_contains_code("# XXX: What ever"));
}
}

2
src/eradicate/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod checks;
pub mod detection;

View File

@@ -7,7 +7,6 @@ mod tests {
use anyhow::Result;
use crate::autofix::fixer;
use crate::checks::CheckCode;
use crate::linter::test_path;
use crate::{flake8_annotations, Settings};
@@ -31,7 +30,7 @@ mod tests {
CheckCode::ANN401,
])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -57,7 +56,7 @@ mod tests {
CheckCode::ANN102,
])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -83,7 +82,7 @@ mod tests {
CheckCode::ANN206,
])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -109,7 +108,7 @@ mod tests {
CheckCode::ANN206,
])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
@@ -129,7 +128,7 @@ mod tests {
},
..Settings::for_rules(vec![CheckCode::ANN401])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);

View File

@@ -1,5 +1,3 @@
use std::ops::Deref;
use rustpython_ast::{Arguments, Constant, Expr, ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
@@ -23,18 +21,16 @@ where
fn visit_stmt(&mut self, stmt: &'b Stmt) {
match &stmt.node {
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
// No recurse.
}
StmtKind::Return { value } => {
self.returns.push(value.as_ref().map(|expr| expr.deref()))
// Don't recurse.
}
StmtKind::Return { value } => self.returns.push(value.as_ref().map(|expr| &**expr)),
_ => visitor::walk_stmt(self, stmt),
}
}
}
fn is_none_returning(body: &[Stmt]) -> bool {
let mut visitor: ReturnStatementVisitor = Default::default();
let mut visitor = ReturnStatementVisitor::default();
for stmt in body {
visitor.visit_stmt(stmt);
}

View File

@@ -21,6 +21,7 @@ pub struct Options {
}
#[derive(Debug, Hash, Default)]
#[allow(clippy::struct_excessive_bools)]
pub struct Settings {
pub mypy_init_return: bool,
pub suppress_dummy_args: bool,
@@ -29,6 +30,7 @@ pub struct Settings {
}
impl Settings {
#[allow(clippy::needless_pass_by_value)]
pub fn from_options(options: Options) -> Self {
Self {
mypy_init_return: options.mypy_init_return.unwrap_or_default(),

View File

@@ -5,6 +5,19 @@ use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
const FUNC_NAME_ALLOWLIST: &[&str] = &["get", "setdefault", "pop", "fromkeys"];
/// Returns `true` if an argument is allowed to use a boolean trap. To return
/// `true`, the function name must be explicitly allowed, and the argument must
/// be either the first or second argument in the call.
fn allow_boolean_trap(func: &Expr) -> bool {
if let ExprKind::Attribute { attr, .. } = &func.node {
FUNC_NAME_ALLOWLIST.contains(&attr.as_ref())
} else {
false
}
}
fn is_boolean_arg(arg: &Expr) -> bool {
matches!(
&arg.node,
@@ -51,7 +64,7 @@ pub fn check_boolean_default_value_in_function_definition(
checker: &mut Checker,
arguments: &Arguments,
) {
for arg in arguments.defaults.iter() {
for arg in &arguments.defaults {
add_if_boolean(
checker,
arg,
@@ -60,8 +73,15 @@ pub fn check_boolean_default_value_in_function_definition(
}
}
pub fn check_boolean_positional_value_in_function_call(checker: &mut Checker, args: &[Expr]) {
for arg in args {
pub fn check_boolean_positional_value_in_function_call(
checker: &mut Checker,
args: &[Expr],
func: &Expr,
) {
for (index, arg) in args.iter().enumerate() {
if index < 2 && allow_boolean_trap(func) {
continue;
}
add_if_boolean(
checker,
arg,

View File

@@ -7,7 +7,6 @@ mod tests {
use anyhow::Result;
use crate::autofix::fixer;
use crate::checks::CheckCode;
use crate::linter::test_path;
use crate::{flake8_bugbear, Settings};
@@ -26,7 +25,7 @@ mod tests {
},
..Settings::for_rules(vec![CheckCode::B008])
},
&fixer::Mode::Generate,
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);

View File

@@ -17,8 +17,7 @@ fn is_abc_class(
.node
.arg
.as_ref()
.map(|a| a == "metaclass")
.unwrap_or(false)
.map_or(false, |a| a == "metaclass")
&& match_module_member(
&keyword.node.value,
"abc",

View File

@@ -1,4 +1,4 @@
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Stmt, StmtKind};
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::autofix::Fix;
@@ -8,16 +8,16 @@ use crate::code_gen::SourceGenerator;
fn assertion_error(msg: Option<&Expr>) -> Stmt {
Stmt::new(
Default::default(),
Default::default(),
Location::default(),
Location::default(),
StmtKind::Raise {
exc: Some(Box::new(Expr::new(
Default::default(),
Default::default(),
Location::default(),
Location::default(),
ExprKind::Call {
func: Box::new(Expr::new(
Default::default(),
Default::default(),
Location::default(),
Location::default(),
ExprKind::Name {
id: "AssertionError".to_string(),
ctx: ExprContext::Load,
@@ -46,14 +46,13 @@ pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: Option
let mut check = Check::new(CheckKind::DoNotAssertFalse, Range::from_located(test));
if checker.patch(check.kind.code()) {
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_stmt(&assertion_error(msg)) {
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
stmt.location,
stmt.end_location.unwrap(),
));
}
generator.unparse_stmt(&assertion_error(msg));
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
stmt.location,
stmt.end_location.unwrap(),
));
}
}
checker.add_check(check);

View File

@@ -1,7 +1,9 @@
use std::collections::BTreeSet;
use itertools::Itertools;
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Stmt};
use rustpython_ast::{
Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Location, Stmt,
};
use crate::ast::helpers;
use crate::ast::types::Range;
@@ -12,8 +14,8 @@ use crate::code_gen::SourceGenerator;
fn type_pattern(elts: Vec<&Expr>) -> Expr {
Expr::new(
Default::default(),
Default::default(),
Location::default(),
Location::default(),
ExprKind::Tuple {
elts: elts.into_iter().cloned().collect(),
ctx: ExprContext::Load,
@@ -26,9 +28,9 @@ fn duplicate_handler_exceptions<'a>(
expr: &'a Expr,
elts: &'a [Expr],
) -> BTreeSet<Vec<&'a str>> {
let mut seen: BTreeSet<Vec<&str>> = Default::default();
let mut duplicates: BTreeSet<Vec<&str>> = Default::default();
let mut unique_elts: Vec<&Expr> = Default::default();
let mut seen: BTreeSet<Vec<&str>> = BTreeSet::default();
let mut duplicates: BTreeSet<Vec<&str>> = BTreeSet::default();
let mut unique_elts: Vec<&Expr> = Vec::default();
for type_ in elts {
let call_path = helpers::collect_call_paths(type_);
if !call_path.is_empty() {
@@ -57,14 +59,13 @@ fn duplicate_handler_exceptions<'a>(
if checker.patch(check.kind.code()) {
// TODO(charlie): If we have a single element, remove the tuple.
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(&type_pattern(unique_elts), 0) {
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
))
}
generator.unparse_expr(&type_pattern(unique_elts), 0);
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
}
checker.add_check(check);
@@ -75,8 +76,8 @@ fn duplicate_handler_exceptions<'a>(
}
pub fn duplicate_exceptions(checker: &mut Checker, stmt: &Stmt, handlers: &[Excepthandler]) {
let mut seen: BTreeSet<Vec<&str>> = Default::default();
let mut duplicates: BTreeSet<Vec<&str>> = Default::default();
let mut seen: BTreeSet<Vec<&str>> = BTreeSet::default();
let mut duplicates: BTreeSet<Vec<&str>> = BTreeSet::default();
for handler in handlers {
match &handler.node {
ExcepthandlerKind::ExceptHandler { type_, .. } => {

View File

@@ -60,9 +60,9 @@ where
self.checks.push((
CheckKind::FunctionCallArgumentDefault(compose_call_path(expr)),
Range::from_located(expr),
))
));
}
visitor::walk_expr(self, expr)
visitor::walk_expr(self, expr);
}
ExprKind::Lambda { .. } => {}
_ => visitor::walk_expr(self, expr),

View File

@@ -0,0 +1,232 @@
use rustc_hash::FxHashSet;
use rustpython_ast::{Comprehension, Expr, ExprContext, ExprKind, Stmt, StmtKind};
use crate::ast::helpers::collect_arg_names;
use crate::ast::types::{Node, Range};
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
#[derive(Default)]
struct LoadedNamesVisitor<'a> {
// Tuple of: name, defining expression, and defining range.
names: Vec<(&'a str, &'a Expr, Range)>,
// If we're in an f-string, the range of the defining expression.
in_f_string: Option<Range>,
}
/// `Visitor` to collect all used identifiers in a statement.
impl<'a, 'b> Visitor<'b> for LoadedNamesVisitor<'a>
where
'b: 'a,
{
fn visit_expr(&mut self, expr: &'b Expr) {
match &expr.node {
ExprKind::JoinedStr { .. } => {
let prev_in_f_string = self.in_f_string;
self.in_f_string = Some(Range::from_located(expr));
visitor::walk_expr(self, expr);
self.in_f_string = prev_in_f_string;
}
ExprKind::Name { id, ctx } if matches!(ctx, ExprContext::Load) => {
self.names.push((
id,
expr,
self.in_f_string
.unwrap_or_else(|| Range::from_located(expr)),
));
}
_ => visitor::walk_expr(self, expr),
}
}
}
#[derive(Default)]
struct SuspiciousVariablesVisitor<'a> {
names: Vec<(&'a str, &'a Expr, Range)>,
}
/// `Visitor` to collect all suspicious variables (those referenced in
/// functions, but not bound as arguments).
impl<'a, 'b> Visitor<'b> for SuspiciousVariablesVisitor<'a>
where
'b: 'a,
{
fn visit_stmt(&mut self, stmt: &'b Stmt) {
match &stmt.node {
StmtKind::FunctionDef { args, body, .. }
| StmtKind::AsyncFunctionDef { args, body, .. } => {
// Collect all loaded variable names.
let mut visitor = LoadedNamesVisitor::default();
for stmt in body {
visitor.visit_stmt(stmt);
}
// Collect all argument names.
let arg_names = collect_arg_names(args);
// Treat any non-arguments as "suspicious".
self.names.extend(
visitor
.names
.into_iter()
.filter(|(id, ..)| !arg_names.contains(id)),
);
}
_ => visitor::walk_stmt(self, stmt),
}
}
fn visit_expr(&mut self, expr: &'b Expr) {
match &expr.node {
ExprKind::Lambda { args, body } => {
// Collect all loaded variable names.
let mut visitor = LoadedNamesVisitor::default();
visitor.visit_expr(body);
// Collect all argument names.
let arg_names = collect_arg_names(args);
// Treat any non-arguments as "suspicious".
self.names.extend(
visitor
.names
.into_iter()
.filter(|(id, ..)| !arg_names.contains(id)),
);
}
_ => visitor::walk_expr(self, expr),
}
}
}
#[derive(Default)]
struct NamesFromAssignmentsVisitor<'a> {
names: FxHashSet<&'a str>,
}
/// `Visitor` to collect all names used in an assignment expression.
impl<'a, 'b> Visitor<'b> for NamesFromAssignmentsVisitor<'a>
where
'b: 'a,
{
fn visit_expr(&mut self, expr: &'b Expr) {
match &expr.node {
ExprKind::Name { id, .. } => {
self.names.insert(id.as_str());
}
ExprKind::Starred { value, .. } => {
self.visit_expr(value);
}
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
for expr in elts {
self.visit_expr(expr);
}
}
_ => {}
}
}
}
#[derive(Default)]
struct AssignedNamesVisitor<'a> {
names: FxHashSet<&'a str>,
}
/// `Visitor` to collect all used identifiers in a statement.
impl<'a, 'b> Visitor<'b> for AssignedNamesVisitor<'a>
where
'b: 'a,
{
fn visit_stmt(&mut self, stmt: &'b Stmt) {
if matches!(
&stmt.node,
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. }
) {
// Don't recurse.
return;
}
match &stmt.node {
StmtKind::Assign { targets, .. } => {
let mut visitor = NamesFromAssignmentsVisitor::default();
for expr in targets {
visitor.visit_expr(expr);
}
self.names.extend(visitor.names);
}
StmtKind::AugAssign { target, .. }
| StmtKind::AnnAssign { target, .. }
| StmtKind::For { target, .. }
| StmtKind::AsyncFor { target, .. } => {
let mut visitor = NamesFromAssignmentsVisitor::default();
visitor.visit_expr(target);
self.names.extend(visitor.names);
}
_ => {}
}
visitor::walk_stmt(self, stmt);
}
fn visit_expr(&mut self, expr: &'b Expr) {
if matches!(&expr.node, ExprKind::Lambda { .. }) {
// Don't recurse.
return;
}
visitor::walk_expr(self, expr);
}
fn visit_comprehension(&mut self, comprehension: &'b Comprehension) {
let mut visitor = NamesFromAssignmentsVisitor::default();
visitor.visit_expr(&comprehension.target);
self.names.extend(visitor.names);
visitor::walk_comprehension(self, comprehension);
}
}
/// B023
pub fn function_uses_loop_variable<'a, 'b>(checker: &'a mut Checker<'b>, node: &Node<'b>)
where
'b: 'a,
{
// Identify any "suspicious" variables. These are defined as variables that are
// referenced in a function or lambda body, but aren't bound as arguments.
let suspicious_variables = {
let mut visitor = SuspiciousVariablesVisitor::<'b>::default();
match node {
Node::Stmt(stmt) => visitor.visit_stmt(stmt),
Node::Expr(expr) => visitor.visit_expr(expr),
}
visitor.names
};
if !suspicious_variables.is_empty() {
// Identify any variables that are assigned in the loop (ignoring functions).
let reassigned_in_loop = {
let mut visitor = AssignedNamesVisitor::<'b>::default();
match node {
Node::Stmt(stmt) => visitor.visit_stmt(stmt),
Node::Expr(expr) => visitor.visit_expr(expr),
}
visitor.names
};
// If a variable was used in a function or lambda body, and assigned in the
// loop, flag it.
for (name, expr, range) in suspicious_variables {
if reassigned_in_loop.contains(name) {
if !checker.seen_b023.contains(&expr) {
checker.seen_b023.push(expr);
checker.add_check(Check::new(
CheckKind::FunctionUsesLoopVariable(name.to_string()),
range,
));
}
}
}
}
}

View File

@@ -1,4 +1,4 @@
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind};
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location};
use crate::ast::types::Range;
use crate::autofix::Fix;
@@ -10,8 +10,8 @@ use crate::python::keyword::KWLIST;
fn attribute(value: &Expr, attr: &str) -> Expr {
Expr::new(
Default::default(),
Default::default(),
Location::default(),
Location::default(),
ExprKind::Attribute {
value: Box::new(value.clone()),
attr: attr.to_string(),
@@ -35,14 +35,13 @@ pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
Check::new(CheckKind::GetAttrWithConstant, Range::from_located(expr));
if checker.patch(check.kind.code()) {
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(&attribute(obj, value), 0) {
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
generator.unparse_expr(&attribute(obj, value), 0);
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
}
checker.add_check(check);

View File

@@ -31,7 +31,7 @@ where
}
ExprKind::Lambda { args, body } => {
visitor::walk_expr(self, body);
for arg in args.args.iter() {
for arg in &args.args {
self.names.remove(arg.node.arg.as_str());
}
}

View File

@@ -7,10 +7,12 @@ pub use cannot_raise_literal::cannot_raise_literal;
pub use duplicate_exceptions::duplicate_exceptions;
pub use f_string_docstring::f_string_docstring;
pub use function_call_argument_default::function_call_argument_default;
pub use function_uses_loop_variable::function_uses_loop_variable;
pub use getattr_with_constant::getattr_with_constant;
pub use jump_statement_in_finally::jump_statement_in_finally;
pub use loop_variable_overrides_iterator::loop_variable_overrides_iterator;
pub use mutable_argument_default::mutable_argument_default;
pub use raise_without_from_inside_except::raise_without_from_inside_except;
pub use redundant_tuple_in_exception_handler::redundant_tuple_in_exception_handler;
pub use setattr_with_constant::setattr_with_constant;
pub use star_arg_unpacking_after_keyword_arg::star_arg_unpacking_after_keyword_arg;
@@ -31,10 +33,12 @@ mod cannot_raise_literal;
mod duplicate_exceptions;
mod f_string_docstring;
mod function_call_argument_default;
mod function_uses_loop_variable;
mod getattr_with_constant;
mod jump_statement_in_finally;
mod loop_variable_overrides_iterator;
mod mutable_argument_default;
mod raise_without_from_inside_except;
mod redundant_tuple_in_exception_handler;
mod setattr_with_constant;
mod star_arg_unpacking_after_keyword_arg;

View File

@@ -0,0 +1,56 @@
use rustpython_ast::{ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::ast::visitor::Visitor;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
use crate::python::string::is_lower;
struct RaiseVisitor {
checks: Vec<Check>,
}
impl<'a> Visitor<'a> for RaiseVisitor {
fn visit_stmt(&mut self, stmt: &'a Stmt) {
match &stmt.node {
StmtKind::Raise { exc, cause } => {
if cause.is_none() {
if let Some(exc) = exc {
match &exc.node {
ExprKind::Name { id, .. } if is_lower(id) => {}
_ => {
self.checks.push(Check::new(
CheckKind::RaiseWithoutFromInsideExcept,
Range::from_located(stmt),
));
}
}
}
}
}
StmtKind::ClassDef { .. }
| StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
| StmtKind::Try { .. } => {}
StmtKind::If { body, .. }
| StmtKind::While { body, .. }
| StmtKind::With { body, .. }
| StmtKind::AsyncWith { body, .. }
| StmtKind::For { body, .. }
| StmtKind::AsyncFor { body, .. } => {
for stmt in body {
self.visit_stmt(stmt);
}
}
_ => {}
}
}
}
pub fn raise_without_from_inside_except(checker: &mut Checker, body: &[Stmt]) {
let mut visitor = RaiseVisitor { checks: vec![] };
for stmt in body {
visitor.visit_stmt(stmt);
}
checker.add_checks(visitor.checks.into_iter());
}

View File

@@ -64,22 +64,21 @@ pub fn redundant_tuple_in_exception_handler(checker: &mut Checker, handlers: &[E
);
if checker.patch(check.kind.code()) {
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(elt, 0) {
if let Ok(content) = generator.generate() {
match match_tuple_range(handler, checker.locator) {
Ok(range) => {
check.amend(Fix::replacement(
content,
range.location,
range.end_location,
));
}
Err(e) => error!("Failed to locate parentheses: {}", e),
generator.unparse_expr(elt, 0);
if let Ok(content) = generator.generate() {
match match_tuple_range(handler, checker.locator) {
Ok(range) => {
check.amend(Fix::replacement(
content,
range.location,
range.end_location,
));
}
Err(e) => error!("Failed to locate parentheses: {}", e),
}
}
}
checker.add_check(check)
checker.add_check(check);
}
}
}

View File

@@ -1,4 +1,6 @@
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Stmt, StmtKind};
use anyhow::Result;
use log::error;
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::autofix::Fix;
@@ -8,14 +10,14 @@ use crate::code_gen::SourceGenerator;
use crate::python::identifiers::IDENTIFIER_REGEX;
use crate::python::keyword::KWLIST;
fn assignment(obj: &Expr, name: &str, value: &Expr) -> Option<String> {
fn assignment(obj: &Expr, name: &str, value: &Expr) -> Result<String> {
let stmt = Stmt::new(
Default::default(),
Default::default(),
Location::default(),
Location::default(),
StmtKind::Assign {
targets: vec![Expr::new(
Default::default(),
Default::default(),
Location::default(),
Location::default(),
ExprKind::Attribute {
value: Box::new(obj.clone()),
attr: name.to_string(),
@@ -27,10 +29,8 @@ fn assignment(obj: &Expr, name: &str, value: &Expr) -> Option<String> {
},
);
let mut generator = SourceGenerator::new();
match generator.unparse_stmt(&stmt) {
Ok(()) => generator.generate().ok(),
Err(_) => None,
}
generator.unparse_stmt(&stmt);
generator.generate().map_err(std::convert::Into::into)
}
/// B010
@@ -47,13 +47,14 @@ pub fn setattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, ar
let mut check =
Check::new(CheckKind::SetAttrWithConstant, Range::from_located(expr));
if checker.patch(check.kind.code()) {
if let Some(content) = assignment(obj, name, value) {
check.amend(Fix::replacement(
match assignment(obj, name, value) {
Ok(content) => check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
)),
Err(e) => error!("Failed to fix invalid comparison: {}", e),
};
}
checker.add_check(check);
}

View File

@@ -12,7 +12,7 @@ pub fn unary_prefix_increment(checker: &mut Checker, expr: &Expr, op: &Unaryop,
checker.add_check(Check::new(
CheckKind::UnaryPrefixIncrement,
Range::from_located(expr),
))
));
}
}
}

View File

@@ -17,7 +17,7 @@ struct NameFinder<'a> {
impl NameFinder<'_> {
fn new() -> Self {
NameFinder {
names: Default::default(),
names: FxHashMap::default(),
}
}
}
@@ -71,7 +71,7 @@ pub fn unused_loop_control_variable(checker: &mut Checker, target: &Expr, body:
format!("_{name}"),
expr.location,
expr.end_location.unwrap(),
))
));
}
checker.add_check(check);
}

View File

@@ -1,4 +1,4 @@
//! Settings for the `pep8-naming` plugin.
//! Settings for the `flake8-bugbear` plugin.
use serde::{Deserialize, Serialize};

View File

@@ -1,3 +1,4 @@
#[derive(Clone, Copy)]
pub enum ShadowingType {
Variable,
Argument,

View File

@@ -390,11 +390,6 @@ pub fn unnecessary_double_cast_or_process(
args: &[Expr],
location: Range,
) -> Option<Check> {
let outer = function_name(func)?;
if !["list", "tuple", "set", "reversed", "sorted"].contains(&outer) {
return None;
}
fn new_check(inner: &str, outer: &str, location: Range) -> Check {
Check::new(
CheckKind::UnnecessaryDoubleCastOrProcess(inner.to_string(), outer.to_string()),
@@ -402,6 +397,11 @@ pub fn unnecessary_double_cast_or_process(
)
}
let outer = function_name(func)?;
if !["list", "tuple", "set", "reversed", "sorted"].contains(&outer) {
return None;
}
if let ExprKind::Call { func, .. } = &args.first()?.node {
let inner = function_name(func)?;
// Ex) set(tuple(...))

View File

@@ -1,9 +1,9 @@
use anyhow::Result;
use libcst_native::{
Arg, AssignEqual, Call, Codegen, Dict, DictComp, DictElement, Element, Expr, Expression,
LeftCurlyBrace, LeftParen, LeftSquareBracket, List, ListComp, Name, ParenthesizableWhitespace,
RightCurlyBrace, RightParen, RightSquareBracket, Set, SetComp, SimpleString, SimpleWhitespace,
Tuple,
Arg, AssignEqual, Call, Codegen, CodegenState, Dict, DictComp, DictElement, Element, Expr,
Expression, LeftCurlyBrace, LeftParen, LeftSquareBracket, List, ListComp, Name,
ParenthesizableWhitespace, RightCurlyBrace, RightParen, RightSquareBracket, Set, SetComp,
SimpleString, SimpleWhitespace, Tuple,
};
use crate::ast::types::Range;
@@ -39,9 +39,7 @@ pub fn fix_unnecessary_generator_list(
let call = match_call(body)?;
let arg = match_arg(call)?;
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg.value {
generator_exp
} else {
let Expression::GeneratorExp(generator_exp) = &arg.value else {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::GeneratorExp"
));
@@ -60,7 +58,7 @@ pub fn fix_unnecessary_generator_list(
rpar: generator_exp.rpar.clone(),
}));
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -82,9 +80,7 @@ pub fn fix_unnecessary_generator_set(
let call = match_call(body)?;
let arg = match_arg(call)?;
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg.value {
generator_exp
} else {
let Expression::GeneratorExp(generator_exp) = &arg.value else {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::GeneratorExp"
));
@@ -103,7 +99,7 @@ pub fn fix_unnecessary_generator_set(
rpar: generator_exp.rpar.clone(),
}));
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -126,28 +122,20 @@ pub fn fix_unnecessary_generator_dict(
let arg = match_arg(call)?;
// Extract the (k, v) from `(k, v) for ...`.
let generator_exp = if let Expression::GeneratorExp(generator_exp) = &arg.value {
generator_exp
} else {
let Expression::GeneratorExp(generator_exp) = &arg.value else {
return Err(anyhow::anyhow!(
"Expected node to be: Expression::GeneratorExp"
));
};
let tuple = if let Expression::Tuple(tuple) = &generator_exp.elt.as_ref() {
tuple
} else {
let Expression::Tuple(tuple) = &generator_exp.elt.as_ref() else {
return Err(anyhow::anyhow!("Expected node to be: Expression::Tuple"));
};
let key = if let Some(Element::Simple { value, .. }) = &tuple.elements.get(0) {
value
} else {
let Some(Element::Simple { value: key, .. }) = &tuple.elements.get(0) else {
return Err(anyhow::anyhow!(
"Expected tuple to contain a key as the first element"
));
};
let value = if let Some(Element::Simple { value, .. }) = &tuple.elements.get(1) {
value
} else {
let Some(Element::Simple { value, .. }) = &tuple.elements.get(1) else {
return Err(anyhow::anyhow!(
"Expected tuple to contain a key as the second element"
));
@@ -163,13 +151,13 @@ pub fn fix_unnecessary_generator_dict(
rbrace: RightCurlyBrace {
whitespace_before: arg.whitespace_after_arg.clone(),
},
lpar: Default::default(),
rpar: Default::default(),
whitespace_before_colon: Default::default(),
lpar: vec![],
rpar: vec![],
whitespace_before_colon: ParenthesizableWhitespace::default(),
whitespace_after_colon: ParenthesizableWhitespace::SimpleWhitespace(SimpleWhitespace(" ")),
}));
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -192,9 +180,7 @@ pub fn fix_unnecessary_list_comprehension_set(
let call = match_call(body)?;
let arg = match_arg(call)?;
let list_comp = if let Expression::ListComp(list_comp) = &arg.value {
list_comp
} else {
let Expression::ListComp(list_comp) = &arg.value else {
return Err(anyhow::anyhow!("Expected node to be: Expression::ListComp"));
};
@@ -211,7 +197,7 @@ pub fn fix_unnecessary_list_comprehension_set(
rpar: list_comp.rpar.clone(),
}));
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -233,25 +219,18 @@ pub fn fix_unnecessary_list_comprehension_dict(
let call = match_call(body)?;
let arg = match_arg(call)?;
let list_comp = if let Expression::ListComp(list_comp) = &arg.value {
list_comp
} else {
let Expression::ListComp(list_comp) = &arg.value else {
return Err(anyhow::anyhow!("Expected node to be: Expression::ListComp"));
};
let tuple = if let Expression::Tuple(tuple) = &*list_comp.elt {
tuple
} else {
let Expression::Tuple(tuple) = &*list_comp.elt else {
return Err(anyhow::anyhow!("Expected node to be: Expression::Tuple"));
};
let (key, comma, value) = match &tuple.elements[..] {
[Element::Simple {
let [Element::Simple {
value: key,
comma: Some(comma),
}, Element::Simple { value, .. }] => (key, comma, value),
_ => return Err(anyhow::anyhow!("Expected tuple with two elements")),
};
}, Element::Simple { value, .. }] = &tuple.elements[..] else { return Err(anyhow::anyhow!("Expected tuple with two elements")) };
body.value = Expression::DictComp(Box::new(DictComp {
key: Box::new(key.clone()),
@@ -269,7 +248,7 @@ pub fn fix_unnecessary_list_comprehension_dict(
rpar: list_comp.rpar.clone(),
}));
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -312,12 +291,12 @@ pub fn fix_unnecessary_literal_set(
rbrace: RightCurlyBrace {
whitespace_before: arg.whitespace_after_arg.clone(),
},
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
}));
}
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -363,7 +342,7 @@ pub fn fix_unnecessary_literal_dict(
key: key.clone(),
value: value.clone(),
comma: comma.clone(),
whitespace_before_colon: Default::default(),
whitespace_before_colon: ParenthesizableWhitespace::default(),
whitespace_after_colon: ParenthesizableWhitespace::SimpleWhitespace(
SimpleWhitespace(" "),
),
@@ -385,11 +364,11 @@ pub fn fix_unnecessary_literal_dict(
rbrace: RightCurlyBrace {
whitespace_before: arg.whitespace_after_arg.clone(),
},
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
}));
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -409,9 +388,7 @@ pub fn fix_unnecessary_collection_call(
let mut tree = match_module(&module_text)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let name = if let Expression::Name(name) = &call.func.as_ref() {
name
} else {
let Expression::Name(name) = &call.func.as_ref() else {
return Err(anyhow::anyhow!("Expected node to be: Expression::Name"));
};
@@ -422,28 +399,28 @@ pub fn fix_unnecessary_collection_call(
match name.value {
"tuple" => {
body.value = Expression::Tuple(Box::new(Tuple {
elements: Default::default(),
lpar: vec![Default::default()],
rpar: vec![Default::default()],
elements: vec![],
lpar: vec![LeftParen::default()],
rpar: vec![RightParen::default()],
}));
}
"list" => {
body.value = Expression::List(Box::new(List {
elements: Default::default(),
lbracket: Default::default(),
rbracket: Default::default(),
lpar: Default::default(),
rpar: Default::default(),
elements: vec![],
lbracket: LeftSquareBracket::default(),
rbracket: RightSquareBracket::default(),
lpar: vec![],
rpar: vec![],
}));
}
"dict" => {
if call.args.is_empty() {
body.value = Expression::Dict(Box::new(Dict {
elements: Default::default(),
lbrace: Default::default(),
rbrace: Default::default(),
lpar: Default::default(),
rpar: Default::default(),
elements: vec![],
lbrace: LeftCurlyBrace::default(),
rbrace: RightCurlyBrace::default(),
lpar: vec![],
rpar: vec![],
}));
} else {
// Quote each argument.
@@ -465,12 +442,12 @@ pub fn fix_unnecessary_collection_call(
.map(|(i, arg)| DictElement::Simple {
key: Expression::SimpleString(Box::new(SimpleString {
value: &arena[i],
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
})),
value: arg.value.clone(),
comma: arg.comma.clone(),
whitespace_before_colon: Default::default(),
whitespace_before_colon: ParenthesizableWhitespace::default(),
whitespace_after_colon: ParenthesizableWhitespace::SimpleWhitespace(
SimpleWhitespace(" "),
),
@@ -490,8 +467,8 @@ pub fn fix_unnecessary_collection_call(
.whitespace_after_arg
.clone(),
},
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
}));
}
}
@@ -502,7 +479,7 @@ pub fn fix_unnecessary_collection_call(
}
};
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -558,7 +535,7 @@ pub fn fix_unnecessary_literal_within_tuple_call(
}],
}));
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -612,11 +589,11 @@ pub fn fix_unnecessary_literal_within_list_call(
rbracket: RightSquareBracket {
whitespace_before: whitespace_before.clone(),
},
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
}));
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -640,7 +617,7 @@ pub fn fix_unnecessary_list_call(
body.value = arg.value.clone();
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -695,22 +672,22 @@ pub fn fix_unnecessary_call_around_sorted(
args.push(Arg {
value: Expression::Name(Box::new(Name {
value: "True",
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
})),
keyword: Some(Name {
value: "reverse",
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
}),
equal: Some(AssignEqual {
whitespace_before: Default::default(),
whitespace_after: Default::default(),
whitespace_before: ParenthesizableWhitespace::default(),
whitespace_after: ParenthesizableWhitespace::default(),
}),
comma: Default::default(),
star: Default::default(),
whitespace_after_star: Default::default(),
whitespace_after_arg: Default::default(),
comma: None,
star: "",
whitespace_after_star: ParenthesizableWhitespace::default(),
whitespace_after_arg: ParenthesizableWhitespace::default(),
});
args
};
@@ -722,11 +699,11 @@ pub fn fix_unnecessary_call_around_sorted(
rpar: inner_call.rpar.clone(),
whitespace_after_func: inner_call.whitespace_after_func.clone(),
whitespace_before_args: inner_call.whitespace_before_args.clone(),
}))
}));
}
}
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
@@ -750,45 +727,45 @@ pub fn fix_unnecessary_comprehension(
body.value = Expression::Call(Box::new(Call {
func: Box::new(Expression::Name(Box::new(Name {
value: "list",
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
}))),
args: vec![Arg {
value: inner.for_in.iter.clone(),
keyword: Default::default(),
equal: Default::default(),
comma: Default::default(),
star: Default::default(),
whitespace_after_star: Default::default(),
whitespace_after_arg: Default::default(),
keyword: None,
equal: None,
comma: None,
star: "",
whitespace_after_star: ParenthesizableWhitespace::default(),
whitespace_after_arg: ParenthesizableWhitespace::default(),
}],
lpar: Default::default(),
rpar: Default::default(),
whitespace_after_func: Default::default(),
whitespace_before_args: Default::default(),
}))
lpar: vec![],
rpar: vec![],
whitespace_after_func: ParenthesizableWhitespace::default(),
whitespace_before_args: ParenthesizableWhitespace::default(),
}));
}
Expression::SetComp(inner) => {
body.value = Expression::Call(Box::new(Call {
func: Box::new(Expression::Name(Box::new(Name {
value: "set",
lpar: Default::default(),
rpar: Default::default(),
lpar: vec![],
rpar: vec![],
}))),
args: vec![Arg {
value: inner.for_in.iter.clone(),
keyword: Default::default(),
equal: Default::default(),
comma: Default::default(),
star: Default::default(),
whitespace_after_star: Default::default(),
whitespace_after_arg: Default::default(),
keyword: None,
equal: None,
comma: None,
star: "",
whitespace_after_star: ParenthesizableWhitespace::default(),
whitespace_after_arg: ParenthesizableWhitespace::default(),
}],
lpar: Default::default(),
rpar: Default::default(),
whitespace_after_func: Default::default(),
whitespace_before_args: Default::default(),
}))
lpar: vec![],
rpar: vec![],
whitespace_after_func: ParenthesizableWhitespace::default(),
whitespace_before_args: ParenthesizableWhitespace::default(),
}));
}
_ => {
return Err(anyhow::anyhow!(
@@ -797,7 +774,7 @@ pub fn fix_unnecessary_comprehension(
}
}
let mut state = Default::default();
let mut state = CodegenState::default();
tree.codegen(&mut state);
Ok(Fix::replacement(

View File

@@ -0,0 +1,64 @@
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{Expr, Stmt};
use crate::ast::helpers::{collect_call_paths, dealias_call_path, match_call_path};
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind};
use crate::flake8_debugger::types::DebuggerUsingType;
const DEBUGGERS: &[(&str, &str)] = &[
("pdb", "set_trace"),
("pudb", "set_trace"),
("ipdb", "set_trace"),
("ipdb", "sset_trace"),
("IPython.terminal.embed", "InteractiveShellEmbed"),
("IPython.frontend.terminal.embed", "InteractiveShellEmbed"),
("celery.contrib.rdb", "set_trace"),
("builtins", "breakpoint"),
("", "breakpoint"),
];
/// Checks for the presence of a debugger call.
pub fn debugger_call(
expr: &Expr,
func: &Expr,
from_imports: &FxHashMap<&str, FxHashSet<&str>>,
import_aliases: &FxHashMap<&str, &str>,
) -> Option<Check> {
let call_path = dealias_call_path(collect_call_paths(func), import_aliases);
if DEBUGGERS
.iter()
.any(|(module, member)| match_call_path(&call_path, module, member, from_imports))
{
Some(Check::new(
CheckKind::Debugger(DebuggerUsingType::Call(call_path.join("."))),
Range::from_located(expr),
))
} else {
None
}
}
/// Checks for the presence of a debugger import.
pub fn debugger_import(stmt: &Stmt, module: Option<&str>, name: &str) -> Option<Check> {
if let Some(module) = module {
if let Some((module_name, member)) = DEBUGGERS
.iter()
.find(|(module_name, member)| module_name == &module && member == &name)
{
return Some(Check::new(
CheckKind::Debugger(DebuggerUsingType::Import(format!("{module_name}.{member}"))),
Range::from_located(stmt),
));
}
} else if DEBUGGERS
.iter()
.any(|(module_name, ..)| module_name == &name)
{
return Some(Check::new(
CheckKind::Debugger(DebuggerUsingType::Import(name.to_string())),
Range::from_located(stmt),
));
}
None
}

View File

@@ -0,0 +1,2 @@
pub mod checks;
pub mod types;

View File

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

View File

@@ -35,7 +35,7 @@ pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
if fix.patch.content.is_empty() || fix.patch.content == "pass" {
checker.deletions.insert(context.defined_by);
}
check.amend(fix)
check.amend(fix);
}
Err(e) => error!("Failed to remove print call: {}", e),
}

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