Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90bfc4ec4d | ||
|
|
b04a6a3f7c | ||
|
|
8ec14e7ee2 | ||
|
|
17c5cd7c42 | ||
|
|
7d8360a1de | ||
|
|
910ee523dd | ||
|
|
72e35a535e | ||
|
|
b4e1563517 | ||
|
|
5717cc97d7 | ||
|
|
2c89a19f76 | ||
|
|
82fea36bb3 | ||
|
|
63d63e8c12 | ||
|
|
9d136de55a | ||
|
|
1821c07367 | ||
|
|
1fe90ef7f4 | ||
|
|
b5cb9485f6 | ||
|
|
4d798512b1 | ||
|
|
5f9815b103 | ||
|
|
0d3fac1bf9 | ||
|
|
ff0e5f5cb4 | ||
|
|
374d57d822 | ||
|
|
85b2a9920f | ||
|
|
3c22913470 | ||
|
|
ea03a59b72 | ||
|
|
058a5276b0 | ||
|
|
62d4096be3 | ||
|
|
8961da7b89 | ||
|
|
58bcffbe2d | ||
|
|
f67727b13c | ||
|
|
fea029ae35 | ||
|
|
3e3c3c7421 | ||
|
|
9047bf680d | ||
|
|
d170388b7b | ||
|
|
502d3316f9 | ||
|
|
a8159f9893 | ||
|
|
71f727c380 | ||
|
|
ce3c45a361 | ||
|
|
29ae6c159d | ||
|
|
1ae07b4c70 | ||
|
|
08ca8788a7 | ||
|
|
8a97a76038 | ||
|
|
d2d84cf5bf | ||
|
|
450970e0e6 | ||
|
|
34ecc69914 | ||
|
|
a310aed128 | ||
|
|
43cc8bc84e | ||
|
|
84bf36194b | ||
|
|
e4d168bb4f | ||
|
|
439642addf | ||
|
|
f5b1f957e3 | ||
|
|
8f99705795 | ||
|
|
9ec7e6bcd6 | ||
|
|
695b06ba60 | ||
|
|
3a2e6926d4 | ||
|
|
d16c3a1186 | ||
|
|
53a2187f02 | ||
|
|
00b5d1059c | ||
|
|
b7acf76aaf | ||
|
|
8cfc0e5cf5 | ||
|
|
aa7681f9ad | ||
|
|
2493d48725 | ||
|
|
1b422a7f12 | ||
|
|
da051624e4 | ||
|
|
da9ae6a42a | ||
|
|
afa59d78bb | ||
|
|
bbc38fea73 | ||
|
|
6bcc11a90f | ||
|
|
6f36e5dd25 | ||
|
|
1d13752eb1 | ||
|
|
394af0dcff | ||
|
|
51cee471a0 | ||
|
|
8df3a5437a | ||
|
|
a21fe716f2 | ||
|
|
558883299a | ||
|
|
048a13c795 | ||
|
|
5a8b7c1d20 | ||
|
|
f8932ec12b | ||
|
|
2e7878ff48 | ||
|
|
5113ded22a | ||
|
|
bf7bf7aa17 | ||
|
|
560c00ff9d | ||
|
|
befe64a10e | ||
|
|
4eccfdeb69 | ||
|
|
4123ba9851 | ||
|
|
e727c24f79 | ||
|
|
bd3b40688f | ||
|
|
b5549382a7 |
7
.github/workflows/ci.yaml
vendored
7
.github/workflows/ci.yaml
vendored
@@ -6,6 +6,11 @@ on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
|
||||
jobs:
|
||||
cargo_build:
|
||||
name: "cargo build"
|
||||
@@ -77,7 +82,7 @@ jobs:
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: cargo clippy --all -- -D warnings
|
||||
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
|
||||
cargo_test:
|
||||
name: "cargo test"
|
||||
|
||||
3
.github/workflows/flake8-to-ruff.yaml
vendored
3
.github/workflows/flake8-to-ruff.yaml
vendored
@@ -10,6 +10,9 @@ env:
|
||||
PACKAGE_NAME: flake8-to-ruff
|
||||
CRATE_NAME: flake8_to_ruff
|
||||
PYTHON_VERSION: "3.7" # to build abi3 wheels
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
|
||||
jobs:
|
||||
macos-x86_64:
|
||||
|
||||
3
.github/workflows/ruff.yaml
vendored
3
.github/workflows/ruff.yaml
vendored
@@ -12,6 +12,9 @@ concurrency:
|
||||
env:
|
||||
PACKAGE_NAME: ruff
|
||||
PYTHON_VERSION: "3.7" # to build abi3 wheels
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_NET_RETRY: 10
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
|
||||
jobs:
|
||||
macos-x86_64:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.110
|
||||
rev: v0.0.124
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
37
Cargo.lock
generated
37
Cargo.lock
generated
@@ -427,11 +427,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"time",
|
||||
"wasm-bindgen",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
@@ -933,11 +930,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.110-dev.0"
|
||||
version = "0.0.124-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.22",
|
||||
"configparser",
|
||||
"fnv",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"ruff",
|
||||
@@ -1622,6 +1620,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nohash-hasher"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.1.2"
|
||||
@@ -2234,11 +2238,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.110"
|
||||
version = "0.0.124"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
"atty",
|
||||
"bincode",
|
||||
"bitflags",
|
||||
"cacache",
|
||||
"chrono",
|
||||
"clap 4.0.22",
|
||||
@@ -2249,12 +2255,14 @@ dependencies = [
|
||||
"dirs 4.0.0",
|
||||
"fern",
|
||||
"filetime",
|
||||
"fnv",
|
||||
"getrandom 0.2.8",
|
||||
"glob",
|
||||
"insta",
|
||||
"itertools",
|
||||
"libcst",
|
||||
"log",
|
||||
"nohash-hasher",
|
||||
"notify",
|
||||
"num-bigint",
|
||||
"once_cell",
|
||||
@@ -2279,7 +2287,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.110"
|
||||
version = "0.0.124"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.22",
|
||||
@@ -2773,17 +2781,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
@@ -3070,12 +3067,6 @@ version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
||||
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.110"
|
||||
version = "0.0.124"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -14,18 +14,22 @@ name = "ruff"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.66" }
|
||||
atty = { version = "0.2.14" }
|
||||
bincode = { version = "1.3.3" }
|
||||
chrono = { version = "0.4.21" }
|
||||
bitflags = { version = "1.3.2" }
|
||||
chrono = { version = "0.4.21", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.0.1", features = ["derive"] }
|
||||
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" }
|
||||
fnv = { version = "1.0.7" }
|
||||
glob = { version = "0.3.0" }
|
||||
itertools = { version = "0.10.5" }
|
||||
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "a13ec97dd4eb925bde4d426c6e422582793b260c" }
|
||||
log = { version = "0.4.17" }
|
||||
nohash-hasher = { version = "0.2.0" }
|
||||
notify = { version = "4.0.17" }
|
||||
num-bigint = { version = "0.4.3" }
|
||||
once_cell = { version = "1.16.0" }
|
||||
|
||||
375
LICENSE
375
LICENSE
@@ -19,3 +19,378 @@ 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.
|
||||
|
||||
The externally maintained libraries from which parts of the Software is derived
|
||||
are:
|
||||
|
||||
- Pyflakes, licensed as follows:
|
||||
"""
|
||||
Copyright 2005-2011 Divmod, Inc.
|
||||
Copyright 2013-2014 Florent Xicluna
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
- autoflake, licensed as follows:
|
||||
"""
|
||||
Copyright (C) 2012-2018 Steven Myint
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- Flake8, licensed as follows:
|
||||
"""
|
||||
== Flake8 License (MIT) ==
|
||||
|
||||
Copyright (C) 2011-2013 Tarek Ziade <tarek@ziade.org>
|
||||
Copyright (C) 2012-2016 Ian Cordasco <graffatcolmingov@gmail.com>
|
||||
|
||||
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-2020, licensed as follows:
|
||||
"""
|
||||
Copyright (c) 2019 Anthony Sottile
|
||||
|
||||
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-annotations, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 - Present S. Co1
|
||||
|
||||
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-bandit, licensed as follows:
|
||||
"""
|
||||
Copyright (c) 2017 Tyler Wince
|
||||
|
||||
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-bugbear, licensed as follows:
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Łukasz Langa
|
||||
|
||||
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-comprehensions, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Adam Johnson
|
||||
|
||||
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-print, 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-quotes, licensed as follows:
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
- isort, licensed as follows:
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Timothy Edmund Crosley
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
- pep8-naming, licensed as follows:
|
||||
"""
|
||||
Copyright © 2013 Florent Xicluna <florent.xicluna@gmail.com>
|
||||
|
||||
Licensed under the terms of the Expat License
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
- pycodestyle, licensed as follows:
|
||||
"""
|
||||
Copyright © 2006-2009 Johann C. Rocholl <johann@rocholl.net>
|
||||
Copyright © 2009-2014 Florent Xicluna <florent.xicluna@gmail.com>
|
||||
Copyright © 2014-2020 Ian Lee <IanLee1521@gmail.com>
|
||||
|
||||
Licensed under the terms of the Expat License
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
- pydocstyle, licensed as follows:
|
||||
"""
|
||||
Copyright (c) 2012 GreenSteam, <http://greensteam.dk/>
|
||||
|
||||
Copyright (c) 2014-2020 Amir Rachum, <http://amir.rachum.com/>
|
||||
|
||||
Copyright (c) 2020 Sambhav Kothari, <https://github.com/samj1912>
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
- pyupgrade, licensed as follows:
|
||||
"""
|
||||
Copyright (c) 2017 Anthony Sottile
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
120
README.md
120
README.md
@@ -36,8 +36,8 @@ faster than any individual tool.
|
||||
automatically convert your existing configuration.)
|
||||
|
||||
Ruff is actively developed and used in major open-source projects
|
||||
like [Zulip](https://github.com/zulip/zulip), [pydantic](https://github.com/pydantic/pydantic),
|
||||
and [Saleor](https://github.com/saleor/saleor).
|
||||
like [FastAPI](https://github.com/tiangolo/fastapi), [Zulip](https://github.com/zulip/zulip),
|
||||
[pydantic](https://github.com/pydantic/pydantic), and [Saleor](https://github.com/saleor/saleor).
|
||||
|
||||
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
|
||||
@@ -48,17 +48,20 @@ Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-mu
|
||||
3. [Supported Rules](#supported-rules)
|
||||
1. [Pyflakes](#pyflakes)
|
||||
2. [pycodestyle](#pycodestyle)
|
||||
3. [pydocstyle](#pydocstyle)
|
||||
4. [pyupgrade](#pyupgrade)
|
||||
5. [pep8-naming](#pep8-naming)
|
||||
6. [flake8-comprehensions](#flake8-comprehensions)
|
||||
7. [flake8-bugbear](#flake8-bugbear)
|
||||
8. [flake8-builtins](#flake8-builtins)
|
||||
9. [flake8-print](#flake8-print)
|
||||
10. [flake8-quotes](#flake8-quotes)
|
||||
11. [flake8-annotations](#flake8-annotations)
|
||||
12. [Ruff-specific rules](#ruff-specific-rules)
|
||||
13. [Meta rules](#meta-rules)
|
||||
3. [isort](#isort)
|
||||
4. [pydocstyle](#pydocstyle)
|
||||
5. [pyupgrade](#pyupgrade)
|
||||
6. [pep8-naming](#pep8-naming)
|
||||
7. [flake8-bandit](#flake8-bandit)
|
||||
8. [flake8-comprehensions](#flake8-comprehensions)
|
||||
9. [flake8-bugbear](#flake8-bugbear)
|
||||
10. [flake8-builtins](#flake8-builtins)
|
||||
11. [flake8-print](#flake8-print)
|
||||
12. [flake8-quotes](#flake8-quotes)
|
||||
13. [flake8-annotations](#flake8-annotations)
|
||||
14. [flake8-2020](#flake8-2020)
|
||||
15. [Ruff-specific rules](#ruff-specific-rules)
|
||||
16. [Meta rules](#meta-rules)
|
||||
5. [Editor Integrations](#editor-integrations)
|
||||
6. [FAQ](#faq)
|
||||
7. [Development](#development)
|
||||
@@ -98,7 +101,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.110
|
||||
rev: v0.0.116
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -234,6 +237,8 @@ Options:
|
||||
Regular expression matching the name of dummy variables
|
||||
--target-version <TARGET_VERSION>
|
||||
The minimum Python version that should be supported
|
||||
--line-length <LINE_LENGTH>
|
||||
Set the line-length for length-associated checks and automatic formatting
|
||||
--stdin-filename <STDIN_FILENAME>
|
||||
The name of the file when passing it through stdin
|
||||
-h, --help
|
||||
@@ -334,7 +339,7 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
|
||||
| F702 | ContinueOutsideLoop | `continue` not properly in loop | |
|
||||
| F704 | YieldOutsideFunction | `yield` or `yield from` statement outside of a function | |
|
||||
| F706 | ReturnOutsideFunction | `return` statement outside of a function/method | |
|
||||
| F707 | DefaultExceptNotLast | An `except:` block as not the last exception handler | |
|
||||
| F707 | DefaultExceptNotLast | An `except` block as not the last exception handler | |
|
||||
| F722 | ForwardAnnotationSyntaxError | Syntax error in forward annotation: `...` | |
|
||||
| F821 | UndefinedName | Undefined name `...` | |
|
||||
| F822 | UndefinedExport | Undefined name `...` in `__all__` | |
|
||||
@@ -408,7 +413,7 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI.
|
||||
| D400 | EndsInPeriod | First line should end with a period | |
|
||||
| D402 | NoSignature | First line should not be the function's signature | |
|
||||
| D403 | FirstLineCapitalized | First word of the first line should be properly capitalized | |
|
||||
| D404 | NoThisPrefix | First word of the docstring should not be 'This' | |
|
||||
| D404 | NoThisPrefix | First word of the docstring should not be "This" | |
|
||||
| D405 | CapitalizeSectionName | Section name should be properly capitalized ("returns") | 🛠 |
|
||||
| D406 | NewLineAfterSectionName | Section name should end with a newline ("Returns") | 🛠 |
|
||||
| D407 | DashedUnderlineAfterSection | Missing dashed underline after section ("Returns") | 🛠 |
|
||||
@@ -439,9 +444,10 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
|
||||
| U006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | 🛠 |
|
||||
| U007 | UsePEP604Annotation | Use `X \| Y` for type annotations | 🛠 |
|
||||
| U008 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | 🛠 |
|
||||
| U009 | PEP3120UnnecessaryCodingComment | utf-8 encoding declaration is unnecessary | 🛠 |
|
||||
| U010 | UnnecessaryFutureImport | Unnessary __future__ import `...` for target Python version | |
|
||||
| U011 | UnnecessaryLRUCacheParams | Unnessary parameters to functools.lru_cache | 🛠 |
|
||||
| U009 | PEP3120UnnecessaryCodingComment | UTF-8 encoding declaration is unnecessary | 🛠 |
|
||||
| U010 | UnnecessaryFutureImport | Unnecessary `__future__` import `...` for target Python version | 🛠 |
|
||||
| U011 | UnnecessaryLRUCacheParams | Unnecessary parameters to `functools.lru_cache` | 🛠 |
|
||||
| U012 | UnnecessaryEncodeUTF8 | Unnecessary call to `encode` as UTF-8 | 🛠 |
|
||||
|
||||
### pep8-naming
|
||||
|
||||
@@ -465,6 +471,19 @@ For more, see [pep8-naming](https://pypi.org/project/pep8-naming/0.13.2/) on PyP
|
||||
| N817 | CamelcaseImportedAsAcronym | Camelcase `...` imported as acronym `...` | |
|
||||
| N818 | ErrorSuffixOnExceptionName | Exception name `...` should be named with an Error suffix | |
|
||||
|
||||
### flake8-bandit
|
||||
|
||||
For more, see [flake8-bandit](https://pypi.org/project/flake8-bandit/4.1.1/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| S101 | AssertUsed | Use of `assert` detected | |
|
||||
| S102 | ExecUsed | Use of `exec` detected | |
|
||||
| S104 | HardcodedBindAllInterfaces | Possible binding to all interfaces | |
|
||||
| S105 | HardcodedPasswordString | Possible hardcoded password: `"..."` | |
|
||||
| S106 | HardcodedPasswordFuncArg | Possible hardcoded password: `"..."` | |
|
||||
| S107 | HardcodedPasswordDefault | Possible hardcoded password: `"..."` | |
|
||||
|
||||
### flake8-comprehensions
|
||||
|
||||
For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/3.10.1/) on PyPI.
|
||||
@@ -494,23 +513,31 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| B002 | UnaryPrefixIncrement | Python does not support the unary prefix increment. | |
|
||||
| B003 | AssignmentToOsEnviron | Assigning to `os.environ` doesn't clear the environment. | |
|
||||
| B002 | UnaryPrefixIncrement | Python does not support the unary prefix increment | |
|
||||
| B003 | AssignmentToOsEnviron | Assigning to `os.environ` doesn't clear the environment | |
|
||||
| B004 | UnreliableCallableCheck | Using `hasattr(x, '__call__')` to test if x is callable is unreliable. Use `callable(x)` for consistent results. | |
|
||||
| B005 | StripWithMultiCharacters | Using `.strip()` with multi-character strings is misleading the reader. | |
|
||||
| B006 | MutableArgumentDefault | Do not use mutable data structures for argument defaults. | |
|
||||
| B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body. | 🛠 |
|
||||
| B008 | FunctionCallArgumentDefault | Do not perform function calls in argument defaults. | |
|
||||
| B009 | GetAttrWithConstant | Do not call `getattr` with a constant attribute value, it is not any safer than normal property access. | |
|
||||
| B005 | StripWithMultiCharacters | Using `.strip()` with multi-character strings is misleading the reader | |
|
||||
| B006 | MutableArgumentDefault | Do not use mutable data structures for argument defaults | |
|
||||
| B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body | 🛠 |
|
||||
| B008 | FunctionCallArgumentDefault | Do not perform function call in argument defaults | |
|
||||
| B009 | GetAttrWithConstant | Do not call `getattr` with a constant attribute value. It is not any safer than normal property access. | 🛠 |
|
||||
| B010 | SetAttrWithConstant | Do not call `setattr` with a constant attribute value. It is not any safer than normal property access. | |
|
||||
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
|
||||
| B013 | RedundantTupleInExceptionHandler | A length-one tuple literal is redundant. Write `except ValueError:` instead of `except (ValueError,):`. | |
|
||||
| B012 | JumpStatementInFinally | `return/continue/break` inside finally blocks cause exceptions to be silenced | |
|
||||
| B013 | RedundantTupleInExceptionHandler | A length-one tuple literal is redundant. Write `except ValueError` instead of `except (ValueError,)`. | |
|
||||
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | 🛠 |
|
||||
| B015 | UselessComparison | Pointless comparison. This comparison does nothing but waste CPU instructions. Either prepend `assert` or remove it. | |
|
||||
| B016 | CannotRaiseLiteral | Cannot raise a literal. Did you intend to return it or raise an Exception? | |
|
||||
| B017 | NoAssertRaisesException | `assertRaises(Exception):` should be considered evil. | |
|
||||
| B017 | NoAssertRaisesException | `assertRaises(Exception)` should be considered evil | |
|
||||
| B018 | UselessExpression | Found useless expression. Either assign it to a variable or remove it. | |
|
||||
| B019 | CachedInstanceMethod | Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks | |
|
||||
| B020 | LoopVariableOverridesIterator | Loop control variable `...` overrides iterable it iterates | |
|
||||
| B021 | FStringDocstring | f-string used as docstring. This will be interpreted by python as a joined string rather than a docstring. | |
|
||||
| B022 | UselessContextlibSuppress | No arguments passed to `contextlib.suppress`. No exceptions will be suppressed and therefore this context manager is redundant | |
|
||||
| B024 | AbstractBaseClassWithoutAbstractMethod | `...` is an abstract base class, but it has no abstract methods | |
|
||||
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` | |
|
||||
| B026 | StarArgUnpackingAfterKeywordArg | Star-arg unpacking after a keyword argument is strongly discouraged. | |
|
||||
| B026 | StarArgUnpackingAfterKeywordArg | Star-arg unpacking after a keyword argument is strongly discouraged | |
|
||||
| B027 | EmptyMethodWithoutAbstractDecorator | `...` is an empty method in an abstract base class, but has no abstract decorator | |
|
||||
|
||||
### flake8-builtins
|
||||
|
||||
@@ -560,6 +587,23 @@ For more, see [flake8-annotations](https://pypi.org/project/flake8-annotations/2
|
||||
| ANN206 | MissingReturnTypeClassMethod | Missing return type annotation for classmethod `...` | |
|
||||
| ANN401 | DynamicallyTypedExpression | Dynamically typed expressions (typing.Any) are disallowed in `...` | |
|
||||
|
||||
### flake8-2020
|
||||
|
||||
For more, see [flake8-2020](https://pypi.org/project/flake8-2020/1.7.0/) on PyPI.
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| YTT101 | SysVersionSlice3Referenced | `sys.version[:3]` referenced (python3.10), use `sys.version_info` | |
|
||||
| YTT102 | SysVersion2Referenced | `sys.version[2]` referenced (python3.10), use `sys.version_info` | |
|
||||
| YTT103 | SysVersionCmpStr3 | `sys.version` compared to string (python3.10), use `sys.version_info` | |
|
||||
| YTT201 | SysVersionInfo0Eq3Referenced | `sys.version_info[0] == 3` referenced (python4), use `>=` | |
|
||||
| YTT202 | SixPY3Referenced | `six.PY3` referenced (python4), use `not six.PY2` | |
|
||||
| YTT203 | SysVersionInfo1CmpInt | `sys.version_info[1]` compared to integer (python4), compare `sys.version_info` to tuple | |
|
||||
| YTT204 | SysVersionInfoMinorCmpInt | `sys.version_info.minor` compared to integer (python4), compare `sys.version_info` to tuple | |
|
||||
| YTT301 | SysVersion0Referenced | `sys.version[0]` referenced (python10), use `sys.version_info` | |
|
||||
| YTT302 | SysVersionCmpStr10 | `sys.version` compared to string (python10), use `sys.version_info` | |
|
||||
| YTT303 | SysVersionSlice1Referenced | `sys.version[:1]` referenced (python10), use `sys.version_info` | |
|
||||
|
||||
### Ruff-specific rules
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
@@ -665,8 +709,10 @@ including:
|
||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (19/32)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/34)
|
||||
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (25/32)
|
||||
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (15/34)
|
||||
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
|
||||
|
||||
Beyond rule-set parity, Ruff suffers from the following limitations vis-à-vis Flake8:
|
||||
@@ -687,11 +733,13 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
|
||||
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
|
||||
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (19/32)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (26/32)
|
||||
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
|
||||
|
||||
Ruff can also replace [`isort`](https://pypi.org/project/isort/), [`yesqa`](https://github.com/asottile/yesqa),
|
||||
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/34).
|
||||
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (15/34).
|
||||
|
||||
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.
|
||||
|
||||
@@ -713,8 +761,10 @@ project. See [#283](https://github.com/charliermarsh/ruff/issues/283) for more.
|
||||
|
||||
### How does Ruff's import sorting compare to [`isort`](https://pypi.org/project/isort/)?
|
||||
|
||||
Ruff's import sorting is intended to be equivalent to `isort` when used `profile = "black"` and
|
||||
`combine_as_imports = true`. Like `isort`, Ruff's import sorting is compatible with Black.
|
||||
Ruff's import sorting is intended to be nearly equivalent to `isort` when used `profile = "black"`.
|
||||
(There are some minor differences in how Ruff and isort break ties between similar imports.)
|
||||
|
||||
Like `isort`, Ruff's import sorting is compatible with Black.
|
||||
|
||||
Ruff is less configurable than `isort`, but supports the `known-first-party`, `known-third-party`,
|
||||
`extra-standard-library`, and `src` settings, like so:
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use ropey::Rope;
|
||||
use ruff::fs;
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let contents = fs::read_file(Path::new("resources/test/fixtures/D.py")).unwrap();
|
||||
let contents = fs::read_to_string(Path::new("resources/test/fixtures/D.py")).unwrap();
|
||||
c.bench_function("rope", |b| {
|
||||
b.iter(|| {
|
||||
let rope = Rope::from_str(black_box(&contents));
|
||||
|
||||
4
flake8_to_ruff/Cargo.lock
generated
4
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.110"
|
||||
version = "0.0.124"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.110"
|
||||
version = "0.0.124"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.110-dev.0"
|
||||
version = "0.0.124-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -10,6 +10,7 @@ name = "flake8_to_ruff"
|
||||
anyhow = { version = "1.0.66" }
|
||||
clap = { version = "4.0.1", features = ["derive"] }
|
||||
configparser = { version = "3.0.2" }
|
||||
fnv = { version = "1.0.7" }
|
||||
once_cell = { version = "1.16.0" }
|
||||
regex = { version = "1.6.0" }
|
||||
ruff = { path = "..", default-features = false }
|
||||
|
||||
@@ -5,7 +5,7 @@ use ruff::checks_gen::CheckCodePrefix;
|
||||
use ruff::flake8_quotes::settings::Quote;
|
||||
use ruff::settings::options::Options;
|
||||
use ruff::settings::pyproject::Pyproject;
|
||||
use ruff::{flake8_annotations, flake8_quotes, pep8_naming};
|
||||
use ruff::{flake8_annotations, flake8_bugbear, flake8_quotes, pep8_naming};
|
||||
|
||||
use crate::plugin::Plugin;
|
||||
use crate::{parser, plugin};
|
||||
@@ -69,6 +69,7 @@ pub fn convert(
|
||||
// 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 pep8_naming: pep8_naming::settings::Options = Default::default();
|
||||
for (key, value) in flake8 {
|
||||
@@ -109,6 +110,11 @@ pub fn convert(
|
||||
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
|
||||
}
|
||||
}
|
||||
// flake8-bugbear
|
||||
"extend-immutable-calls" | "extend_immutable_calls" => {
|
||||
flake8_bugbear.extend_immutable_calls =
|
||||
Some(parser::parse_strings(value.as_ref()));
|
||||
}
|
||||
// flake8-annotations
|
||||
"suppress-none-returning" | "suppress_none_returning" => {
|
||||
match parser::parse_bool(value.as_ref()) {
|
||||
@@ -179,6 +185,12 @@ 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() {
|
||||
options.flake8_annotations = Some(flake8_annotations);
|
||||
}
|
||||
if flake8_bugbear != Default::default() {
|
||||
options.flake8_bugbear = Some(flake8_bugbear);
|
||||
}
|
||||
if flake8_quotes != Default::default() {
|
||||
options.flake8_quotes = Some(flake8_quotes);
|
||||
}
|
||||
@@ -224,6 +236,7 @@ mod tests {
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_quotes: None,
|
||||
isort: None,
|
||||
pep8_naming: None,
|
||||
@@ -257,6 +270,7 @@ mod tests {
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_quotes: None,
|
||||
isort: None,
|
||||
pep8_naming: None,
|
||||
@@ -290,6 +304,7 @@ mod tests {
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_quotes: None,
|
||||
isort: None,
|
||||
pep8_naming: None,
|
||||
@@ -323,6 +338,7 @@ mod tests {
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_quotes: None,
|
||||
isort: None,
|
||||
pep8_naming: None,
|
||||
@@ -356,6 +372,7 @@ mod tests {
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_quotes: Some(flake8_quotes::settings::Options {
|
||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||
multiline_quotes: None,
|
||||
@@ -432,6 +449,7 @@ mod tests {
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_quotes: None,
|
||||
isort: None,
|
||||
pep8_naming: None,
|
||||
@@ -466,6 +484,7 @@ mod tests {
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
flake8_annotations: None,
|
||||
flake8_bugbear: None,
|
||||
flake8_quotes: Some(flake8_quotes::settings::Options {
|
||||
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
|
||||
multiline_quotes: None,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use fnv::FnvHashMap;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
@@ -179,8 +179,8 @@ pub fn parse_files_to_codes_mapping(value: &str) -> Result<Vec<PatternPrefixPair
|
||||
/// Collect a list of `PatternPrefixPair` structs as a `BTreeMap`.
|
||||
pub fn collect_per_file_ignores(
|
||||
pairs: Vec<PatternPrefixPair>,
|
||||
) -> BTreeMap<String, Vec<CheckCodePrefix>> {
|
||||
let mut per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>> = BTreeMap::new();
|
||||
) -> FnvHashMap<String, Vec<CheckCodePrefix>> {
|
||||
let mut per_file_ignores: FnvHashMap<String, Vec<CheckCodePrefix>> = FnvHashMap::default();
|
||||
for pair in pairs {
|
||||
per_file_ignores
|
||||
.entry(pair.pattern)
|
||||
|
||||
@@ -6,6 +6,7 @@ use ruff::checks_gen::CheckCodePrefix;
|
||||
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub enum Plugin {
|
||||
Flake8Bandit,
|
||||
Flake8Bugbear,
|
||||
Flake8Builtins,
|
||||
Flake8Comprehensions,
|
||||
@@ -22,6 +23,7 @@ impl FromStr for Plugin {
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
match string {
|
||||
"flake8-bandit" => Ok(Plugin::Flake8Bandit),
|
||||
"flake8-bugbear" => Ok(Plugin::Flake8Bugbear),
|
||||
"flake8-builtins" => Ok(Plugin::Flake8Builtins),
|
||||
"flake8-comprehensions" => Ok(Plugin::Flake8Comprehensions),
|
||||
@@ -39,6 +41,7 @@ impl FromStr for Plugin {
|
||||
impl Plugin {
|
||||
pub fn default(&self) -> CheckCodePrefix {
|
||||
match self {
|
||||
Plugin::Flake8Bandit => CheckCodePrefix::S,
|
||||
Plugin::Flake8Bugbear => CheckCodePrefix::B,
|
||||
Plugin::Flake8Builtins => CheckCodePrefix::A,
|
||||
Plugin::Flake8Comprehensions => CheckCodePrefix::C,
|
||||
@@ -53,6 +56,7 @@ impl Plugin {
|
||||
|
||||
pub fn select(&self, flake8: &HashMap<String, Option<String>>) -> Vec<CheckCodePrefix> {
|
||||
match self {
|
||||
Plugin::Flake8Bandit => vec![CheckCodePrefix::S],
|
||||
Plugin::Flake8Bugbear => vec![CheckCodePrefix::B],
|
||||
Plugin::Flake8Builtins => vec![CheckCodePrefix::A],
|
||||
Plugin::Flake8Comprehensions => vec![CheckCodePrefix::C],
|
||||
@@ -265,6 +269,10 @@ pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> V
|
||||
"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);
|
||||
@@ -329,6 +337,7 @@ 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::Flake8Bandit,
|
||||
Plugin::Flake8Bugbear,
|
||||
Plugin::Flake8Builtins,
|
||||
Plugin::Flake8Comprehensions,
|
||||
|
||||
20
resources/test/fixtures/B008_extended.py
vendored
Normal file
20
resources/test/fixtures/B008_extended.py
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
from typing import List
|
||||
|
||||
import fastapi
|
||||
from fastapi import Query
|
||||
|
||||
|
||||
def okay(db=fastapi.Depends(get_db)):
|
||||
...
|
||||
|
||||
|
||||
def okay(data: List[str] = fastapi.Query(None)):
|
||||
...
|
||||
|
||||
|
||||
def okay(data: List[str] = Query(None)):
|
||||
...
|
||||
|
||||
|
||||
def error_due_to_missing_import(data: List[str] = Depends(None)):
|
||||
...
|
||||
27
resources/test/fixtures/B009_B010.py
vendored
27
resources/test/fixtures/B009_B010.py
vendored
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Should emit:
|
||||
B009 - Line 17, 18, 19, 44
|
||||
B010 - Line 28, 29, 30
|
||||
B009 - Line 18, 19, 20, 21, 22
|
||||
B010 - Line 33, 34, 35, 36
|
||||
"""
|
||||
|
||||
# Valid getattr usage
|
||||
@@ -11,37 +11,26 @@ getattr(foo, "bar{foo}".format(foo="a"), None)
|
||||
getattr(foo, "bar{foo}".format(foo="a"))
|
||||
getattr(foo, bar, None)
|
||||
getattr(foo, "123abc")
|
||||
getattr(foo, r"123\abc")
|
||||
getattr(foo, "except")
|
||||
|
||||
# Invalid usage
|
||||
getattr(foo, "bar")
|
||||
getattr(foo, "_123abc")
|
||||
getattr(foo, "abc123")
|
||||
getattr(foo, r"abc123")
|
||||
_ = lambda x: getattr(x, "bar")
|
||||
|
||||
# Valid setattr usage
|
||||
setattr(foo, bar, None)
|
||||
setattr(foo, "bar{foo}".format(foo="a"), None)
|
||||
setattr(foo, "123abc", None)
|
||||
setattr(foo, r"123\abc", None)
|
||||
setattr(foo, "except", None)
|
||||
_ = lambda x: setattr(x, "bar", 1)
|
||||
|
||||
# Invalid usage
|
||||
setattr(foo, "bar", None)
|
||||
setattr(foo, "_123abc", None)
|
||||
setattr(foo, "abc123", None)
|
||||
|
||||
# Allow use of setattr within lambda expression
|
||||
# since assignment is not valid in this context.
|
||||
c = lambda x: setattr(x, "some_attr", 1)
|
||||
|
||||
|
||||
class FakeCookieStore:
|
||||
def __init__(self, has_setter):
|
||||
self.cookie_filter = None
|
||||
if has_setter:
|
||||
self.setCookieFilter = lambda func: setattr(self, "cookie_filter", func)
|
||||
|
||||
|
||||
# getattr is still flagged within lambda though
|
||||
c = lambda x: getattr(x, "some_attr")
|
||||
# should be replaced with
|
||||
c = lambda x: x.some_attr
|
||||
setattr(foo, r"abc123", None)
|
||||
|
||||
107
resources/test/fixtures/B012.py
vendored
Normal file
107
resources/test/fixtures/B012.py
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
def a():
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
return # warning
|
||||
|
||||
|
||||
def b():
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
if 1 + 0 == 2 - 1:
|
||||
return # warning
|
||||
|
||||
|
||||
def c():
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
return # warning
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def d():
|
||||
try:
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
return # warning
|
||||
finally:
|
||||
pass
|
||||
|
||||
|
||||
def e():
|
||||
if 1 == 2 - 1:
|
||||
try:
|
||||
|
||||
def f():
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
return # warning
|
||||
|
||||
finally:
|
||||
pass
|
||||
|
||||
|
||||
def g():
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
|
||||
def h():
|
||||
return # no warning
|
||||
|
||||
e()
|
||||
|
||||
|
||||
def i():
|
||||
while True:
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
break # warning
|
||||
|
||||
def j():
|
||||
while True:
|
||||
break # no warning
|
||||
|
||||
|
||||
def h():
|
||||
while True:
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
continue # warning
|
||||
|
||||
def j():
|
||||
while True:
|
||||
continue # no warning
|
||||
|
||||
|
||||
def k():
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
while True:
|
||||
break # no warning
|
||||
while True:
|
||||
continue # no warning
|
||||
while True:
|
||||
return # warning
|
||||
|
||||
|
||||
while True:
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
continue # warning
|
||||
|
||||
while True:
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
break # warning
|
||||
108
resources/test/fixtures/B019.py
vendored
Normal file
108
resources/test/fixtures/B019.py
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
Should emit:
|
||||
B019 - on lines 73, 77, 81, 85, 89, 93, 97, 101
|
||||
"""
|
||||
import functools
|
||||
from functools import cache, cached_property, lru_cache
|
||||
|
||||
|
||||
def some_other_cache():
|
||||
...
|
||||
|
||||
|
||||
@functools.cache
|
||||
def compute_func(self, y):
|
||||
...
|
||||
|
||||
|
||||
class Foo:
|
||||
def __init__(self, x):
|
||||
self.x = x
|
||||
|
||||
def compute_method(self, y):
|
||||
...
|
||||
|
||||
@some_other_cache
|
||||
def user_cached_instance_method(self, y):
|
||||
...
|
||||
|
||||
@classmethod
|
||||
@functools.cache
|
||||
def cached_classmethod(cls, y):
|
||||
...
|
||||
|
||||
@classmethod
|
||||
@cache
|
||||
def other_cached_classmethod(cls, y):
|
||||
...
|
||||
|
||||
@classmethod
|
||||
@functools.lru_cache
|
||||
def lru_cached_classmethod(cls, y):
|
||||
...
|
||||
|
||||
@classmethod
|
||||
@lru_cache
|
||||
def other_lru_cached_classmethod(cls, y):
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
@functools.cache
|
||||
def cached_staticmethod(y):
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
@cache
|
||||
def other_cached_staticmethod(y):
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
@functools.lru_cache
|
||||
def lru_cached_staticmethod(y):
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
@lru_cache
|
||||
def other_lru_cached_staticmethod(y):
|
||||
...
|
||||
|
||||
@functools.cached_property
|
||||
def some_cached_property(self):
|
||||
...
|
||||
|
||||
@cached_property
|
||||
def some_other_cached_property(self):
|
||||
...
|
||||
|
||||
# Remaining methods should emit B019
|
||||
@functools.cache
|
||||
def cached_instance_method(self, y):
|
||||
...
|
||||
|
||||
@cache
|
||||
def another_cached_instance_method(self, y):
|
||||
...
|
||||
|
||||
@functools.cache()
|
||||
def called_cached_instance_method(self, y):
|
||||
...
|
||||
|
||||
@cache()
|
||||
def another_called_cached_instance_method(self, y):
|
||||
...
|
||||
|
||||
@functools.lru_cache
|
||||
def lru_cached_instance_method(self, y):
|
||||
...
|
||||
|
||||
@lru_cache
|
||||
def another_lru_cached_instance_method(self, y):
|
||||
...
|
||||
|
||||
@functools.lru_cache()
|
||||
def called_lru_cached_instance_method(self, y):
|
||||
...
|
||||
|
||||
@lru_cache()
|
||||
def another_called_lru_cached_instance_method(self, y):
|
||||
...
|
||||
40
resources/test/fixtures/B020.py
vendored
Normal file
40
resources/test/fixtures/B020.py
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
"""
|
||||
Should emit:
|
||||
B020 - on lines 8, 21, and 36
|
||||
"""
|
||||
|
||||
items = [1, 2, 3]
|
||||
|
||||
for items in items:
|
||||
print(items)
|
||||
|
||||
items = [1, 2, 3]
|
||||
|
||||
for item in items:
|
||||
print(item)
|
||||
|
||||
values = {"secret": 123}
|
||||
|
||||
for key, value in values.items():
|
||||
print(f"{key}, {value}")
|
||||
|
||||
for key, values in values.items():
|
||||
print(f"{key}, {values}")
|
||||
|
||||
# Variables defined in a comprehension are local in scope
|
||||
# to that comprehension and are therefore allowed.
|
||||
for var in [var for var in range(10)]:
|
||||
print(var)
|
||||
|
||||
for var in (var for var in range(10)):
|
||||
print(var)
|
||||
|
||||
for k, v in {k: v for k, v in zip(range(10), range(10, 20))}.items():
|
||||
print(k, v)
|
||||
|
||||
# However we still call out reassigning the iterable in the comprehension.
|
||||
for vars in [i for i in vars]:
|
||||
print(vars)
|
||||
|
||||
for var in sorted(range(10), key=lambda var: var.real):
|
||||
print(var)
|
||||
76
resources/test/fixtures/B021.py
vendored
Normal file
76
resources/test/fixtures/B021.py
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
f"""
|
||||
Should emit:
|
||||
B021 - on lines 14, 22, 30, 38, 46, 54, 62, 70, 73
|
||||
"""
|
||||
|
||||
VARIABLE = "world"
|
||||
|
||||
|
||||
def foo1():
|
||||
"""hello world!"""
|
||||
|
||||
|
||||
def foo2():
|
||||
f"""hello {VARIABLE}!"""
|
||||
|
||||
|
||||
class bar1:
|
||||
"""hello world!"""
|
||||
|
||||
|
||||
class bar2:
|
||||
f"""hello {VARIABLE}!"""
|
||||
|
||||
|
||||
def foo1():
|
||||
"""hello world!"""
|
||||
|
||||
|
||||
def foo2():
|
||||
f"""hello {VARIABLE}!"""
|
||||
|
||||
|
||||
class bar1:
|
||||
"""hello world!"""
|
||||
|
||||
|
||||
class bar2:
|
||||
f"""hello {VARIABLE}!"""
|
||||
|
||||
|
||||
def foo1():
|
||||
"hello world!"
|
||||
|
||||
|
||||
def foo2():
|
||||
f"hello {VARIABLE}!"
|
||||
|
||||
|
||||
class bar1:
|
||||
"hello world!"
|
||||
|
||||
|
||||
class bar2:
|
||||
f"hello {VARIABLE}!"
|
||||
|
||||
|
||||
def foo1():
|
||||
"hello world!"
|
||||
|
||||
|
||||
def foo2():
|
||||
f"hello {VARIABLE}!"
|
||||
|
||||
|
||||
class bar1:
|
||||
"hello world!"
|
||||
|
||||
|
||||
class bar2:
|
||||
f"hello {VARIABLE}!"
|
||||
|
||||
|
||||
def baz():
|
||||
f"""I'm probably a docstring: {VARIABLE}!"""
|
||||
print(f"""I'm a normal string""")
|
||||
f"""Don't detect me!"""
|
||||
23
resources/test/fixtures/B022.py
vendored
Normal file
23
resources/test/fixtures/B022.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Should emit:
|
||||
B022 - on lines 8
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
from contextlib import suppress
|
||||
|
||||
with contextlib.suppress():
|
||||
raise ValueError
|
||||
|
||||
with suppress():
|
||||
raise ValueError
|
||||
|
||||
with contextlib.suppress(ValueError):
|
||||
raise ValueError
|
||||
|
||||
exceptions_to_suppress = []
|
||||
if True:
|
||||
exceptions_to_suppress.append(ValueError)
|
||||
|
||||
with contextlib.suppress(*exceptions_to_suppress):
|
||||
raise
|
||||
129
resources/test/fixtures/B024.py
vendored
Normal file
129
resources/test/fixtures/B024.py
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
Should emit:
|
||||
B024 - on lines 17, 34, 52, 58, 69, 74, 79, 84, 89
|
||||
"""
|
||||
|
||||
import abc
|
||||
import abc as notabc
|
||||
from abc import ABC, ABCMeta
|
||||
from abc import abstractmethod
|
||||
from abc import abstractmethod as abstract
|
||||
from abc import abstractmethod as abstractaoeuaoeuaoeu
|
||||
from abc import abstractmethod as notabstract
|
||||
|
||||
import foo
|
||||
|
||||
|
||||
class Base_1(ABC): # error
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class Base_2(ABC):
|
||||
@abstractmethod
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class Base_3(ABC):
|
||||
@abc.abstractmethod
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class Base_4(ABC):
|
||||
@notabc.abstractmethod
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class Base_5(ABC):
|
||||
@abstract
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class Base_6(ABC):
|
||||
@abstractaoeuaoeuaoeu
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class Base_7(ABC): # error
|
||||
@notabstract
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class MetaBase_1(metaclass=ABCMeta): # error
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class MetaBase_2(metaclass=ABCMeta):
|
||||
@abstractmethod
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class abc_Base_1(abc.ABC): # error
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class abc_Base_2(metaclass=abc.ABCMeta): # error
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class notabc_Base_1(notabc.ABC): # error
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class multi_super_1(notabc.ABC, abc.ABCMeta): # safe
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class multi_super_2(notabc.ABC, metaclass=abc.ABCMeta): # safe
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class non_keyword_abcmeta_1(ABCMeta): # safe
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class non_keyword_abcmeta_2(abc.ABCMeta): # safe
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
# very invalid code, but that's up to mypy et al to check
|
||||
class keyword_abc_1(metaclass=ABC): # safe
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class keyword_abc_2(metaclass=abc.ABC): # safe
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class abc_set_class_variable_1(ABC): # safe
|
||||
foo: int
|
||||
|
||||
|
||||
class abc_set_class_variable_2(ABC): # safe
|
||||
foo = 2
|
||||
|
||||
|
||||
class abc_set_class_variable_3(ABC): # safe
|
||||
foo: int = 2
|
||||
|
||||
|
||||
# this doesn't actually declare a class variable, it's just an expression
|
||||
class abc_set_class_variable_4(ABC): # error
|
||||
foo
|
||||
88
resources/test/fixtures/B027.py
vendored
Normal file
88
resources/test/fixtures/B027.py
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
"""
|
||||
Should emit:
|
||||
B027 - on lines 12, 15, 18, 22, 30
|
||||
"""
|
||||
import abc
|
||||
from abc import ABC
|
||||
from abc import abstractmethod
|
||||
from abc import abstractmethod as notabstract
|
||||
|
||||
|
||||
class AbstractClass(ABC):
|
||||
def empty_1(self): # error
|
||||
...
|
||||
|
||||
def empty_2(self): # error
|
||||
pass
|
||||
|
||||
def empty_3(self): # error
|
||||
"""docstring"""
|
||||
...
|
||||
|
||||
def empty_4(self): # error
|
||||
"""multiple ellipsis/pass"""
|
||||
...
|
||||
pass
|
||||
...
|
||||
pass
|
||||
|
||||
@notabstract
|
||||
def abstract_0(self):
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def abstract_1(self):
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def abstract_2(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def abstract_3(self):
|
||||
...
|
||||
|
||||
def body_1(self):
|
||||
print("foo")
|
||||
...
|
||||
|
||||
def body_2(self):
|
||||
self.body_1()
|
||||
|
||||
|
||||
class NonAbstractClass:
|
||||
def empty_1(self): # safe
|
||||
...
|
||||
|
||||
def empty_2(self): # safe
|
||||
pass
|
||||
|
||||
|
||||
# ignore @overload, fixes issue #304
|
||||
# ignore overload with other imports, fixes #308
|
||||
import typing
|
||||
import typing as t
|
||||
import typing as anything
|
||||
from typing import Union, overload
|
||||
|
||||
|
||||
class AbstractClass(ABC):
|
||||
@overload
|
||||
def empty_1(self, foo: str):
|
||||
...
|
||||
|
||||
@typing.overload
|
||||
def empty_1(self, foo: int):
|
||||
...
|
||||
|
||||
@t.overload
|
||||
def empty_1(self, foo: list):
|
||||
...
|
||||
|
||||
@anything.overload
|
||||
def empty_1(self, foo: float):
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def empty_1(self, foo: Union[str, int, list, float]):
|
||||
...
|
||||
2
resources/test/fixtures/E501.py
vendored
2
resources/test/fixtures/E501.py
vendored
@@ -5,7 +5,7 @@
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
"""
|
||||
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
_ = """Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
|
||||
https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
|
||||
|
||||
4
resources/test/fixtures/E731.py
vendored
4
resources/test/fixtures/E731.py
vendored
@@ -8,14 +8,14 @@ while False:
|
||||
|
||||
|
||||
f = object()
|
||||
#: E731
|
||||
f.method = lambda: "Method"
|
||||
|
||||
f = {}
|
||||
#: E731
|
||||
f["a"] = lambda x: x ** 2
|
||||
|
||||
f = []
|
||||
f.append(lambda x: x ** 2)
|
||||
|
||||
f = g = lambda x: x ** 2
|
||||
|
||||
lambda: "no-op"
|
||||
|
||||
1
resources/test/fixtures/F401_0.py
vendored
1
resources/test/fixtures/F401_0.py
vendored
@@ -28,7 +28,6 @@ from blah import ClassA, ClassB, ClassC
|
||||
if TYPE_CHECKING:
|
||||
from models import Fruit, Nut, Vegetable
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import shelve
|
||||
import importlib
|
||||
|
||||
20
resources/test/fixtures/F401_6.py
vendored
Normal file
20
resources/test/fixtures/F401_6.py
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
"""Test: explicit re-export."""
|
||||
|
||||
# OK
|
||||
from .applications import FastAPI as FastAPI
|
||||
|
||||
# F401 `background.BackgroundTasks` imported but unused
|
||||
from .background import BackgroundTasks
|
||||
|
||||
# F401 `datastructures.UploadFile` imported but unused
|
||||
from .datastructures import UploadFile as FileUpload
|
||||
|
||||
|
||||
# OK
|
||||
import applications as applications
|
||||
|
||||
# F401 `background` imported but unused
|
||||
import background
|
||||
|
||||
# F401 `datastructures` imported but unused
|
||||
import datastructures as structures
|
||||
24
resources/test/fixtures/F821_4.py
vendored
Normal file
24
resources/test/fixtures/F821_4.py
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
"""Test: import alias tracking."""
|
||||
from typing import List
|
||||
|
||||
_ = List["Model"]
|
||||
|
||||
|
||||
from typing import List as IList
|
||||
|
||||
_ = IList["Model"]
|
||||
|
||||
|
||||
from collections.abc import ItemsView
|
||||
|
||||
_ = ItemsView["Model"]
|
||||
|
||||
|
||||
import collections.abc
|
||||
|
||||
_ = collections.abc.ItemsView["Model"]
|
||||
|
||||
|
||||
from collections import abc
|
||||
|
||||
_ = abc.ItemsView["Model"]
|
||||
14
resources/test/fixtures/F821_5.py
vendored
Normal file
14
resources/test/fixtures/F821_5.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Test: inner class annotation."""
|
||||
|
||||
|
||||
class RandomClass:
|
||||
def random_func(self) -> "InnerClass":
|
||||
pass
|
||||
|
||||
|
||||
class OuterClass:
|
||||
class InnerClass:
|
||||
pass
|
||||
|
||||
def failing_func(self) -> "InnerClass":
|
||||
return self.InnerClass()
|
||||
10
resources/test/fixtures/N805.py
vendored
10
resources/test/fixtures/N805.py
vendored
@@ -1,5 +1,7 @@
|
||||
from abc import ABCMeta
|
||||
|
||||
import pydantic
|
||||
|
||||
|
||||
class Class:
|
||||
def bad_method(this):
|
||||
@@ -21,6 +23,14 @@ class Class:
|
||||
def static_method(x):
|
||||
return x
|
||||
|
||||
@pydantic.validator
|
||||
def lower(cls, my_field: str) -> str:
|
||||
pass
|
||||
|
||||
@pydantic.validator("my_field")
|
||||
def lower(cls, my_field: str) -> str:
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
...
|
||||
|
||||
|
||||
6
resources/test/fixtures/N806.py
vendored
6
resources/test/fixtures/N806.py
vendored
@@ -1,5 +1,11 @@
|
||||
import collections
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
def f():
|
||||
lower = 0
|
||||
Camel = 0
|
||||
CONSTANT = 0
|
||||
_ = 0
|
||||
MyObj1 = collections.namedtuple("MyObj1", ["a", "b"])
|
||||
MyObj2 = namedtuple("MyObj12", ["a", "b"])
|
||||
|
||||
6
resources/test/fixtures/N815.py
vendored
6
resources/test/fixtures/N815.py
vendored
@@ -1,6 +1,12 @@
|
||||
import collections
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
class C:
|
||||
lower = 0
|
||||
CONSTANT = 0
|
||||
mixedCase = 0
|
||||
_mixedCase = 0
|
||||
mixed_Case = 0
|
||||
myObj1 = collections.namedtuple("MyObj1", ["a", "b"])
|
||||
myObj2 = namedtuple("MyObj2", ["a", "b"])
|
||||
|
||||
5
resources/test/fixtures/N816.py
vendored
5
resources/test/fixtures/N816.py
vendored
@@ -1,5 +1,10 @@
|
||||
import collections
|
||||
from collections import namedtuple
|
||||
|
||||
lower = 0
|
||||
CONSTANT = 0
|
||||
mixedCase = 0
|
||||
_mixedCase = 0
|
||||
mixed_Case = 0
|
||||
myObj1 = collections.namedtuple("MyObj1", ["a", "b"])
|
||||
myObj2 = namedtuple("MyObj2", ["a", "b"])
|
||||
|
||||
11
resources/test/fixtures/S101.py
vendored
Normal file
11
resources/test/fixtures/S101.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# Error
|
||||
assert True
|
||||
|
||||
def fn():
|
||||
x = 1
|
||||
|
||||
# Error
|
||||
assert x == 1
|
||||
|
||||
# Error
|
||||
assert x == 2
|
||||
5
resources/test/fixtures/S102.py
vendored
Normal file
5
resources/test/fixtures/S102.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
def fn():
|
||||
# Error
|
||||
exec('x = 2')
|
||||
|
||||
exec('y = 3')
|
||||
19
resources/test/fixtures/S104.py
vendored
Normal file
19
resources/test/fixtures/S104.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
def func(address):
|
||||
print(address)
|
||||
|
||||
|
||||
# OK
|
||||
"OK"
|
||||
|
||||
# Error
|
||||
"0.0.0.0"
|
||||
'0.0.0.0'
|
||||
|
||||
|
||||
# Error
|
||||
func("0.0.0.0")
|
||||
|
||||
|
||||
def my_func():
|
||||
x = "0.0.0.0"
|
||||
print(x)
|
||||
53
resources/test/fixtures/S105.py
vendored
Normal file
53
resources/test/fixtures/S105.py
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
d = {}
|
||||
|
||||
# OK
|
||||
safe = "s3cr3t"
|
||||
password = True
|
||||
password = safe
|
||||
password is True
|
||||
password == 1
|
||||
d["safe"] = "s3cr3t"
|
||||
|
||||
# Errors
|
||||
password = "s3cr3t"
|
||||
_pass = "s3cr3t"
|
||||
passwd = "s3cr3t"
|
||||
pwd = "s3cr3t"
|
||||
secret = "s3cr3t"
|
||||
token = "s3cr3t"
|
||||
secrete = "s3cr3t"
|
||||
safe = password = "s3cr3t"
|
||||
password = safe = "s3cr3t"
|
||||
|
||||
d["password"] = "s3cr3t"
|
||||
d["pass"] = "s3cr3t"
|
||||
d["passwd"] = "s3cr3t"
|
||||
d["pwd"] = "s3cr3t"
|
||||
d["secret"] = "s3cr3t"
|
||||
d["token"] = "s3cr3t"
|
||||
d["secrete"] = "s3cr3t"
|
||||
safe = d["password"] = "s3cr3t"
|
||||
d["password"] = safe = "s3cr3t"
|
||||
|
||||
|
||||
class MyClass:
|
||||
password = "s3cr3t"
|
||||
safe = password
|
||||
|
||||
|
||||
MyClass.password = "s3cr3t"
|
||||
MyClass._pass = "s3cr3t"
|
||||
MyClass.passwd = "s3cr3t"
|
||||
MyClass.pwd = "s3cr3t"
|
||||
MyClass.secret = "s3cr3t"
|
||||
MyClass.token = "s3cr3t"
|
||||
MyClass.secrete = "s3cr3t"
|
||||
|
||||
password == "s3cr3t"
|
||||
_pass == "s3cr3t"
|
||||
passwd == "s3cr3t"
|
||||
pwd == "s3cr3t"
|
||||
secret == "s3cr3t"
|
||||
token == "s3cr3t"
|
||||
secrete == "s3cr3t"
|
||||
password == safe == "s3cr3t"
|
||||
13
resources/test/fixtures/S106.py
vendored
Normal file
13
resources/test/fixtures/S106.py
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
def func(pos, password):
|
||||
pass
|
||||
|
||||
|
||||
string = "Hello World"
|
||||
|
||||
# OK
|
||||
func("s3cr3t")
|
||||
func(1, password=string)
|
||||
func(pos="s3cr3t", password=string)
|
||||
|
||||
# Error
|
||||
func(1, password="s3cr3t")
|
||||
30
resources/test/fixtures/S107.py
vendored
Normal file
30
resources/test/fixtures/S107.py
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
def ok(first, default="default"):
|
||||
pass
|
||||
|
||||
|
||||
def default(first, password="default"):
|
||||
pass
|
||||
|
||||
|
||||
def ok_posonly(first, /, pos, default="posonly"):
|
||||
pass
|
||||
|
||||
|
||||
def default_posonly(first, /, pos, password="posonly"):
|
||||
pass
|
||||
|
||||
|
||||
def ok_kwonly(first, *, default="kwonly"):
|
||||
pass
|
||||
|
||||
|
||||
def default_kwonly(first, *, password="kwonly"):
|
||||
pass
|
||||
|
||||
|
||||
def ok_all(first, /, pos, default="posonly", *, kwonly="kwonly"):
|
||||
pass
|
||||
|
||||
|
||||
def default_all(first, /, pos, secret="posonly", *, password="kwonly"):
|
||||
pass
|
||||
18
resources/test/fixtures/U006.py
vendored
18
resources/test/fixtures/U006.py
vendored
@@ -1,3 +1,10 @@
|
||||
import typing
|
||||
|
||||
|
||||
def f(x: typing.List[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
from typing import List
|
||||
|
||||
|
||||
@@ -5,8 +12,15 @@ def f(x: List[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
import typing
|
||||
import typing as t
|
||||
|
||||
|
||||
def f(x: typing.List[str]) -> None:
|
||||
def f(x: t.List[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
from typing import List as IList
|
||||
|
||||
|
||||
def f(x: IList[str]) -> None:
|
||||
...
|
||||
|
||||
15
resources/test/fixtures/U010.py
vendored
15
resources/test/fixtures/U010.py
vendored
@@ -1,5 +1,14 @@
|
||||
from __future__ import annotations, nested_scopes, generators
|
||||
|
||||
from __future__ import nested_scopes, generators
|
||||
from __future__ import with_statement, unicode_literals
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
from __future__ import generator_stop
|
||||
from __future__ import print_function, generator_stop
|
||||
from __future__ import invalid_module, generators
|
||||
|
||||
if True:
|
||||
from __future__ import generator_stop
|
||||
from __future__ import generators
|
||||
|
||||
if True:
|
||||
from __future__ import generator_stop
|
||||
from __future__ import invalid_module, generators
|
||||
|
||||
52
resources/test/fixtures/U012.py
vendored
Normal file
52
resources/test/fixtures/U012.py
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
# ASCII literals should be replaced by a bytes literal
|
||||
"foo".encode("utf-8") # b"foo"
|
||||
"foo".encode("u8") # b"foo"
|
||||
"foo".encode() # b"foo"
|
||||
"foo".encode("UTF8") # b"foo"
|
||||
U"foo".encode("utf-8") # b"foo"
|
||||
"foo".encode(encoding="utf-8") # b"foo"
|
||||
"""
|
||||
Lorem
|
||||
|
||||
Ipsum
|
||||
""".encode(
|
||||
"utf-8"
|
||||
)
|
||||
# b"""
|
||||
# Lorem
|
||||
#
|
||||
# Ipsum
|
||||
# """
|
||||
|
||||
# `encode` on variables should not be processed.
|
||||
string = "hello there"
|
||||
string.encode("utf-8")
|
||||
|
||||
bar = "bar"
|
||||
f"foo{bar}".encode("utf-8") # f"foo{bar}".encode()
|
||||
encoding = "latin"
|
||||
"foo".encode(encoding)
|
||||
f"foo{bar}".encode(encoding)
|
||||
|
||||
# `encode` with custom args and kwargs should not be processed.
|
||||
"foo".encode("utf-8", errors="replace")
|
||||
"foo".encode("utf-8", "replace")
|
||||
"foo".encode(errors="replace")
|
||||
"foo".encode(encoding="utf-8", errors="replace")
|
||||
|
||||
# `encode` with custom args and kwargs on unicode should not be processed.
|
||||
"unicode text©".encode("utf-8", errors="replace")
|
||||
"unicode text©".encode("utf-8", "replace")
|
||||
"unicode text©".encode(errors="replace")
|
||||
"unicode text©".encode(encoding="utf-8", errors="replace")
|
||||
|
||||
# Unicode literals should only be stripped of default encoding.
|
||||
"unicode text©".encode("utf-8") # "unicode text©".encode()
|
||||
"unicode text©".encode()
|
||||
"unicode text©".encode(encoding="UTF8") # "unicode text©".encode()
|
||||
|
||||
r"fo\o".encode("utf-8") # br"fo\o"
|
||||
u"foo".encode("utf-8") # b"foo"
|
||||
R"fo\o".encode("utf-8") # br"fo\o"
|
||||
U"foo".encode("utf-8") # b"foo"
|
||||
print("foo".encode()) # print(b"foo")
|
||||
12
resources/test/fixtures/YTT101.py
vendored
Normal file
12
resources/test/fixtures/YTT101.py
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import sys
|
||||
from sys import version, version as v
|
||||
|
||||
print(sys.version)
|
||||
|
||||
print(sys.version[:3])
|
||||
print(version[:3])
|
||||
print(v[:3])
|
||||
|
||||
# the tool is timid and only flags certain numeric slices
|
||||
i = 3
|
||||
print(sys.version[:i])
|
||||
5
resources/test/fixtures/YTT102.py
vendored
Normal file
5
resources/test/fixtures/YTT102.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import sys
|
||||
from sys import version
|
||||
|
||||
py_minor = sys.version[2]
|
||||
py_minor = version[2]
|
||||
8
resources/test/fixtures/YTT103.py
vendored
Normal file
8
resources/test/fixtures/YTT103.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import sys
|
||||
from sys import version
|
||||
|
||||
version < "3.5"
|
||||
sys.version < "3.5"
|
||||
sys.version <= "3.5"
|
||||
sys.version > "3.5"
|
||||
sys.version >= "3.5"
|
||||
10
resources/test/fixtures/YTT201.py
vendored
Normal file
10
resources/test/fixtures/YTT201.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import sys
|
||||
from sys import version_info
|
||||
|
||||
print("{}.{}".format(*sys.version_info))
|
||||
PY3 = sys.version_info[0] >= 3
|
||||
|
||||
PY3 = sys.version_info[0] == 3
|
||||
PY3 = version_info[0] == 3
|
||||
PY2 = sys.version_info[0] != 3
|
||||
PY2 = version_info[0] != 3
|
||||
7
resources/test/fixtures/YTT202.py
vendored
Normal file
7
resources/test/fixtures/YTT202.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import six
|
||||
from six import PY3
|
||||
|
||||
if six.PY3:
|
||||
print("3")
|
||||
if PY3:
|
||||
print("3")
|
||||
5
resources/test/fixtures/YTT203.py
vendored
Normal file
5
resources/test/fixtures/YTT203.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import sys
|
||||
from sys import version_info
|
||||
|
||||
sys.version_info[1] >= 5
|
||||
version_info[1] < 6
|
||||
5
resources/test/fixtures/YTT204.py
vendored
Normal file
5
resources/test/fixtures/YTT204.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import sys
|
||||
from sys import version_info
|
||||
|
||||
sys.version_info.minor <= 7
|
||||
version_info.minor > 8
|
||||
5
resources/test/fixtures/YTT301.py
vendored
Normal file
5
resources/test/fixtures/YTT301.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import sys
|
||||
from sys import version
|
||||
|
||||
py_major = sys.version[0]
|
||||
py_major = version[0]
|
||||
8
resources/test/fixtures/YTT302.py
vendored
Normal file
8
resources/test/fixtures/YTT302.py
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import sys
|
||||
from sys import version
|
||||
|
||||
version < "3"
|
||||
sys.version < "3"
|
||||
sys.version <= "3"
|
||||
sys.version > "3"
|
||||
sys.version >= "3"
|
||||
5
resources/test/fixtures/YTT303.py
vendored
Normal file
5
resources/test/fixtures/YTT303.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import sys
|
||||
from sys import version
|
||||
|
||||
print(sys.version[:1])
|
||||
print(version[:1])
|
||||
7
resources/test/fixtures/isort/add_newline_before_comments.py
vendored
Normal file
7
resources/test/fixtures/isort/add_newline_before_comments.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import os
|
||||
# This is a comment in the same section, so we need to add one newline.
|
||||
import sys
|
||||
import numpy as np
|
||||
# This is a comment, but it starts a new section, so we don't need to add a newline
|
||||
# before it.
|
||||
import leading_prefix
|
||||
25
resources/test/fixtures/isort/comments.py
vendored
Normal file
25
resources/test/fixtures/isort/comments.py
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Comment 1
|
||||
# Comment 2
|
||||
import D
|
||||
|
||||
# Comment 3a
|
||||
import C
|
||||
|
||||
# Comment 3b
|
||||
import C
|
||||
|
||||
import B # Comment 4
|
||||
|
||||
# Comment 5
|
||||
|
||||
# Comment 6
|
||||
from A import (
|
||||
a, # Comment 7
|
||||
b,
|
||||
c, # Comment 8
|
||||
)
|
||||
from A import (
|
||||
a, # Comment 9
|
||||
b, # Comment 10
|
||||
c, # Comment 11
|
||||
)
|
||||
15
resources/test/fixtures/isort/fit_line_length.py
vendored
15
resources/test/fixtures/isort/fit_line_length.py
vendored
@@ -1 +1,14 @@
|
||||
from collections import Collection
|
||||
from line_with_88 import aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
from line_with_89 import (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||
)
|
||||
|
||||
if indented:
|
||||
from line_with_88 import aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
from line_with_89 import aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
from line_with_90 import aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
from line_with_91 import aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
from line_with_92 import aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
from line_with_93 import (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
|
||||
)
|
||||
|
||||
4
resources/test/fixtures/isort/fit_line_length_comment.py
vendored
Normal file
4
resources/test/fixtures/isort/fit_line_length_comment.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import a
|
||||
# Don't take this comment into account when determining whether the next import can fit on one line.
|
||||
from b import c
|
||||
from d import e # Do take this comment into account when determining whether the next import can fit on one line.
|
||||
12
resources/test/fixtures/isort/order_by_type.py
vendored
Normal file
12
resources/test/fixtures/isort/order_by_type.py
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
import StringIO
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import time
|
||||
from subprocess import PIPE, Popen, STDOUT
|
||||
from module import Class, CONSTANT, function, BASIC, Apple
|
||||
import foo
|
||||
import FOO
|
||||
import BAR
|
||||
import bar
|
||||
11
resources/test/fixtures/isort/preserve_comment_order.py
vendored
Normal file
11
resources/test/fixtures/isort/preserve_comment_order.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import io
|
||||
# Old MacDonald had a farm,
|
||||
# EIEIO
|
||||
# And on his farm he had a cow,
|
||||
# EIEIO
|
||||
# With a moo-moo here and a moo-moo there
|
||||
# Here a moo, there a moo, everywhere moo-moo
|
||||
# Old MacDonald had a farm,
|
||||
# EIEIO
|
||||
from errno import EIO
|
||||
import abc
|
||||
2
resources/test/fixtures/isort/pyproject.toml
vendored
Normal file
2
resources/test/fixtures/isort/pyproject.toml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
4
resources/test/fixtures/isort/separate_local_folder_imports.py
vendored
Normal file
4
resources/test/fixtures/isort/separate_local_folder_imports.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import sys
|
||||
import leading_prefix
|
||||
import os
|
||||
from . import leading_prefix
|
||||
10
resources/test/fixtures/isort/skip.py
vendored
Normal file
10
resources/test/fixtures/isort/skip.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# isort: off
|
||||
import sys
|
||||
import os
|
||||
import collections
|
||||
# isort: on
|
||||
|
||||
import sys
|
||||
import os # isort: skip
|
||||
import collections
|
||||
import abc
|
||||
26
resources/test/fixtures/isort/sort_similar_imports.py
vendored
Normal file
26
resources/test/fixtures/isort/sort_similar_imports.py
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
from a import b
|
||||
from a import BAD as DEF
|
||||
from a import B
|
||||
from a import Boo as DEF
|
||||
from a import B as Abc
|
||||
from a import B as A
|
||||
from a import B as DEF
|
||||
from a import b as a
|
||||
from a import b as x
|
||||
from a import b as c
|
||||
from b import c
|
||||
from a import b as d
|
||||
from a import b as y
|
||||
from b import C
|
||||
from b import c as d
|
||||
|
||||
import A
|
||||
import a
|
||||
import b
|
||||
import B
|
||||
|
||||
import x as y
|
||||
import x as A
|
||||
import x as Y
|
||||
import x
|
||||
import x as a
|
||||
2
resources/test/fixtures/isort/type_comments.py
vendored
Normal file
2
resources/test/fixtures/isort/type_comments.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
import A # type: ignore
|
||||
from B import C # type: ignore
|
||||
4
resources/test/fixtures/pyproject.toml
vendored
4
resources/test/fixtures/pyproject.toml
vendored
@@ -7,6 +7,9 @@ extend-exclude = [
|
||||
]
|
||||
per-file-ignores = { "__init__.py" = ["F401"] }
|
||||
|
||||
[tool.ruff.flake8-bugbear]
|
||||
extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"]
|
||||
|
||||
[tool.ruff.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
multiline-quotes = "double"
|
||||
@@ -30,6 +33,7 @@ ignore-names = [
|
||||
]
|
||||
classmethod-decorators = [
|
||||
"classmethod",
|
||||
"pydantic.validator",
|
||||
]
|
||||
staticmethod-decorators = [
|
||||
"staticmethod",
|
||||
|
||||
0
ruff/__init__.py
Normal file
0
ruff/__init__.py
Normal file
7
ruff/__main__.py
Normal file
7
ruff/__main__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import os
|
||||
import sys
|
||||
import sysconfig
|
||||
|
||||
if __name__ == "__main__":
|
||||
ruff = os.path.join(sysconfig.get_path("scripts"), "ruff")
|
||||
os.spawnv(os.P_WAIT, ruff, [ruff, *sys.argv[1:]])
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.110"
|
||||
version = "0.0.124"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, Location, StmtKind};
|
||||
|
||||
fn compose_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
|
||||
#[inline(always)]
|
||||
fn collect_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
|
||||
match &expr.node {
|
||||
ExprKind::Call { func, .. } => {
|
||||
compose_call_path_inner(func, parts);
|
||||
collect_call_path_inner(func, parts);
|
||||
}
|
||||
ExprKind::Attribute { value, attr, .. } => {
|
||||
compose_call_path_inner(value, parts);
|
||||
collect_call_path_inner(value, parts);
|
||||
parts.push(attr);
|
||||
}
|
||||
ExprKind::Name { id, .. } => {
|
||||
@@ -20,9 +20,10 @@ fn compose_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 mut segments = vec![];
|
||||
compose_call_path_inner(expr, &mut segments);
|
||||
let segments = collect_call_paths(expr);
|
||||
if segments.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -30,6 +31,34 @@ 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);
|
||||
segments
|
||||
}
|
||||
|
||||
/// Rewrite any import aliases on a call path.
|
||||
pub fn dealias_call_path<'a>(
|
||||
call_path: Vec<&'a str>,
|
||||
import_aliases: &FnvHashMap<&str, &'a str>,
|
||||
) -> Vec<&'a str> {
|
||||
if let Some(head) = call_path.first() {
|
||||
if let Some(origin) = import_aliases.get(head) {
|
||||
let tail = &call_path[1..];
|
||||
let mut call_path: Vec<&str> = vec![];
|
||||
call_path.extend(origin.split('.'));
|
||||
call_path.extend(tail);
|
||||
call_path
|
||||
} else {
|
||||
call_path
|
||||
}
|
||||
} else {
|
||||
call_path
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the `Expr` is a name or attribute reference to `${target}`.
|
||||
pub fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
|
||||
match &expr.node {
|
||||
@@ -43,24 +72,87 @@ pub fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
|
||||
///
|
||||
/// Useful for, e.g., ensuring that a `Union` reference represents
|
||||
/// `typing.Union`.
|
||||
pub fn match_name_or_attr_from_module(
|
||||
pub fn match_module_member(
|
||||
expr: &Expr,
|
||||
target: &str,
|
||||
module: &str,
|
||||
imports: Option<&BTreeSet<&str>>,
|
||||
member: &str,
|
||||
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
|
||||
import_aliases: &FnvHashMap<&str, &str>,
|
||||
) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { value, attr, .. } => match &value.node {
|
||||
ExprKind::Name { id, .. } => id == module && target == attr,
|
||||
_ => false,
|
||||
},
|
||||
ExprKind::Name { id, .. } => {
|
||||
target == id
|
||||
&& imports
|
||||
.map(|imports| imports.contains(&id.as_str()))
|
||||
.unwrap_or_default()
|
||||
match_call_path(
|
||||
&dealias_call_path(collect_call_paths(expr), import_aliases),
|
||||
module,
|
||||
member,
|
||||
from_imports,
|
||||
)
|
||||
}
|
||||
|
||||
/// Return `true` if the `call_path` is a reference to `${module}.${target}`.
|
||||
///
|
||||
/// Optimized version of `match_module_member` for pre-computed call paths.
|
||||
pub fn match_call_path(
|
||||
call_path: &[&str],
|
||||
module: &str,
|
||||
member: &str,
|
||||
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
|
||||
) -> bool {
|
||||
// If we have no segments, we can't ever match.
|
||||
let num_segments = call_path.len();
|
||||
if num_segments == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the last segment doesn't match the member, we can't ever match.
|
||||
if call_path[num_segments - 1] != member {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We now only need the module path, so throw out the member name.
|
||||
let call_path = &call_path[..num_segments - 1];
|
||||
let num_segments = call_path.len();
|
||||
|
||||
// Case (1): It's a builtin (like `list`).
|
||||
// Case (2a): We imported from the parent (`from typing.re import Match`,
|
||||
// `Match`).
|
||||
// Case (2b): We imported star from the parent (`from typing.re import *`,
|
||||
// `Match`).
|
||||
if num_segments == 0 {
|
||||
module.is_empty()
|
||||
|| from_imports
|
||||
.get(module)
|
||||
.map(|imports| imports.contains(member) || imports.contains("*"))
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
let components: Vec<&str> = module.split('.').collect();
|
||||
|
||||
// Case (3a): it's a fully qualified call path (`import typing`,
|
||||
// `typing.re.Match`). Case (3b): it's a fully qualified call path (`import
|
||||
// typing.re`, `typing.re.Match`).
|
||||
if components == call_path {
|
||||
return true;
|
||||
}
|
||||
_ => false,
|
||||
|
||||
// Case (4): We imported from the grandparent (`from typing import re`,
|
||||
// `re.Match`)
|
||||
let num_matches = (0..components.len())
|
||||
.take(num_segments)
|
||||
.take_while(|i| components[components.len() - 1 - i] == call_path[num_segments - 1 - i])
|
||||
.count();
|
||||
if num_matches > 0 {
|
||||
let cut = components.len() - num_matches;
|
||||
// TODO(charlie): Rewrite to avoid this allocation.
|
||||
let module = components[..cut].join(".");
|
||||
let member = components[cut];
|
||||
if from_imports
|
||||
.get(&module.as_str())
|
||||
.map(|imports| imports.contains(member))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +189,7 @@ pub fn is_assignment_to_a_dunder(node: &StmtKind) -> bool {
|
||||
}
|
||||
|
||||
/// Extract the names of all handled exceptions.
|
||||
pub fn extract_handler_names(handlers: &[Excepthandler]) -> Vec<String> {
|
||||
pub fn extract_handler_names(handlers: &[Excepthandler]) -> Vec<Vec<&str>> {
|
||||
let mut handler_names = vec![];
|
||||
for handler in handlers {
|
||||
match &handler.node {
|
||||
@@ -105,12 +197,16 @@ pub fn extract_handler_names(handlers: &[Excepthandler]) -> Vec<String> {
|
||||
if let Some(type_) = type_ {
|
||||
if let ExprKind::Tuple { elts, .. } = &type_.node {
|
||||
for type_ in elts {
|
||||
if let Some(name) = compose_call_path(type_) {
|
||||
handler_names.push(name);
|
||||
let call_path = collect_call_paths(type_);
|
||||
if !call_path.is_empty() {
|
||||
handler_names.push(call_path);
|
||||
}
|
||||
}
|
||||
} else if let Some(name) = compose_call_path(type_) {
|
||||
handler_names.push(name);
|
||||
} else {
|
||||
let call_path = collect_call_paths(type_);
|
||||
if !call_path.is_empty() {
|
||||
handler_names.push(call_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,6 +225,29 @@ pub fn is_super_call_with_arguments(func: &Expr, args: &[Expr]) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// Format the module name for a relative import.
|
||||
pub fn format_import_from(level: Option<&usize>, module: Option<&String>) -> String {
|
||||
let mut module_name = String::with_capacity(16);
|
||||
if let Some(level) = level {
|
||||
for _ in 0..*level {
|
||||
module_name.push('.');
|
||||
}
|
||||
}
|
||||
if let Some(module) = module {
|
||||
module_name.push_str(module);
|
||||
}
|
||||
module_name
|
||||
}
|
||||
|
||||
/// Split a target string (like `typing.List`) into (`typing`, `List`).
|
||||
pub fn to_module_and_member(target: &str) -> (&str, &str) {
|
||||
if let Some(index) = target.rfind('.') {
|
||||
(&target[..index], &target[index + 1..])
|
||||
} else {
|
||||
("", target)
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a location within a file (relative to `base`) to an absolute
|
||||
/// position.
|
||||
pub fn to_absolute(relative: &Location, base: &Location) -> Location {
|
||||
@@ -141,3 +260,155 @@ pub fn to_absolute(relative: &Location, base: &Location) -> Location {
|
||||
Location::new(relative.row() + base.row() - 1, relative.column())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::ast::helpers::match_module_member;
|
||||
|
||||
#[test]
|
||||
fn builtin() -> Result<()> {
|
||||
let expr = parser::parse_expression("list", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"",
|
||||
"list",
|
||||
&FnvHashMap::default(),
|
||||
&FnvHashMap::default(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fully_qualified() -> Result<()> {
|
||||
let expr = parser::parse_expression("typing.re.Match", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::default(),
|
||||
&FnvHashMap::default(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unimported() -> Result<()> {
|
||||
let expr = parser::parse_expression("Match", "<filename>")?;
|
||||
assert!(!match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::default(),
|
||||
&FnvHashMap::default(),
|
||||
));
|
||||
let expr = parser::parse_expression("re.Match", "<filename>")?;
|
||||
assert!(!match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::default(),
|
||||
&FnvHashMap::default(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_star() -> Result<()> {
|
||||
let expr = parser::parse_expression("Match", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::from_iter([("typing.re", FnvHashSet::from_iter(["*"]))]),
|
||||
&FnvHashMap::default()
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_parent() -> Result<()> {
|
||||
let expr = parser::parse_expression("Match", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::from_iter([("typing.re", FnvHashSet::from_iter(["Match"]))]),
|
||||
&FnvHashMap::default()
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_grandparent() -> Result<()> {
|
||||
let expr = parser::parse_expression("re.Match", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::from_iter([("typing", FnvHashSet::from_iter(["re"]))]),
|
||||
&FnvHashMap::default()
|
||||
));
|
||||
|
||||
let expr = parser::parse_expression("match.Match", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re.match",
|
||||
"Match",
|
||||
&FnvHashMap::from_iter([("typing.re", FnvHashSet::from_iter(["match"]))]),
|
||||
&FnvHashMap::default()
|
||||
));
|
||||
|
||||
let expr = parser::parse_expression("re.match.Match", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re.match",
|
||||
"Match",
|
||||
&FnvHashMap::from_iter([("typing", FnvHashSet::from_iter(["re"]))]),
|
||||
&FnvHashMap::default()
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_alias() -> Result<()> {
|
||||
let expr = parser::parse_expression("IMatch", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::from_iter([("typing.re", FnvHashSet::from_iter(["Match"]))]),
|
||||
&FnvHashMap::from_iter([("IMatch", "Match")]),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_aliased_parent() -> Result<()> {
|
||||
let expr = parser::parse_expression("t.Match", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::default(),
|
||||
&FnvHashMap::from_iter([("t", "typing.re")]),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_aliased_grandparent() -> Result<()> {
|
||||
let expr = parser::parse_expression("t.re.Match", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::default(),
|
||||
&FnvHashMap::from_iter([("t", "typing")]),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,9 +67,8 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
|
||||
}
|
||||
|
||||
/// Check if a node is parent of a conditional branch.
|
||||
pub fn on_conditional_branch(parent_stack: &[usize], parents: &[&Stmt]) -> bool {
|
||||
for index in parent_stack.iter().rev() {
|
||||
let parent = parents[*index];
|
||||
pub fn on_conditional_branch<'a>(parents: &mut impl Iterator<Item = &'a Stmt>) -> bool {
|
||||
parents.any(|parent| {
|
||||
if matches!(parent.node, StmtKind::If { .. } | StmtKind::While { .. }) {
|
||||
return true;
|
||||
}
|
||||
@@ -78,24 +77,18 @@ pub fn on_conditional_branch(parent_stack: &[usize], parents: &[&Stmt]) -> bool
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if a node is in a nested block.
|
||||
pub fn in_nested_block(parent_stack: &[usize], parents: &[&Stmt]) -> bool {
|
||||
for index in parent_stack.iter().rev() {
|
||||
let parent = parents[*index];
|
||||
if matches!(
|
||||
pub fn in_nested_block<'a>(parents: &mut impl Iterator<Item = &'a Stmt>) -> bool {
|
||||
parents.any(|parent| {
|
||||
matches!(
|
||||
parent.node,
|
||||
StmtKind::Try { .. } | StmtKind::If { .. } | StmtKind::With { .. }
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if a node represents an unpacking assignment.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use fnv::FnvHashMap;
|
||||
use rustpython_ast::{Expr, Keyword};
|
||||
use rustpython_parser::ast::{Located, Location};
|
||||
|
||||
@@ -46,6 +46,7 @@ pub enum ScopeKind<'a> {
|
||||
Generator,
|
||||
Module,
|
||||
Arg,
|
||||
Lambda,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -53,7 +54,7 @@ pub struct Scope<'a> {
|
||||
pub id: usize,
|
||||
pub kind: ScopeKind<'a>,
|
||||
pub import_starred: bool,
|
||||
pub values: BTreeMap<String, Binding>,
|
||||
pub values: FnvHashMap<&'a str, Binding>,
|
||||
}
|
||||
|
||||
impl<'a> Scope<'a> {
|
||||
@@ -62,7 +63,7 @@ impl<'a> Scope<'a> {
|
||||
id: id(),
|
||||
kind,
|
||||
import_starred: false,
|
||||
values: BTreeMap::new(),
|
||||
values: FnvHashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,7 +86,7 @@ pub enum BindingKind {
|
||||
Definition,
|
||||
Export(Vec<String>),
|
||||
FutureImportation,
|
||||
StarImportation,
|
||||
StarImportation(Option<usize>, Option<String>),
|
||||
Importation(String, String, BindingContext),
|
||||
FromImportation(String, String, BindingContext),
|
||||
SubmoduleImportation(String, String, BindingContext),
|
||||
|
||||
@@ -61,7 +61,7 @@ fn apply_fixes<'a>(
|
||||
) -> Cow<'a, str> {
|
||||
let mut output = RopeBuilder::new();
|
||||
let mut last_pos: Location = Location::new(1, 0);
|
||||
let mut applied: BTreeSet<&Patch> = BTreeSet::new();
|
||||
let mut applied: BTreeSet<&Patch> = BTreeSet::default();
|
||||
|
||||
for fix in fixes.sorted_by_key(|fix| fix.patch.location) {
|
||||
// If we already applied an identical fix as part of another correction, skip
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::autofix::fixer;
|
||||
use crate::message::Message;
|
||||
use crate::settings::Settings;
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct CacheMetadata {
|
||||
@@ -89,7 +89,7 @@ fn cache_key(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> String
|
||||
format!(
|
||||
"{}@{}@{}",
|
||||
path.absolutize().unwrap().to_string_lossy(),
|
||||
VERSION,
|
||||
CARGO_PKG_VERSION,
|
||||
hasher.finish()
|
||||
)
|
||||
}
|
||||
|
||||
797
src/check_ast.rs
797
src/check_ast.rs
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
//! Lint rules based on import analysis.
|
||||
|
||||
use nohash_hasher::IntSet;
|
||||
use rustpython_parser::ast::Suite;
|
||||
|
||||
use crate::ast::visitor::Visitor;
|
||||
@@ -30,10 +31,11 @@ fn check_import_blocks(
|
||||
pub fn check_imports(
|
||||
python_ast: &Suite,
|
||||
locator: &SourceCodeLocator,
|
||||
exclusions: &IntSet<usize>,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Vec<Check> {
|
||||
let mut tracker = ImportTracker::new();
|
||||
let mut tracker = ImportTracker::new(exclusions);
|
||||
for stmt in python_ast {
|
||||
tracker.visit_stmt(stmt);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! Lint rules based on checking raw physical lines.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use nohash_hasher::IntMap;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::ast::Location;
|
||||
@@ -36,7 +35,7 @@ fn should_enforce_line_length(line: &str, length: usize, limit: usize) -> bool {
|
||||
pub fn check_lines(
|
||||
checks: &mut Vec<Check>,
|
||||
contents: &str,
|
||||
noqa_line_for: &[usize],
|
||||
noqa_line_for: &IntMap<usize, usize>,
|
||||
settings: &Settings,
|
||||
autofix: &fixer::Mode,
|
||||
) {
|
||||
@@ -44,20 +43,22 @@ pub fn check_lines(
|
||||
let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501);
|
||||
let enforce_noqa = settings.enabled.contains(&CheckCode::M001);
|
||||
|
||||
let mut noqa_directives: BTreeMap<usize, (Directive, Vec<&str>)> = BTreeMap::new();
|
||||
|
||||
let mut noqa_directives: IntMap<usize, (Directive, Vec<&str>)> = IntMap::default();
|
||||
let mut line_checks = vec![];
|
||||
let mut ignored = vec![];
|
||||
|
||||
checks.sort_by_key(|check| check.location);
|
||||
let mut checks_iter = checks.iter().enumerate().peekable();
|
||||
if let Some((_index, check)) = checks_iter.peek() {
|
||||
assert!(check.location.row() >= 1);
|
||||
}
|
||||
|
||||
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.
|
||||
// If there are newlines at the end of the file, they won't be represented in
|
||||
// `noqa_line_for`, so fallback to the current line.
|
||||
let noqa_lineno = noqa_line_for
|
||||
.get(lineno)
|
||||
.map(|lineno| lineno - 1)
|
||||
.unwrap_or(lineno);
|
||||
let noqa_lineno = noqa_line_for.get(&(lineno + 1)).unwrap_or(&(lineno + 1)) - 1;
|
||||
|
||||
// Enforce unnecessary coding comments (U009).
|
||||
if enforce_unnecessary_coding_comment {
|
||||
@@ -90,26 +91,25 @@ pub fn check_lines(
|
||||
}
|
||||
|
||||
// Remove any ignored checks.
|
||||
// TODO(charlie): Only validate checks for the current line.
|
||||
for (index, check) in checks.iter().enumerate() {
|
||||
if check.location.row() == lineno + 1 {
|
||||
let noqa = noqa_directives
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
|
||||
while let Some((index, check)) =
|
||||
checks_iter.next_if(|(_index, check)| check.location.row() == lineno + 1)
|
||||
{
|
||||
let noqa = noqa_directives
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
|
||||
|
||||
match noqa {
|
||||
(Directive::All(..), matches) => {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
ignored.push(index)
|
||||
}
|
||||
(Directive::Codes(_, _, codes), matches) => {
|
||||
if codes.contains(&check.kind.code().as_ref()) {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
ignored.push(index);
|
||||
}
|
||||
}
|
||||
(Directive::None, _) => {}
|
||||
match noqa {
|
||||
(Directive::All(..), matches) => {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
ignored.push(index)
|
||||
}
|
||||
(Directive::Codes(_, _, codes), matches) => {
|
||||
if codes.contains(&check.kind.code().as_ref()) {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
ignored.push(index);
|
||||
}
|
||||
}
|
||||
(Directive::None, _) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,10 +152,7 @@ pub fn check_lines(
|
||||
// want to raise W292 anyway).
|
||||
if let Some(line) = lines.last() {
|
||||
let lineno = lines.len() - 1;
|
||||
let noqa_lineno = noqa_line_for
|
||||
.get(lineno)
|
||||
.map(|lineno| lineno - 1)
|
||||
.unwrap_or(lineno);
|
||||
let noqa_lineno = noqa_line_for.get(&(lineno + 1)).unwrap_or(&(lineno + 1)) - 1;
|
||||
|
||||
let noqa = noqa_directives
|
||||
.entry(noqa_lineno)
|
||||
@@ -257,6 +254,8 @@ pub fn check_lines(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nohash_hasher::IntMap;
|
||||
|
||||
use super::check_lines;
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
@@ -265,7 +264,7 @@ mod tests {
|
||||
#[test]
|
||||
fn e501_non_ascii_char() {
|
||||
let line = "'\u{4e9c}' * 2"; // 7 in UTF-32, 9 in UTF-8.
|
||||
let noqa_line_for: Vec<usize> = vec![1];
|
||||
let noqa_line_for: IntMap<usize, usize> = Default::default();
|
||||
let check_with_max_line_length = |line_length: usize| {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
check_lines(
|
||||
@@ -278,7 +277,7 @@ mod tests {
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
);
|
||||
return checks;
|
||||
checks
|
||||
};
|
||||
assert!(!check_with_max_line_length(6).is_empty());
|
||||
assert!(check_with_max_line_length(7).is_empty());
|
||||
|
||||
291
src/checks.rs
291
src/checks.rs
@@ -85,15 +85,23 @@ pub enum CheckCode {
|
||||
B007,
|
||||
B008,
|
||||
B009,
|
||||
B010,
|
||||
B011,
|
||||
B012,
|
||||
B013,
|
||||
B014,
|
||||
B015,
|
||||
B016,
|
||||
B017,
|
||||
B018,
|
||||
B019,
|
||||
B020,
|
||||
B021,
|
||||
B022,
|
||||
B024,
|
||||
B025,
|
||||
B026,
|
||||
B027,
|
||||
// flake8-comprehensions
|
||||
C400,
|
||||
C401,
|
||||
@@ -131,6 +139,17 @@ pub enum CheckCode {
|
||||
ANN205,
|
||||
ANN206,
|
||||
ANN401,
|
||||
// flake8-2020
|
||||
YTT101,
|
||||
YTT102,
|
||||
YTT103,
|
||||
YTT201,
|
||||
YTT202,
|
||||
YTT203,
|
||||
YTT204,
|
||||
YTT301,
|
||||
YTT302,
|
||||
YTT303,
|
||||
// pyupgrade
|
||||
U001,
|
||||
U002,
|
||||
@@ -143,6 +162,7 @@ pub enum CheckCode {
|
||||
U009,
|
||||
U010,
|
||||
U011,
|
||||
U012,
|
||||
// pydocstyle
|
||||
D100,
|
||||
D101,
|
||||
@@ -206,6 +226,13 @@ pub enum CheckCode {
|
||||
N818,
|
||||
// isort
|
||||
I001,
|
||||
// flake8-bandit
|
||||
S101,
|
||||
S102,
|
||||
S104,
|
||||
S105,
|
||||
S106,
|
||||
S107,
|
||||
// Ruff
|
||||
RUF001,
|
||||
RUF002,
|
||||
@@ -222,12 +249,14 @@ pub enum CheckCategory {
|
||||
Pydocstyle,
|
||||
Pyupgrade,
|
||||
PEP8Naming,
|
||||
Flake8Bandit,
|
||||
Flake8Comprehensions,
|
||||
Flake8Bugbear,
|
||||
Flake8Builtins,
|
||||
Flake8Print,
|
||||
Flake8Quotes,
|
||||
Flake8Annotations,
|
||||
Flake82020,
|
||||
Ruff,
|
||||
Meta,
|
||||
}
|
||||
@@ -238,12 +267,14 @@ impl CheckCategory {
|
||||
CheckCategory::Pycodestyle => "pycodestyle",
|
||||
CheckCategory::Pyflakes => "Pyflakes",
|
||||
CheckCategory::Isort => "isort",
|
||||
CheckCategory::Flake8Bandit => "flake8-bandit",
|
||||
CheckCategory::Flake8Builtins => "flake8-builtins",
|
||||
CheckCategory::Flake8Bugbear => "flake8-bugbear",
|
||||
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
|
||||
CheckCategory::Flake8Print => "flake8-print",
|
||||
CheckCategory::Flake8Quotes => "flake8-quotes",
|
||||
CheckCategory::Flake8Annotations => "flake8-annotations",
|
||||
CheckCategory::Flake82020 => "flake8-2020",
|
||||
CheckCategory::Pyupgrade => "pyupgrade",
|
||||
CheckCategory::Pydocstyle => "pydocstyle",
|
||||
CheckCategory::PEP8Naming => "pep8-naming",
|
||||
@@ -271,9 +302,11 @@ impl CheckCategory {
|
||||
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::Ruff => None,
|
||||
CheckCategory::Meta => None,
|
||||
}
|
||||
@@ -355,17 +388,25 @@ pub enum CheckKind {
|
||||
StripWithMultiCharacters,
|
||||
MutableArgumentDefault,
|
||||
UnusedLoopControlVariable(String),
|
||||
FunctionCallArgumentDefault,
|
||||
FunctionCallArgumentDefault(Option<String>),
|
||||
GetAttrWithConstant,
|
||||
SetAttrWithConstant,
|
||||
DoNotAssertFalse,
|
||||
JumpStatementInFinally(String),
|
||||
RedundantTupleInExceptionHandler(String),
|
||||
DuplicateHandlerException(Vec<String>),
|
||||
UselessComparison,
|
||||
CannotRaiseLiteral,
|
||||
NoAssertRaisesException,
|
||||
UselessExpression,
|
||||
CachedInstanceMethod,
|
||||
LoopVariableOverridesIterator(String),
|
||||
FStringDocstring,
|
||||
UselessContextlibSuppress,
|
||||
AbstractBaseClassWithoutAbstractMethod(String),
|
||||
DuplicateTryBlockException(String),
|
||||
StarArgUnpackingAfterKeywordArg,
|
||||
EmptyMethodWithoutAbstractDecorator(String),
|
||||
// flake8-comprehensions
|
||||
UnnecessaryGeneratorList,
|
||||
UnnecessaryGeneratorSet,
|
||||
@@ -403,6 +444,17 @@ pub enum CheckKind {
|
||||
MissingReturnTypeStaticMethod(String),
|
||||
MissingReturnTypeClassMethod(String),
|
||||
DynamicallyTypedExpression(String),
|
||||
// flake8-2020
|
||||
SysVersionSlice3Referenced,
|
||||
SysVersion2Referenced,
|
||||
SysVersionCmpStr3,
|
||||
SysVersionInfo0Eq3Referenced,
|
||||
SixPY3Referenced,
|
||||
SysVersionInfo1CmpInt,
|
||||
SysVersionInfoMinorCmpInt,
|
||||
SysVersion0Referenced,
|
||||
SysVersionCmpStr10,
|
||||
SysVersionSlice1Referenced,
|
||||
// pyupgrade
|
||||
TypeOfPrimitive(Primitive),
|
||||
UnnecessaryAbspath,
|
||||
@@ -413,8 +465,9 @@ pub enum CheckKind {
|
||||
UsePEP604Annotation,
|
||||
SuperCallWithParameters,
|
||||
PEP3120UnnecessaryCodingComment,
|
||||
UnnecessaryFutureImport(String),
|
||||
UnnecessaryFutureImport(Vec<String>),
|
||||
UnnecessaryLRUCacheParams,
|
||||
UnnecessaryEncodeUTF8,
|
||||
// pydocstyle
|
||||
BlankLineAfterLastSection(String),
|
||||
BlankLineAfterSection(String),
|
||||
@@ -478,6 +531,13 @@ pub enum CheckKind {
|
||||
ErrorSuffixOnExceptionName(String),
|
||||
// isort
|
||||
UnsortedImports,
|
||||
// flake8-bandit
|
||||
AssertUsed,
|
||||
ExecUsed,
|
||||
HardcodedBindAllInterfaces,
|
||||
HardcodedPasswordString(String),
|
||||
HardcodedPasswordFuncArg(String),
|
||||
HardcodedPasswordDefault(String),
|
||||
// Ruff
|
||||
AmbiguousUnicodeCharacterString(char, char),
|
||||
AmbiguousUnicodeCharacterDocstring(char, char),
|
||||
@@ -571,9 +631,13 @@ impl CheckCode {
|
||||
CheckCode::B005 => CheckKind::StripWithMultiCharacters,
|
||||
CheckCode::B006 => CheckKind::MutableArgumentDefault,
|
||||
CheckCode::B007 => CheckKind::UnusedLoopControlVariable("i".to_string()),
|
||||
CheckCode::B008 => CheckKind::FunctionCallArgumentDefault,
|
||||
CheckCode::B008 => CheckKind::FunctionCallArgumentDefault(None),
|
||||
CheckCode::B009 => CheckKind::GetAttrWithConstant,
|
||||
CheckCode::B010 => CheckKind::SetAttrWithConstant,
|
||||
CheckCode::B011 => CheckKind::DoNotAssertFalse,
|
||||
CheckCode::B012 => {
|
||||
CheckKind::JumpStatementInFinally("return/continue/break".to_string())
|
||||
}
|
||||
CheckCode::B013 => {
|
||||
CheckKind::RedundantTupleInExceptionHandler("ValueError".to_string())
|
||||
}
|
||||
@@ -582,8 +646,14 @@ impl CheckCode {
|
||||
CheckCode::B016 => CheckKind::CannotRaiseLiteral,
|
||||
CheckCode::B017 => CheckKind::NoAssertRaisesException,
|
||||
CheckCode::B018 => CheckKind::UselessExpression,
|
||||
CheckCode::B019 => CheckKind::CachedInstanceMethod,
|
||||
CheckCode::B020 => CheckKind::LoopVariableOverridesIterator("...".to_string()),
|
||||
CheckCode::B021 => CheckKind::FStringDocstring,
|
||||
CheckCode::B022 => CheckKind::UselessContextlibSuppress,
|
||||
CheckCode::B024 => CheckKind::AbstractBaseClassWithoutAbstractMethod("...".to_string()),
|
||||
CheckCode::B025 => CheckKind::DuplicateTryBlockException("Exception".to_string()),
|
||||
CheckCode::B026 => CheckKind::StarArgUnpackingAfterKeywordArg,
|
||||
CheckCode::B027 => CheckKind::EmptyMethodWithoutAbstractDecorator("...".to_string()),
|
||||
// flake8-comprehensions
|
||||
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
|
||||
CheckCode::C401 => CheckKind::UnnecessaryGeneratorSet,
|
||||
@@ -634,6 +704,17 @@ impl CheckCode {
|
||||
CheckCode::ANN205 => CheckKind::MissingReturnTypeStaticMethod("...".to_string()),
|
||||
CheckCode::ANN206 => CheckKind::MissingReturnTypeClassMethod("...".to_string()),
|
||||
CheckCode::ANN401 => CheckKind::DynamicallyTypedExpression("...".to_string()),
|
||||
// flake8-2020
|
||||
CheckCode::YTT101 => CheckKind::SysVersionSlice3Referenced,
|
||||
CheckCode::YTT102 => CheckKind::SysVersion2Referenced,
|
||||
CheckCode::YTT103 => CheckKind::SysVersionCmpStr3,
|
||||
CheckCode::YTT201 => CheckKind::SysVersionInfo0Eq3Referenced,
|
||||
CheckCode::YTT202 => CheckKind::SixPY3Referenced,
|
||||
CheckCode::YTT203 => CheckKind::SysVersionInfo1CmpInt,
|
||||
CheckCode::YTT204 => CheckKind::SysVersionInfoMinorCmpInt,
|
||||
CheckCode::YTT301 => CheckKind::SysVersion0Referenced,
|
||||
CheckCode::YTT302 => CheckKind::SysVersionCmpStr10,
|
||||
CheckCode::YTT303 => CheckKind::SysVersionSlice1Referenced,
|
||||
// pyupgrade
|
||||
CheckCode::U001 => CheckKind::UselessMetaclassType,
|
||||
CheckCode::U002 => CheckKind::UnnecessaryAbspath,
|
||||
@@ -647,8 +728,9 @@ impl CheckCode {
|
||||
CheckCode::U007 => CheckKind::UsePEP604Annotation,
|
||||
CheckCode::U008 => CheckKind::SuperCallWithParameters,
|
||||
CheckCode::U009 => CheckKind::PEP3120UnnecessaryCodingComment,
|
||||
CheckCode::U010 => CheckKind::UnnecessaryFutureImport("...".to_string()),
|
||||
CheckCode::U010 => CheckKind::UnnecessaryFutureImport(vec!["...".to_string()]),
|
||||
CheckCode::U011 => CheckKind::UnnecessaryLRUCacheParams,
|
||||
CheckCode::U012 => CheckKind::UnnecessaryEncodeUTF8,
|
||||
// pydocstyle
|
||||
CheckCode::D100 => CheckKind::PublicModule,
|
||||
CheckCode::D101 => CheckKind::PublicClass,
|
||||
@@ -728,6 +810,13 @@ impl CheckCode {
|
||||
CheckCode::N818 => CheckKind::ErrorSuffixOnExceptionName("...".to_string()),
|
||||
// isort
|
||||
CheckCode::I001 => CheckKind::UnsortedImports,
|
||||
// flake8-bandit
|
||||
CheckCode::S101 => CheckKind::AssertUsed,
|
||||
CheckCode::S102 => CheckKind::ExecUsed,
|
||||
CheckCode::S104 => CheckKind::HardcodedBindAllInterfaces,
|
||||
CheckCode::S105 => CheckKind::HardcodedPasswordString("...".to_string()),
|
||||
CheckCode::S106 => CheckKind::HardcodedPasswordFuncArg("...".to_string()),
|
||||
CheckCode::S107 => CheckKind::HardcodedPasswordDefault("...".to_string()),
|
||||
// Ruff
|
||||
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
|
||||
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
|
||||
@@ -794,15 +883,23 @@ impl CheckCode {
|
||||
CheckCode::B007 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B008 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B009 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B010 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B011 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B012 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B013 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B014 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B015 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B016 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B017 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B018 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B019 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B020 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B021 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B022 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B024 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B025 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B026 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B027 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::C400 => CheckCategory::Flake8Comprehensions,
|
||||
CheckCode::C401 => CheckCategory::Flake8Comprehensions,
|
||||
CheckCode::C402 => CheckCategory::Flake8Comprehensions,
|
||||
@@ -836,6 +933,16 @@ impl CheckCode {
|
||||
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::U002 => CheckCategory::Pyupgrade,
|
||||
CheckCode::U003 => CheckCategory::Pyupgrade,
|
||||
@@ -847,6 +954,7 @@ impl CheckCode {
|
||||
CheckCode::U009 => CheckCategory::Pyupgrade,
|
||||
CheckCode::U010 => CheckCategory::Pyupgrade,
|
||||
CheckCode::U011 => CheckCategory::Pyupgrade,
|
||||
CheckCode::U012 => CheckCategory::Pyupgrade,
|
||||
CheckCode::D100 => CheckCategory::Pydocstyle,
|
||||
CheckCode::D101 => CheckCategory::Pydocstyle,
|
||||
CheckCode::D102 => CheckCategory::Pydocstyle,
|
||||
@@ -907,6 +1015,12 @@ impl CheckCode {
|
||||
CheckCode::N817 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N818 => CheckCategory::PEP8Naming,
|
||||
CheckCode::I001 => CheckCategory::Isort,
|
||||
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::RUF001 => CheckCategory::Ruff,
|
||||
CheckCode::RUF002 => CheckCategory::Ruff,
|
||||
CheckCode::RUF003 => CheckCategory::Ruff,
|
||||
@@ -976,17 +1090,25 @@ impl CheckKind {
|
||||
CheckKind::StripWithMultiCharacters => &CheckCode::B005,
|
||||
CheckKind::MutableArgumentDefault => &CheckCode::B006,
|
||||
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
|
||||
CheckKind::FunctionCallArgumentDefault => &CheckCode::B008,
|
||||
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::DuplicateTryBlockException(_) => &CheckCode::B025,
|
||||
CheckKind::StarArgUnpackingAfterKeywordArg => &CheckCode::B026,
|
||||
CheckKind::EmptyMethodWithoutAbstractDecorator(_) => &CheckCode::B027,
|
||||
// flake8-comprehensions
|
||||
CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
|
||||
CheckKind::UnnecessaryGeneratorSet => &CheckCode::C401,
|
||||
@@ -1024,6 +1146,17 @@ impl CheckKind {
|
||||
CheckKind::MissingReturnTypeStaticMethod(_) => &CheckCode::ANN205,
|
||||
CheckKind::MissingReturnTypeClassMethod(_) => &CheckCode::ANN206,
|
||||
CheckKind::DynamicallyTypedExpression(_) => &CheckCode::ANN401,
|
||||
// flake8-2020
|
||||
CheckKind::SysVersionSlice3Referenced => &CheckCode::YTT101,
|
||||
CheckKind::SysVersion2Referenced => &CheckCode::YTT102,
|
||||
CheckKind::SysVersionCmpStr3 => &CheckCode::YTT103,
|
||||
CheckKind::SysVersionInfo0Eq3Referenced => &CheckCode::YTT201,
|
||||
CheckKind::SixPY3Referenced => &CheckCode::YTT202,
|
||||
CheckKind::SysVersionInfo1CmpInt => &CheckCode::YTT203,
|
||||
CheckKind::SysVersionInfoMinorCmpInt => &CheckCode::YTT204,
|
||||
CheckKind::SysVersion0Referenced => &CheckCode::YTT301,
|
||||
CheckKind::SysVersionCmpStr10 => &CheckCode::YTT302,
|
||||
CheckKind::SysVersionSlice1Referenced => &CheckCode::YTT303,
|
||||
// pyupgrade
|
||||
CheckKind::TypeOfPrimitive(_) => &CheckCode::U003,
|
||||
CheckKind::UnnecessaryAbspath => &CheckCode::U002,
|
||||
@@ -1036,6 +1169,7 @@ impl CheckKind {
|
||||
CheckKind::PEP3120UnnecessaryCodingComment => &CheckCode::U009,
|
||||
CheckKind::UnnecessaryFutureImport(_) => &CheckCode::U010,
|
||||
CheckKind::UnnecessaryLRUCacheParams => &CheckCode::U011,
|
||||
CheckKind::UnnecessaryEncodeUTF8 => &CheckCode::U012,
|
||||
// pydocstyle
|
||||
CheckKind::BlankLineAfterLastSection(_) => &CheckCode::D413,
|
||||
CheckKind::BlankLineAfterSection(_) => &CheckCode::D410,
|
||||
@@ -1099,6 +1233,13 @@ impl CheckKind {
|
||||
CheckKind::ErrorSuffixOnExceptionName(..) => &CheckCode::N818,
|
||||
// isort
|
||||
CheckKind::UnsortedImports => &CheckCode::I001,
|
||||
// flake8-bandit
|
||||
CheckKind::AssertUsed => &CheckCode::S101,
|
||||
CheckKind::ExecUsed => &CheckCode::S102,
|
||||
CheckKind::HardcodedBindAllInterfaces => &CheckCode::S104,
|
||||
CheckKind::HardcodedPasswordString(..) => &CheckCode::S105,
|
||||
CheckKind::HardcodedPasswordFuncArg(..) => &CheckCode::S106,
|
||||
CheckKind::HardcodedPasswordDefault(..) => &CheckCode::S107,
|
||||
// Ruff
|
||||
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
|
||||
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
|
||||
@@ -1127,7 +1268,7 @@ impl CheckKind {
|
||||
CheckKind::BreakOutsideLoop => "`break` outside loop".to_string(),
|
||||
CheckKind::ContinueOutsideLoop => "`continue` not properly in loop".to_string(),
|
||||
CheckKind::DefaultExceptNotLast => {
|
||||
"An `except:` block as not the last exception handler".to_string()
|
||||
"An `except` block as not the last exception handler".to_string()
|
||||
}
|
||||
CheckKind::DoNotAssignLambda => {
|
||||
"Do not assign a lambda expression, use a def".to_string()
|
||||
@@ -1264,36 +1405,47 @@ impl CheckKind {
|
||||
`+(+(n))`, which equals `n`. You meant `n += 1`."
|
||||
.to_string(),
|
||||
CheckKind::AssignmentToOsEnviron => {
|
||||
"Assigning to `os.environ` doesn't clear the environment.".to_string()
|
||||
"Assigning to `os.environ` doesn't clear the environment".to_string()
|
||||
}
|
||||
CheckKind::UnreliableCallableCheck => " Using `hasattr(x, '__call__')` to test if x \
|
||||
is callable is unreliable. Use `callable(x)` \
|
||||
for consistent results."
|
||||
.to_string(),
|
||||
CheckKind::StripWithMultiCharacters => "Using `.strip()` with multi-character strings \
|
||||
is misleading the reader."
|
||||
.to_string(),
|
||||
CheckKind::StripWithMultiCharacters => {
|
||||
"Using `.strip()` with multi-character strings is misleading the reader".to_string()
|
||||
}
|
||||
CheckKind::MutableArgumentDefault => {
|
||||
"Do not use mutable data structures for argument defaults.".to_string()
|
||||
"Do not use mutable data structures for argument defaults".to_string()
|
||||
}
|
||||
CheckKind::UnusedLoopControlVariable(name) => format!(
|
||||
"Loop control variable `{name}` not used within the loop body. If this is \
|
||||
intended, start the name with an underscore."
|
||||
),
|
||||
CheckKind::FunctionCallArgumentDefault => {
|
||||
"Do not perform function calls in argument defaults.".to_string()
|
||||
CheckKind::FunctionCallArgumentDefault(name) => {
|
||||
if let Some(name) = name {
|
||||
format!("Do not perform function call `{name}` in argument defaults")
|
||||
} else {
|
||||
"Do not perform function call in argument defaults".to_string()
|
||||
}
|
||||
}
|
||||
CheckKind::GetAttrWithConstant => "Do not call `getattr` with a constant attribute \
|
||||
value, it is not any safer than normal property \
|
||||
value. It is not any safer than normal property \
|
||||
access."
|
||||
.to_string(),
|
||||
CheckKind::SetAttrWithConstant => "Do not call `setattr` with a constant attribute \
|
||||
value. It is not any safer than normal property \
|
||||
access."
|
||||
.to_string(),
|
||||
CheckKind::DoNotAssertFalse => "Do not `assert False` (`python -O` removes these \
|
||||
calls), raise `AssertionError()`"
|
||||
.to_string(),
|
||||
CheckKind::JumpStatementInFinally(name) => {
|
||||
format!("`{name}` inside finally blocks cause exceptions to be silenced")
|
||||
}
|
||||
CheckKind::RedundantTupleInExceptionHandler(name) => {
|
||||
format!(
|
||||
"A length-one tuple literal is redundant. Write `except {name}:` instead of \
|
||||
`except ({name},):`."
|
||||
"A length-one tuple literal is redundant. Write `except {name}` instead of \
|
||||
`except ({name},)`."
|
||||
)
|
||||
}
|
||||
CheckKind::UselessComparison => "Pointless comparison. This comparison does nothing \
|
||||
@@ -1313,7 +1465,7 @@ impl CheckKind {
|
||||
}
|
||||
}
|
||||
CheckKind::NoAssertRaisesException => {
|
||||
"`assertRaises(Exception):` should be considered evil. It can lead to your test \
|
||||
"`assertRaises(Exception)` should be considered evil. It can lead to your test \
|
||||
passing even if the code being tested is never executed due to a typo. Either \
|
||||
assert for a more specific exception (builtin or custom), use \
|
||||
`assertRaisesRegex`, or use the context manager form of `assertRaises`."
|
||||
@@ -1322,16 +1474,38 @@ impl CheckKind {
|
||||
CheckKind::UselessExpression => {
|
||||
"Found useless expression. Either assign it to a variable or remove it.".to_string()
|
||||
}
|
||||
CheckKind::CachedInstanceMethod => "Use of `functools.lru_cache` or `functools.cache` \
|
||||
on methods can lead to memory leaks"
|
||||
.to_string(),
|
||||
CheckKind::LoopVariableOverridesIterator(name) => {
|
||||
format!("Loop control variable `{name}` overrides iterable it iterates")
|
||||
}
|
||||
CheckKind::FStringDocstring => "f-string used as docstring. This will be interpreted \
|
||||
by python as a joined string rather than a docstring."
|
||||
.to_string(),
|
||||
CheckKind::UselessContextlibSuppress => {
|
||||
"No arguments passed to `contextlib.suppress`. No exceptions will be suppressed \
|
||||
and therefore this context manager is redundant"
|
||||
.to_string()
|
||||
}
|
||||
CheckKind::AbstractBaseClassWithoutAbstractMethod(name) => {
|
||||
format!("`{name}` is an abstract base class, but it has no abstract methods")
|
||||
}
|
||||
CheckKind::DuplicateTryBlockException(name) => {
|
||||
format!("try-except block with duplicate exception `{name}`")
|
||||
}
|
||||
CheckKind::StarArgUnpackingAfterKeywordArg => {
|
||||
"Star-arg unpacking after a keyword argument is strongly discouraged, because it \
|
||||
only works when the keyword parameter is declared after all parameters supplied \
|
||||
by the unpacked sequence, and this change of ordering can surprise and mislead \
|
||||
readers."
|
||||
"Star-arg unpacking after a keyword argument is strongly discouraged. It only \
|
||||
works when the keyword parameter is declared after all parameters supplied by the \
|
||||
unpacked sequence, and this change of ordering can surprise and mislead readers."
|
||||
.to_string()
|
||||
}
|
||||
CheckKind::EmptyMethodWithoutAbstractDecorator(name) => {
|
||||
format!(
|
||||
"`{name}` is an empty method in an abstract base class, but has no abstract \
|
||||
decorator"
|
||||
)
|
||||
}
|
||||
// flake8-comprehensions
|
||||
CheckKind::UnnecessaryGeneratorList => {
|
||||
"Unnecessary generator (rewrite as a `list` comprehension)".to_string()
|
||||
@@ -1464,6 +1638,38 @@ impl CheckKind {
|
||||
CheckKind::DynamicallyTypedExpression(name) => {
|
||||
format!("Dynamically typed expressions (typing.Any) are disallowed in `{name}`")
|
||||
}
|
||||
// flake8-2020
|
||||
CheckKind::SysVersionSlice3Referenced => {
|
||||
"`sys.version[:3]` referenced (python3.10), use `sys.version_info`".to_string()
|
||||
}
|
||||
CheckKind::SysVersion2Referenced => {
|
||||
"`sys.version[2]` referenced (python3.10), use `sys.version_info`".to_string()
|
||||
}
|
||||
CheckKind::SysVersionCmpStr3 => {
|
||||
"`sys.version` compared to string (python3.10), use `sys.version_info`".to_string()
|
||||
}
|
||||
CheckKind::SysVersionInfo0Eq3Referenced => {
|
||||
"`sys.version_info[0] == 3` referenced (python4), use `>=`".to_string()
|
||||
}
|
||||
CheckKind::SixPY3Referenced => {
|
||||
"`six.PY3` referenced (python4), use `not six.PY2`".to_string()
|
||||
}
|
||||
CheckKind::SysVersionInfo1CmpInt => "`sys.version_info[1]` compared to integer \
|
||||
(python4), compare `sys.version_info` to tuple"
|
||||
.to_string(),
|
||||
CheckKind::SysVersionInfoMinorCmpInt => "`sys.version_info.minor` compared to integer \
|
||||
(python4), compare `sys.version_info` to \
|
||||
tuple"
|
||||
.to_string(),
|
||||
CheckKind::SysVersion0Referenced => {
|
||||
"`sys.version[0]` referenced (python10), use `sys.version_info`".to_string()
|
||||
}
|
||||
CheckKind::SysVersionCmpStr10 => {
|
||||
"`sys.version` compared to string (python10), use `sys.version_info`".to_string()
|
||||
}
|
||||
CheckKind::SysVersionSlice1Referenced => {
|
||||
"`sys.version[:1]` referenced (python10), use `sys.version_info`".to_string()
|
||||
}
|
||||
// pyupgrade
|
||||
CheckKind::TypeOfPrimitive(primitive) => {
|
||||
format!("Use `{}` instead of `type(...)`", primitive.builtin())
|
||||
@@ -1489,12 +1695,19 @@ impl CheckKind {
|
||||
CheckKind::SuperCallWithParameters => {
|
||||
"Use `super()` instead of `super(__class__, self)`".to_string()
|
||||
}
|
||||
CheckKind::UnnecessaryFutureImport(name) => {
|
||||
format!("Unnessary __future__ import `{name}` for target Python version")
|
||||
CheckKind::UnnecessaryFutureImport(names) => {
|
||||
if names.len() == 1 {
|
||||
let import = &names[0];
|
||||
format!("Unnecessary `__future__` import `{import}` for target Python version")
|
||||
} else {
|
||||
let imports = names.iter().map(|name| format!("`{name}`")).join(", ");
|
||||
format!("Unnecessary `__future__` imports {imports} for target Python version")
|
||||
}
|
||||
}
|
||||
CheckKind::UnnecessaryLRUCacheParams => {
|
||||
"Unnessary parameters to functools.lru_cache".to_string()
|
||||
"Unnecessary parameters to `functools.lru_cache`".to_string()
|
||||
}
|
||||
CheckKind::UnnecessaryEncodeUTF8 => "Unnecessary call to `encode` as UTF-8".to_string(),
|
||||
// pydocstyle
|
||||
CheckKind::FitsOnOneLine => "One-line docstring should fit on one line".to_string(),
|
||||
CheckKind::BlankLineAfterSummary => {
|
||||
@@ -1548,7 +1761,7 @@ impl CheckKind {
|
||||
CheckKind::PublicNestedClass => "Missing docstring in public nested class".to_string(),
|
||||
CheckKind::PublicInit => "Missing docstring in `__init__`".to_string(),
|
||||
CheckKind::NoThisPrefix => {
|
||||
"First word of the docstring should not be 'This'".to_string()
|
||||
"First word of the docstring should not be \"This\"".to_string()
|
||||
}
|
||||
CheckKind::SkipDocstring => {
|
||||
"Function decorated with `@overload` shouldn't contain a docstring".to_string()
|
||||
@@ -1656,10 +1869,25 @@ impl CheckKind {
|
||||
format!("Exception name `{name}` should be named with an Error suffix")
|
||||
}
|
||||
CheckKind::PEP3120UnnecessaryCodingComment => {
|
||||
"utf-8 encoding declaration is unnecessary".to_string()
|
||||
"UTF-8 encoding declaration is unnecessary".to_string()
|
||||
}
|
||||
// isort
|
||||
CheckKind::UnsortedImports => "Import block is un-sorted or un-formatted".to_string(),
|
||||
// flake8-bandit
|
||||
CheckKind::AssertUsed => "Use of `assert` detected".to_string(),
|
||||
CheckKind::ExecUsed => "Use of `exec` detected".to_string(),
|
||||
CheckKind::HardcodedBindAllInterfaces => {
|
||||
"Possible binding to all interfaces".to_string()
|
||||
}
|
||||
CheckKind::HardcodedPasswordString(string) => {
|
||||
format!("Possible hardcoded password: `\"{string}\"`")
|
||||
}
|
||||
CheckKind::HardcodedPasswordFuncArg(string) => {
|
||||
format!("Possible hardcoded password: `\"{string}\"`")
|
||||
}
|
||||
CheckKind::HardcodedPasswordDefault(string) => {
|
||||
format!("Possible hardcoded password: `\"{string}\"`")
|
||||
}
|
||||
// Ruff
|
||||
CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) => {
|
||||
format!(
|
||||
@@ -1704,16 +1932,16 @@ impl CheckKind {
|
||||
pub fn summary(&self) -> String {
|
||||
match self {
|
||||
CheckKind::UnaryPrefixIncrement => {
|
||||
"Python does not support the unary prefix increment.".to_string()
|
||||
"Python does not support the unary prefix increment".to_string()
|
||||
}
|
||||
CheckKind::UnusedLoopControlVariable(name) => {
|
||||
format!("Loop control variable `{name}` not used within the loop body.")
|
||||
format!("Loop control variable `{name}` not used within the loop body")
|
||||
}
|
||||
CheckKind::NoAssertRaisesException => {
|
||||
"`assertRaises(Exception):` should be considered evil.".to_string()
|
||||
"`assertRaises(Exception)` should be considered evil".to_string()
|
||||
}
|
||||
CheckKind::StarArgUnpackingAfterKeywordArg => {
|
||||
"Star-arg unpacking after a keyword argument is strongly discouraged.".to_string()
|
||||
"Star-arg unpacking after a keyword argument is strongly discouraged".to_string()
|
||||
}
|
||||
_ => self.body(),
|
||||
}
|
||||
@@ -1734,6 +1962,7 @@ impl CheckKind {
|
||||
| CheckKind::DeprecatedUnittestAlias(_, _)
|
||||
| CheckKind::DoNotAssertFalse
|
||||
| CheckKind::DuplicateHandlerException(_)
|
||||
| CheckKind::GetAttrWithConstant
|
||||
| CheckKind::IsLiteral
|
||||
| CheckKind::NewLineAfterLastParagraph
|
||||
| CheckKind::NewLineAfterSectionName(_)
|
||||
@@ -1760,6 +1989,8 @@ impl CheckKind {
|
||||
| CheckKind::UnnecessaryAbspath
|
||||
| CheckKind::UnnecessaryCollectionCall(_)
|
||||
| CheckKind::UnnecessaryComprehension(_)
|
||||
| CheckKind::UnnecessaryEncodeUTF8
|
||||
| CheckKind::UnnecessaryFutureImport(_)
|
||||
| CheckKind::UnnecessaryGeneratorDict
|
||||
| CheckKind::UnnecessaryGeneratorList
|
||||
| CheckKind::UnnecessaryGeneratorSet
|
||||
|
||||
@@ -45,16 +45,24 @@ pub enum CheckCodePrefix {
|
||||
B008,
|
||||
B009,
|
||||
B01,
|
||||
B010,
|
||||
B011,
|
||||
B012,
|
||||
B013,
|
||||
B014,
|
||||
B015,
|
||||
B016,
|
||||
B017,
|
||||
B018,
|
||||
B019,
|
||||
B02,
|
||||
B020,
|
||||
B021,
|
||||
B022,
|
||||
B024,
|
||||
B025,
|
||||
B026,
|
||||
B027,
|
||||
C,
|
||||
C4,
|
||||
C40,
|
||||
@@ -243,6 +251,15 @@ pub enum CheckCodePrefix {
|
||||
RUF001,
|
||||
RUF002,
|
||||
RUF003,
|
||||
S,
|
||||
S1,
|
||||
S10,
|
||||
S101,
|
||||
S102,
|
||||
S104,
|
||||
S105,
|
||||
S106,
|
||||
S107,
|
||||
T,
|
||||
T2,
|
||||
T20,
|
||||
@@ -263,6 +280,7 @@ pub enum CheckCodePrefix {
|
||||
U01,
|
||||
U010,
|
||||
U011,
|
||||
U012,
|
||||
W,
|
||||
W2,
|
||||
W29,
|
||||
@@ -270,6 +288,23 @@ pub enum CheckCodePrefix {
|
||||
W6,
|
||||
W60,
|
||||
W605,
|
||||
YTT,
|
||||
YTT1,
|
||||
YTT10,
|
||||
YTT101,
|
||||
YTT102,
|
||||
YTT103,
|
||||
YTT2,
|
||||
YTT20,
|
||||
YTT201,
|
||||
YTT202,
|
||||
YTT203,
|
||||
YTT204,
|
||||
YTT3,
|
||||
YTT30,
|
||||
YTT301,
|
||||
YTT302,
|
||||
YTT303,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -342,15 +377,23 @@ impl CheckCodePrefix {
|
||||
CheckCode::B007,
|
||||
CheckCode::B008,
|
||||
CheckCode::B009,
|
||||
CheckCode::B010,
|
||||
CheckCode::B011,
|
||||
CheckCode::B012,
|
||||
CheckCode::B013,
|
||||
CheckCode::B014,
|
||||
CheckCode::B015,
|
||||
CheckCode::B016,
|
||||
CheckCode::B017,
|
||||
CheckCode::B018,
|
||||
CheckCode::B019,
|
||||
CheckCode::B020,
|
||||
CheckCode::B021,
|
||||
CheckCode::B022,
|
||||
CheckCode::B024,
|
||||
CheckCode::B025,
|
||||
CheckCode::B026,
|
||||
CheckCode::B027,
|
||||
],
|
||||
CheckCodePrefix::B0 => vec![
|
||||
CheckCode::B002,
|
||||
@@ -361,15 +404,23 @@ impl CheckCodePrefix {
|
||||
CheckCode::B007,
|
||||
CheckCode::B008,
|
||||
CheckCode::B009,
|
||||
CheckCode::B010,
|
||||
CheckCode::B011,
|
||||
CheckCode::B012,
|
||||
CheckCode::B013,
|
||||
CheckCode::B014,
|
||||
CheckCode::B015,
|
||||
CheckCode::B016,
|
||||
CheckCode::B017,
|
||||
CheckCode::B018,
|
||||
CheckCode::B019,
|
||||
CheckCode::B020,
|
||||
CheckCode::B021,
|
||||
CheckCode::B022,
|
||||
CheckCode::B024,
|
||||
CheckCode::B025,
|
||||
CheckCode::B026,
|
||||
CheckCode::B027,
|
||||
],
|
||||
CheckCodePrefix::B00 => vec![
|
||||
CheckCode::B002,
|
||||
@@ -390,24 +441,43 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::B008 => vec![CheckCode::B008],
|
||||
CheckCodePrefix::B009 => vec![CheckCode::B009],
|
||||
CheckCodePrefix::B01 => vec![
|
||||
CheckCode::B010,
|
||||
CheckCode::B011,
|
||||
CheckCode::B012,
|
||||
CheckCode::B013,
|
||||
CheckCode::B014,
|
||||
CheckCode::B015,
|
||||
CheckCode::B016,
|
||||
CheckCode::B017,
|
||||
CheckCode::B018,
|
||||
CheckCode::B019,
|
||||
],
|
||||
CheckCodePrefix::B010 => vec![CheckCode::B010],
|
||||
CheckCodePrefix::B011 => vec![CheckCode::B011],
|
||||
CheckCodePrefix::B012 => vec![CheckCode::B012],
|
||||
CheckCodePrefix::B013 => vec![CheckCode::B013],
|
||||
CheckCodePrefix::B014 => vec![CheckCode::B014],
|
||||
CheckCodePrefix::B015 => vec![CheckCode::B015],
|
||||
CheckCodePrefix::B016 => vec![CheckCode::B016],
|
||||
CheckCodePrefix::B017 => vec![CheckCode::B017],
|
||||
CheckCodePrefix::B018 => vec![CheckCode::B018],
|
||||
CheckCodePrefix::B02 => vec![CheckCode::B025, CheckCode::B026],
|
||||
CheckCodePrefix::B019 => vec![CheckCode::B019],
|
||||
CheckCodePrefix::B02 => vec![
|
||||
CheckCode::B020,
|
||||
CheckCode::B021,
|
||||
CheckCode::B022,
|
||||
CheckCode::B024,
|
||||
CheckCode::B025,
|
||||
CheckCode::B026,
|
||||
CheckCode::B027,
|
||||
],
|
||||
CheckCodePrefix::B020 => vec![CheckCode::B020],
|
||||
CheckCodePrefix::B021 => vec![CheckCode::B021],
|
||||
CheckCodePrefix::B022 => vec![CheckCode::B022],
|
||||
CheckCodePrefix::B024 => vec![CheckCode::B024],
|
||||
CheckCodePrefix::B025 => vec![CheckCode::B025],
|
||||
CheckCodePrefix::B026 => vec![CheckCode::B026],
|
||||
CheckCodePrefix::B027 => vec![CheckCode::B027],
|
||||
CheckCodePrefix::C => vec![
|
||||
CheckCode::C400,
|
||||
CheckCode::C401,
|
||||
@@ -960,6 +1030,36 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::RUF001 => vec![CheckCode::RUF001],
|
||||
CheckCodePrefix::RUF002 => vec![CheckCode::RUF002],
|
||||
CheckCodePrefix::RUF003 => vec![CheckCode::RUF003],
|
||||
CheckCodePrefix::S => vec![
|
||||
CheckCode::S101,
|
||||
CheckCode::S102,
|
||||
CheckCode::S104,
|
||||
CheckCode::S105,
|
||||
CheckCode::S106,
|
||||
CheckCode::S107,
|
||||
],
|
||||
CheckCodePrefix::S1 => vec![
|
||||
CheckCode::S101,
|
||||
CheckCode::S102,
|
||||
CheckCode::S104,
|
||||
CheckCode::S105,
|
||||
CheckCode::S106,
|
||||
CheckCode::S107,
|
||||
],
|
||||
CheckCodePrefix::S10 => vec![
|
||||
CheckCode::S101,
|
||||
CheckCode::S102,
|
||||
CheckCode::S104,
|
||||
CheckCode::S105,
|
||||
CheckCode::S106,
|
||||
CheckCode::S107,
|
||||
],
|
||||
CheckCodePrefix::S101 => vec![CheckCode::S101],
|
||||
CheckCodePrefix::S102 => vec![CheckCode::S102],
|
||||
CheckCodePrefix::S104 => vec![CheckCode::S104],
|
||||
CheckCodePrefix::S105 => vec![CheckCode::S105],
|
||||
CheckCodePrefix::S106 => vec![CheckCode::S106],
|
||||
CheckCodePrefix::S107 => vec![CheckCode::S107],
|
||||
CheckCodePrefix::T => vec![CheckCode::T201, CheckCode::T203],
|
||||
CheckCodePrefix::T2 => vec![CheckCode::T201, CheckCode::T203],
|
||||
CheckCodePrefix::T20 => vec![CheckCode::T201, CheckCode::T203],
|
||||
@@ -977,6 +1077,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::U009,
|
||||
CheckCode::U010,
|
||||
CheckCode::U011,
|
||||
CheckCode::U012,
|
||||
],
|
||||
CheckCodePrefix::U0 => vec![
|
||||
CheckCode::U001,
|
||||
@@ -990,6 +1091,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::U009,
|
||||
CheckCode::U010,
|
||||
CheckCode::U011,
|
||||
CheckCode::U012,
|
||||
],
|
||||
CheckCodePrefix::U00 => vec![
|
||||
CheckCode::U001,
|
||||
@@ -1011,9 +1113,10 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::U007 => vec![CheckCode::U007],
|
||||
CheckCodePrefix::U008 => vec![CheckCode::U008],
|
||||
CheckCodePrefix::U009 => vec![CheckCode::U009],
|
||||
CheckCodePrefix::U01 => vec![CheckCode::U010, CheckCode::U011],
|
||||
CheckCodePrefix::U01 => vec![CheckCode::U010, CheckCode::U011, CheckCode::U012],
|
||||
CheckCodePrefix::U010 => vec![CheckCode::U010],
|
||||
CheckCodePrefix::U011 => vec![CheckCode::U011],
|
||||
CheckCodePrefix::U012 => vec![CheckCode::U012],
|
||||
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
|
||||
CheckCodePrefix::W2 => vec![CheckCode::W292],
|
||||
CheckCodePrefix::W29 => vec![CheckCode::W292],
|
||||
@@ -1021,6 +1124,44 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::W6 => vec![CheckCode::W605],
|
||||
CheckCodePrefix::W60 => vec![CheckCode::W605],
|
||||
CheckCodePrefix::W605 => vec![CheckCode::W605],
|
||||
CheckCodePrefix::YTT => vec![
|
||||
CheckCode::YTT101,
|
||||
CheckCode::YTT102,
|
||||
CheckCode::YTT103,
|
||||
CheckCode::YTT201,
|
||||
CheckCode::YTT202,
|
||||
CheckCode::YTT203,
|
||||
CheckCode::YTT204,
|
||||
CheckCode::YTT301,
|
||||
CheckCode::YTT302,
|
||||
CheckCode::YTT303,
|
||||
],
|
||||
CheckCodePrefix::YTT1 => vec![CheckCode::YTT101, CheckCode::YTT102, CheckCode::YTT103],
|
||||
CheckCodePrefix::YTT10 => vec![CheckCode::YTT101, CheckCode::YTT102, CheckCode::YTT103],
|
||||
CheckCodePrefix::YTT101 => vec![CheckCode::YTT101],
|
||||
CheckCodePrefix::YTT102 => vec![CheckCode::YTT102],
|
||||
CheckCodePrefix::YTT103 => vec![CheckCode::YTT103],
|
||||
CheckCodePrefix::YTT2 => vec![
|
||||
CheckCode::YTT201,
|
||||
CheckCode::YTT202,
|
||||
CheckCode::YTT203,
|
||||
CheckCode::YTT204,
|
||||
],
|
||||
CheckCodePrefix::YTT20 => vec![
|
||||
CheckCode::YTT201,
|
||||
CheckCode::YTT202,
|
||||
CheckCode::YTT203,
|
||||
CheckCode::YTT204,
|
||||
],
|
||||
CheckCodePrefix::YTT201 => vec![CheckCode::YTT201],
|
||||
CheckCodePrefix::YTT202 => vec![CheckCode::YTT202],
|
||||
CheckCodePrefix::YTT203 => vec![CheckCode::YTT203],
|
||||
CheckCodePrefix::YTT204 => vec![CheckCode::YTT204],
|
||||
CheckCodePrefix::YTT3 => vec![CheckCode::YTT301, CheckCode::YTT302, CheckCode::YTT303],
|
||||
CheckCodePrefix::YTT30 => vec![CheckCode::YTT301, CheckCode::YTT302, CheckCode::YTT303],
|
||||
CheckCodePrefix::YTT301 => vec![CheckCode::YTT301],
|
||||
CheckCodePrefix::YTT302 => vec![CheckCode::YTT302],
|
||||
CheckCodePrefix::YTT303 => vec![CheckCode::YTT303],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1066,16 +1207,24 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::B008 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B009 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B01 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::B010 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B011 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B012 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B013 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B014 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B015 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B016 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B017 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B018 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B019 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B02 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::B020 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B021 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B022 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B024 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B025 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B026 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B027 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::C => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::C4 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::C40 => PrefixSpecificity::Tens,
|
||||
@@ -1264,6 +1413,15 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::RUF001 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::RUF002 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::RUF003 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::S1 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::S10 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::S101 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S102 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S104 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S105 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S106 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S107 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::T => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::T2 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::T20 => PrefixSpecificity::Tens,
|
||||
@@ -1284,6 +1442,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::U01 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::U010 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::U011 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::U012 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::W => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::W2 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::W29 => PrefixSpecificity::Tens,
|
||||
@@ -1291,6 +1450,23 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::W6 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::W60 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::W605 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::YTT => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::YTT1 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::YTT10 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::YTT101 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::YTT102 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::YTT103 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::YTT2 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::YTT20 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::YTT201 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::YTT202 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::YTT203 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::YTT204 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::YTT3 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::YTT30 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::YTT301 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::YTT302 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::YTT303 => PrefixSpecificity::Explicit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
14
src/cli.rs
14
src/cli.rs
@@ -1,8 +1,8 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{command, Parser};
|
||||
use fnv::FnvHashMap;
|
||||
use log::warn;
|
||||
use regex::Regex;
|
||||
|
||||
@@ -87,6 +87,10 @@ pub struct Cli {
|
||||
/// The minimum Python version that should be supported.
|
||||
#[arg(long)]
|
||||
pub target_version: Option<PythonVersion>,
|
||||
/// Set the line-length for length-associated checks and automatic
|
||||
/// formatting.
|
||||
#[arg(long)]
|
||||
pub line_length: Option<usize>,
|
||||
/// Round-trip auto-formatting.
|
||||
// TODO(charlie): This should be a sub-command.
|
||||
#[arg(long, hide = true)]
|
||||
@@ -120,6 +124,8 @@ 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
|
||||
}
|
||||
@@ -146,7 +152,7 @@ pub fn warn_on(
|
||||
cli_ignore: &[CheckCodePrefix],
|
||||
cli_extend_ignore: &[CheckCodePrefix],
|
||||
pyproject_configuration: &Configuration,
|
||||
pyproject_path: &Option<PathBuf>,
|
||||
pyproject_path: Option<&PathBuf>,
|
||||
) {
|
||||
for code in codes {
|
||||
if !cli_ignore.is_empty() {
|
||||
@@ -186,9 +192,9 @@ pub fn warn_on(
|
||||
/// Convert a list of `PatternPrefixPair` structs to `PerFileIgnore`.
|
||||
pub fn collect_per_file_ignores(
|
||||
pairs: Vec<PatternPrefixPair>,
|
||||
project_root: &Option<PathBuf>,
|
||||
project_root: Option<&PathBuf>,
|
||||
) -> Vec<PerFileIgnore> {
|
||||
let mut per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>> = BTreeMap::new();
|
||||
let mut per_file_ignores: FnvHashMap<String, Vec<CheckCodePrefix>> = FnvHashMap::default();
|
||||
for pair in pairs {
|
||||
per_file_ignores
|
||||
.entry(pair.pattern)
|
||||
|
||||
206
src/directives.rs
Normal file
206
src/directives.rs
Normal file
@@ -0,0 +1,206 @@
|
||||
//! Extract `# noqa` and `# isort: skip` directives from tokenized source.
|
||||
|
||||
use bitflags::bitflags;
|
||||
use nohash_hasher::{IntMap, IntSet};
|
||||
use rustpython_ast::Location;
|
||||
use rustpython_parser::lexer::{LexResult, Tok};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::LintSource;
|
||||
use crate::{Settings, SourceCodeLocator};
|
||||
|
||||
bitflags! {
|
||||
pub struct Flags: u32 {
|
||||
const NOQA = 0b00000001;
|
||||
const ISORT = 0b00000010;
|
||||
}
|
||||
}
|
||||
|
||||
impl Flags {
|
||||
pub fn from_settings(settings: &Settings) -> Self {
|
||||
if settings
|
||||
.enabled
|
||||
.iter()
|
||||
.any(|check_code| matches!(check_code.lint_source(), LintSource::Imports))
|
||||
{
|
||||
Flags::NOQA | Flags::ISORT
|
||||
} else {
|
||||
Flags::NOQA
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Directives {
|
||||
pub noqa_line_for: IntMap<usize, usize>,
|
||||
pub isort_exclusions: IntSet<usize>,
|
||||
}
|
||||
|
||||
pub fn extract_directives(
|
||||
lxr: &[LexResult],
|
||||
locator: &SourceCodeLocator,
|
||||
flags: &Flags,
|
||||
) -> Directives {
|
||||
Directives {
|
||||
noqa_line_for: if flags.contains(Flags::NOQA) {
|
||||
extract_noqa_line_for(lxr)
|
||||
} else {
|
||||
Default::default()
|
||||
},
|
||||
isort_exclusions: if flags.contains(Flags::ISORT) {
|
||||
extract_isort_exclusions(lxr, locator)
|
||||
} else {
|
||||
Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract a mapping from logical line to noqa line.
|
||||
pub fn extract_noqa_line_for(lxr: &[LexResult]) -> IntMap<usize, usize> {
|
||||
let mut noqa_line_for: IntMap<usize, usize> = IntMap::default();
|
||||
for (start, tok, end) in lxr.iter().flatten() {
|
||||
if matches!(tok, Tok::EndOfFile) {
|
||||
break;
|
||||
}
|
||||
// For multi-line strings, we expect `noqa` directives on the last line of the
|
||||
// string.
|
||||
if matches!(tok, Tok::String { .. }) && end.row() > start.row() {
|
||||
for i in start.row()..end.row() {
|
||||
noqa_line_for.insert(i, end.row());
|
||||
}
|
||||
}
|
||||
}
|
||||
noqa_line_for
|
||||
}
|
||||
|
||||
/// Extract a set of lines over which to disable isort.
|
||||
pub fn extract_isort_exclusions(lxr: &[LexResult], locator: &SourceCodeLocator) -> IntSet<usize> {
|
||||
let mut exclusions: IntSet<usize> = IntSet::default();
|
||||
let mut off: Option<&Location> = None;
|
||||
for (start, tok, end) in lxr.iter().flatten() {
|
||||
// TODO(charlie): Modify RustPython to include the comment text in the token.
|
||||
if matches!(tok, Tok::Comment) {
|
||||
let comment_text = locator.slice_source_code_range(&Range {
|
||||
location: *start,
|
||||
end_location: *end,
|
||||
});
|
||||
if off.is_some() {
|
||||
if comment_text == "# isort: on" {
|
||||
if let Some(start) = off {
|
||||
for row in start.row() + 1..=end.row() {
|
||||
exclusions.insert(row);
|
||||
}
|
||||
}
|
||||
off = None;
|
||||
}
|
||||
} else {
|
||||
if comment_text.contains("isort: skip") || comment_text.contains("isort:skip") {
|
||||
exclusions.insert(start.row());
|
||||
} else if comment_text == "# isort: off" {
|
||||
off = Some(start);
|
||||
}
|
||||
}
|
||||
} else if matches!(tok, Tok::EndOfFile) {
|
||||
if let Some(start) = off {
|
||||
for row in start.row() + 1..=end.row() {
|
||||
exclusions.insert(row);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
exclusions
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use nohash_hasher::IntMap;
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use crate::directives::extract_noqa_line_for;
|
||||
|
||||
#[test]
|
||||
fn extraction() -> Result<()> {
|
||||
let empty: IntMap<usize, usize> = Default::default();
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"
|
||||
x = 1
|
||||
y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = 2
|
||||
z = x + 1
|
||||
",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
|
||||
y = 2
|
||||
z = x + 1
|
||||
",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(extract_noqa_line_for(&lxr), empty);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = '''abc
|
||||
def
|
||||
ghi
|
||||
'''
|
||||
y = 2
|
||||
z = x + 1",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(
|
||||
extract_noqa_line_for(&lxr),
|
||||
IntMap::from_iter([(1, 4), (2, 4), (3, 4)])
|
||||
);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = '''abc
|
||||
def
|
||||
ghi
|
||||
'''
|
||||
z = 2",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(
|
||||
extract_noqa_line_for(&lxr),
|
||||
IntMap::from_iter([(2, 5), (3, 5), (4, 5)])
|
||||
);
|
||||
|
||||
let lxr: Vec<LexResult> = lexer::make_tokenizer(
|
||||
"x = 1
|
||||
y = '''abc
|
||||
def
|
||||
ghi
|
||||
'''",
|
||||
)
|
||||
.collect();
|
||||
assert_eq!(
|
||||
extract_noqa_line_for(&lxr),
|
||||
IntMap::from_iter([(2, 5), (3, 5), (4, 5)])
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
//! Abstractions for Google-style docstrings.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use fnv::FnvHashSet;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub(crate) static GOOGLE_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
BTreeSet::from([
|
||||
pub(crate) static GOOGLE_SECTION_NAMES: Lazy<FnvHashSet<&'static str>> = Lazy::new(|| {
|
||||
FnvHashSet::from_iter([
|
||||
"Args",
|
||||
"Arguments",
|
||||
"Attention",
|
||||
@@ -37,35 +36,36 @@ pub(crate) static GOOGLE_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new
|
||||
])
|
||||
});
|
||||
|
||||
pub(crate) static LOWERCASE_GOOGLE_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
BTreeSet::from([
|
||||
"args",
|
||||
"arguments",
|
||||
"attention",
|
||||
"attributes",
|
||||
"caution",
|
||||
"danger",
|
||||
"error",
|
||||
"example",
|
||||
"examples",
|
||||
"hint",
|
||||
"important",
|
||||
"keyword args",
|
||||
"keyword arguments",
|
||||
"methods",
|
||||
"note",
|
||||
"notes",
|
||||
"return",
|
||||
"returns",
|
||||
"raises",
|
||||
"references",
|
||||
"see also",
|
||||
"tip",
|
||||
"todo",
|
||||
"warning",
|
||||
"warnings",
|
||||
"warns",
|
||||
"yield",
|
||||
"yields",
|
||||
])
|
||||
});
|
||||
pub(crate) static LOWERCASE_GOOGLE_SECTION_NAMES: Lazy<FnvHashSet<&'static str>> =
|
||||
Lazy::new(|| {
|
||||
FnvHashSet::from_iter([
|
||||
"args",
|
||||
"arguments",
|
||||
"attention",
|
||||
"attributes",
|
||||
"caution",
|
||||
"danger",
|
||||
"error",
|
||||
"example",
|
||||
"examples",
|
||||
"hint",
|
||||
"important",
|
||||
"keyword args",
|
||||
"keyword arguments",
|
||||
"methods",
|
||||
"note",
|
||||
"notes",
|
||||
"return",
|
||||
"returns",
|
||||
"raises",
|
||||
"references",
|
||||
"see also",
|
||||
"tip",
|
||||
"todo",
|
||||
"warning",
|
||||
"warnings",
|
||||
"warns",
|
||||
"yield",
|
||||
"yields",
|
||||
])
|
||||
});
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
//! Abstractions for NumPy-style docstrings.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use fnv::FnvHashSet;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub(crate) static LOWERCASE_NUMPY_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
BTreeSet::from([
|
||||
pub(crate) static LOWERCASE_NUMPY_SECTION_NAMES: Lazy<FnvHashSet<&'static str>> = Lazy::new(|| {
|
||||
FnvHashSet::from_iter([
|
||||
"short summary",
|
||||
"extended summary",
|
||||
"parameters",
|
||||
@@ -22,8 +21,8 @@ pub(crate) static LOWERCASE_NUMPY_SECTION_NAMES: Lazy<BTreeSet<&'static str>> =
|
||||
])
|
||||
});
|
||||
|
||||
pub(crate) static NUMPY_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
BTreeSet::from([
|
||||
pub(crate) static NUMPY_SECTION_NAMES: Lazy<FnvHashSet<&'static str>> = Lazy::new(|| {
|
||||
FnvHashSet::from_iter([
|
||||
"Short Summary",
|
||||
"Extended Summary",
|
||||
"Parameters",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use fnv::FnvHashSet;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::docstrings::google::{GOOGLE_SECTION_NAMES, LOWERCASE_GOOGLE_SECTION_NAMES};
|
||||
@@ -11,14 +10,14 @@ pub(crate) enum SectionStyle {
|
||||
}
|
||||
|
||||
impl SectionStyle {
|
||||
pub(crate) fn section_names(&self) -> &Lazy<BTreeSet<&'static str>> {
|
||||
pub(crate) fn section_names(&self) -> &Lazy<FnvHashSet<&'static str>> {
|
||||
match self {
|
||||
SectionStyle::NumPy => &NUMPY_SECTION_NAMES,
|
||||
SectionStyle::Google => &GOOGLE_SECTION_NAMES,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lowercase_section_names(&self) -> &Lazy<BTreeSet<&'static str>> {
|
||||
pub(crate) fn lowercase_section_names(&self) -> &Lazy<FnvHashSet<&'static str>> {
|
||||
match self {
|
||||
SectionStyle::NumPy => &LOWERCASE_NUMPY_SECTION_NAMES,
|
||||
SectionStyle::Google => &LOWERCASE_GOOGLE_SECTION_NAMES,
|
||||
|
||||
1
src/flake8_2020/mod.rs
Normal file
1
src/flake8_2020/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod plugins;
|
||||
202
src/flake8_2020/plugins.rs
Normal file
202
src/flake8_2020/plugins.rs
Normal file
@@ -0,0 +1,202 @@
|
||||
use num_bigint::BigInt;
|
||||
use rustpython_ast::{Cmpop, Constant, Expr, ExprKind, Located};
|
||||
|
||||
use crate::ast::helpers::match_module_member;
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
|
||||
fn is_sys(checker: &Checker, expr: &Expr, target: &str) -> bool {
|
||||
match_module_member(
|
||||
expr,
|
||||
"sys",
|
||||
target,
|
||||
&checker.from_imports,
|
||||
&checker.import_aliases,
|
||||
)
|
||||
}
|
||||
|
||||
/// YTT101, YTT102, YTT301, YTT303
|
||||
pub fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) {
|
||||
if is_sys(checker, value, "version") {
|
||||
match &slice.node {
|
||||
ExprKind::Slice {
|
||||
lower: None,
|
||||
upper: Some(upper),
|
||||
step: None,
|
||||
..
|
||||
} => {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Int(i),
|
||||
..
|
||||
} = &upper.node
|
||||
{
|
||||
if *i == BigInt::from(1)
|
||||
&& checker.settings.enabled.contains(&CheckCode::YTT303)
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::SysVersionSlice1Referenced,
|
||||
Range::from_located(value),
|
||||
));
|
||||
} else if *i == BigInt::from(3)
|
||||
&& checker.settings.enabled.contains(&CheckCode::YTT101)
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::SysVersionSlice3Referenced,
|
||||
Range::from_located(value),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::Constant {
|
||||
value: Constant::Int(i),
|
||||
..
|
||||
} => {
|
||||
if *i == BigInt::from(2) && checker.settings.enabled.contains(&CheckCode::YTT102) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::SysVersion2Referenced,
|
||||
Range::from_located(value),
|
||||
));
|
||||
} else if *i == BigInt::from(0)
|
||||
&& checker.settings.enabled.contains(&CheckCode::YTT301)
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::SysVersion0Referenced,
|
||||
Range::from_located(value),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// YTT103, YTT201, YTT203, YTT204, YTT302
|
||||
pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &[Expr]) {
|
||||
match &left.node {
|
||||
ExprKind::Subscript { value, slice, .. } if is_sys(checker, value, "version_info") => {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Int(i),
|
||||
..
|
||||
} = &slice.node
|
||||
{
|
||||
if *i == BigInt::from(0) {
|
||||
if let (
|
||||
[Cmpop::Eq | Cmpop::NotEq],
|
||||
[Located {
|
||||
node:
|
||||
ExprKind::Constant {
|
||||
value: Constant::Int(n),
|
||||
..
|
||||
},
|
||||
..
|
||||
}],
|
||||
) = (ops, comparators)
|
||||
{
|
||||
if *n == BigInt::from(3)
|
||||
&& checker.settings.enabled.contains(&CheckCode::YTT201)
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::SysVersionInfo0Eq3Referenced,
|
||||
Range::from_located(left),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else if *i == BigInt::from(1) {
|
||||
if let (
|
||||
[Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE],
|
||||
[Located {
|
||||
node:
|
||||
ExprKind::Constant {
|
||||
value: Constant::Int(_),
|
||||
..
|
||||
},
|
||||
..
|
||||
}],
|
||||
) = (ops, comparators)
|
||||
{
|
||||
if checker.settings.enabled.contains(&CheckCode::YTT203) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::SysVersionInfo1CmpInt,
|
||||
Range::from_located(left),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::Attribute { value, attr, .. }
|
||||
if is_sys(checker, value, "version_info") && attr == "minor" =>
|
||||
{
|
||||
if let (
|
||||
[Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE],
|
||||
[Located {
|
||||
node:
|
||||
ExprKind::Constant {
|
||||
value: Constant::Int(_),
|
||||
..
|
||||
},
|
||||
..
|
||||
}],
|
||||
) = (ops, comparators)
|
||||
{
|
||||
if checker.settings.enabled.contains(&CheckCode::YTT204) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::SysVersionInfoMinorCmpInt,
|
||||
Range::from_located(left),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if is_sys(checker, left, "version") {
|
||||
if let (
|
||||
[Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE],
|
||||
[Located {
|
||||
node:
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(s),
|
||||
..
|
||||
},
|
||||
..
|
||||
}],
|
||||
) = (ops, comparators)
|
||||
{
|
||||
if s.len() == 1 {
|
||||
if checker.settings.enabled.contains(&CheckCode::YTT302) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::SysVersionCmpStr10,
|
||||
Range::from_located(left),
|
||||
));
|
||||
}
|
||||
} else if checker.settings.enabled.contains(&CheckCode::YTT103) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::SysVersionCmpStr3,
|
||||
Range::from_located(left),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// YTT202
|
||||
pub fn name_or_attribute(checker: &mut Checker, expr: &Expr) {
|
||||
if match_module_member(
|
||||
expr,
|
||||
"six",
|
||||
"PY3",
|
||||
&checker.from_imports,
|
||||
&checker.import_aliases,
|
||||
) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::SixPY3Referenced,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use rustpython_ast::{Arguments, Constant, Expr, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
@@ -11,7 +13,7 @@ use crate::{visibility, Check};
|
||||
|
||||
#[derive(Default)]
|
||||
struct ReturnStatementVisitor<'a> {
|
||||
returns: Vec<&'a Option<Box<Expr>>>,
|
||||
returns: Vec<Option<&'a Expr>>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Visitor<'b> for ReturnStatementVisitor<'a>
|
||||
@@ -23,7 +25,9 @@ where
|
||||
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
|
||||
// No recurse.
|
||||
}
|
||||
StmtKind::Return { value } => self.returns.push(value),
|
||||
StmtKind::Return { value } => {
|
||||
self.returns.push(value.as_ref().map(|expr| expr.deref()))
|
||||
}
|
||||
_ => visitor::walk_stmt(self, stmt),
|
||||
}
|
||||
}
|
||||
@@ -49,10 +53,13 @@ fn is_none_returning(body: &[Stmt]) -> bool {
|
||||
}
|
||||
|
||||
/// ANN401
|
||||
fn check_dynamically_typed(checker: &mut Checker, annotation: &Expr, name: &str) {
|
||||
if checker.match_typing_module(annotation, "Any") {
|
||||
fn check_dynamically_typed<F>(checker: &mut Checker, annotation: &Expr, func: F)
|
||||
where
|
||||
F: FnOnce() -> String,
|
||||
{
|
||||
if checker.match_typing_expr(annotation, "Any") {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::DynamicallyTypedExpression(name.to_string()),
|
||||
CheckKind::DynamicallyTypedExpression(func()),
|
||||
Range::from_located(annotation),
|
||||
));
|
||||
};
|
||||
@@ -100,7 +107,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
{
|
||||
if let Some(expr) = &arg.node.annotation {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN401) {
|
||||
check_dynamically_typed(checker, expr, &arg.node.arg);
|
||||
check_dynamically_typed(checker, expr, || arg.node.arg.to_string());
|
||||
};
|
||||
} else {
|
||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||
@@ -122,7 +129,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN401) {
|
||||
let name = arg.node.arg.to_string();
|
||||
check_dynamically_typed(checker, expr, &format!("*{name}"));
|
||||
check_dynamically_typed(checker, expr, || format!("*{name}"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -145,7 +152,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN401) {
|
||||
let name = arg.node.arg.to_string();
|
||||
check_dynamically_typed(checker, expr, &format!("**{name}"));
|
||||
check_dynamically_typed(checker, expr, || format!("**{name}"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -165,7 +172,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
// ANN201, ANN202, ANN401
|
||||
if let Some(expr) = &returns {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN401) {
|
||||
check_dynamically_typed(checker, expr, name);
|
||||
check_dynamically_typed(checker, expr, || name.to_string());
|
||||
};
|
||||
} else {
|
||||
// Allow omission of return annotation in `__init__` functions, if the function
|
||||
@@ -215,7 +222,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
if let Some(annotation) = &arg.node.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN401) {
|
||||
check_dynamically_typed(checker, annotation, &arg.node.arg);
|
||||
check_dynamically_typed(checker, annotation, || arg.node.arg.to_string());
|
||||
}
|
||||
} else {
|
||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||
@@ -238,7 +245,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN401) {
|
||||
let name = arg.node.arg.to_string();
|
||||
check_dynamically_typed(checker, expr, &format!("*{name}"));
|
||||
check_dynamically_typed(checker, expr, || format!("*{name}"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -262,7 +269,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN401) {
|
||||
let name = arg.node.arg.to_string();
|
||||
check_dynamically_typed(checker, expr, &format!("**{name}"));
|
||||
check_dynamically_typed(checker, expr, || format!("**{name}"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -305,7 +312,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
// ANN201, ANN202
|
||||
if let Some(expr) = &returns {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN401) {
|
||||
check_dynamically_typed(checker, expr, name);
|
||||
check_dynamically_typed(checker, expr, || name.to_string());
|
||||
}
|
||||
} else {
|
||||
// Allow omission of return annotation in `__init__` functions, if the function
|
||||
|
||||
22
src/flake8_bandit/helpers.rs
Normal file
22
src/flake8_bandit/helpers.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprKind};
|
||||
|
||||
const PASSWORD_NAMES: [&str; 7] = [
|
||||
"password", "pass", "passwd", "pwd", "secret", "token", "secrete",
|
||||
];
|
||||
|
||||
pub fn string_literal(expr: &Expr) -> Option<&str> {
|
||||
match &expr.node {
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} => Some(string),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Maybe use regex for this?
|
||||
pub fn matches_password_name(string: &str) -> bool {
|
||||
PASSWORD_NAMES
|
||||
.iter()
|
||||
.any(|name| string.to_lowercase().contains(name))
|
||||
}
|
||||
2
src/flake8_bandit/mod.rs
Normal file
2
src/flake8_bandit/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod helpers;
|
||||
pub mod plugins;
|
||||
9
src/flake8_bandit/plugins/assert_used.rs
Normal file
9
src/flake8_bandit/plugins/assert_used.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use rustpython_ast::{Located, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// S101
|
||||
pub fn assert_used(stmt: &Located<StmtKind>) -> Check {
|
||||
Check::new(CheckKind::AssertUsed, Range::from_located(stmt))
|
||||
}
|
||||
14
src/flake8_bandit/plugins/exec_used.rs
Normal file
14
src/flake8_bandit/plugins/exec_used.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// S102
|
||||
pub fn exec_used(expr: &Expr, func: &Expr) -> Option<Check> {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "exec" {
|
||||
return Some(Check::new(CheckKind::ExecUsed, Range::from_located(expr)));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
11
src/flake8_bandit/plugins/hardcoded_bind_all_interfaces.rs
Normal file
11
src/flake8_bandit/plugins/hardcoded_bind_all_interfaces.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// S104
|
||||
pub fn hardcoded_bind_all_interfaces(value: &str, range: &Range) -> Option<Check> {
|
||||
if value == "0.0.0.0" {
|
||||
Some(Check::new(CheckKind::HardcodedBindAllInterfaces, *range))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
51
src/flake8_bandit/plugins/hardcoded_password_default.rs
Normal file
51
src/flake8_bandit/plugins/hardcoded_password_default.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use rustpython_ast::{ArgData, Arguments, Expr, Located};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::flake8_bandit::helpers::{matches_password_name, string_literal};
|
||||
|
||||
fn check_password_kwarg(arg: &Located<ArgData>, default: &Expr) -> Option<Check> {
|
||||
if let Some(string) = string_literal(default) {
|
||||
let kwarg_name = &arg.node.arg;
|
||||
if matches_password_name(kwarg_name) {
|
||||
return Some(Check::new(
|
||||
CheckKind::HardcodedPasswordDefault(string.to_string()),
|
||||
Range::from_located(default),
|
||||
));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// S107
|
||||
pub fn hardcoded_password_default(arguments: &Arguments) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = Vec::new();
|
||||
|
||||
let defaults_start =
|
||||
arguments.posonlyargs.len() + arguments.args.len() - arguments.defaults.len();
|
||||
for (i, arg) in arguments
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(&arguments.args)
|
||||
.enumerate()
|
||||
{
|
||||
if let Some(i) = i.checked_sub(defaults_start) {
|
||||
let default = &arguments.defaults[i];
|
||||
if let Some(check) = check_password_kwarg(arg, default) {
|
||||
checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let defaults_start = arguments.kwonlyargs.len() - arguments.kw_defaults.len();
|
||||
for (i, kwarg) in arguments.kwonlyargs.iter().enumerate() {
|
||||
if let Some(i) = i.checked_sub(defaults_start) {
|
||||
let default = &arguments.kw_defaults[i];
|
||||
if let Some(check) = check_password_kwarg(kwarg, default) {
|
||||
checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checks
|
||||
}
|
||||
25
src/flake8_bandit/plugins/hardcoded_password_func_arg.rs
Normal file
25
src/flake8_bandit/plugins/hardcoded_password_func_arg.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use rustpython_ast::Keyword;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::flake8_bandit::helpers::{matches_password_name, string_literal};
|
||||
|
||||
/// S106
|
||||
pub fn hardcoded_password_func_arg(keywords: &[Keyword]) -> Vec<Check> {
|
||||
keywords
|
||||
.iter()
|
||||
.filter_map(|keyword| {
|
||||
if let Some(string) = string_literal(&keyword.node.value) {
|
||||
if let Some(arg) = &keyword.node.arg {
|
||||
if matches_password_name(arg) {
|
||||
return Some(Check::new(
|
||||
CheckKind::HardcodedPasswordFuncArg(string.to_string()),
|
||||
Range::from_located(keyword),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
58
src/flake8_bandit/plugins/hardcoded_password_string.rs
Normal file
58
src/flake8_bandit/plugins/hardcoded_password_string.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::flake8_bandit::helpers::{matches_password_name, string_literal};
|
||||
|
||||
fn is_password_target(target: &Expr) -> bool {
|
||||
let target_name = match &target.node {
|
||||
// variable = "s3cr3t"
|
||||
ExprKind::Name { id, .. } => id,
|
||||
// d["password"] = "s3cr3t"
|
||||
ExprKind::Subscript { slice, .. } => match &slice.node {
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} => string,
|
||||
_ => return false,
|
||||
},
|
||||
// obj.password = "s3cr3t"
|
||||
ExprKind::Attribute { attr, .. } => attr,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
matches_password_name(target_name)
|
||||
}
|
||||
|
||||
/// S105
|
||||
pub fn compare_to_hardcoded_password_string(left: &Expr, comparators: &[Expr]) -> Vec<Check> {
|
||||
comparators
|
||||
.iter()
|
||||
.filter_map(|comp| {
|
||||
if let Some(string) = string_literal(comp) {
|
||||
if is_password_target(left) {
|
||||
return Some(Check::new(
|
||||
CheckKind::HardcodedPasswordString(string.to_string()),
|
||||
Range::from_located(comp),
|
||||
));
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// S105
|
||||
pub fn assign_hardcoded_password_string(value: &Expr, targets: &Vec<Expr>) -> Option<Check> {
|
||||
if let Some(string) = string_literal(value) {
|
||||
for target in targets {
|
||||
if is_password_target(target) {
|
||||
return Some(Check::new(
|
||||
CheckKind::HardcodedPasswordString(string.to_string()),
|
||||
Range::from_located(value),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
15
src/flake8_bandit/plugins/mod.rs
Normal file
15
src/flake8_bandit/plugins/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
pub use assert_used::assert_used;
|
||||
pub use exec_used::exec_used;
|
||||
pub use hardcoded_bind_all_interfaces::hardcoded_bind_all_interfaces;
|
||||
pub use hardcoded_password_default::hardcoded_password_default;
|
||||
pub use hardcoded_password_func_arg::hardcoded_password_func_arg;
|
||||
pub use hardcoded_password_string::{
|
||||
assign_hardcoded_password_string, compare_to_hardcoded_password_string,
|
||||
};
|
||||
|
||||
mod assert_used;
|
||||
mod exec_used;
|
||||
mod hardcoded_bind_all_interfaces;
|
||||
mod hardcoded_password_default;
|
||||
mod hardcoded_password_func_arg;
|
||||
mod hardcoded_password_string;
|
||||
5
src/flake8_bugbear/constants.rs
Normal file
5
src/flake8_bugbear/constants.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
|
||||
pub static IDENTIFIER_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^[A-Za-z_][A-Za-z0-9_]*$").unwrap());
|
||||
@@ -1 +1,36 @@
|
||||
mod constants;
|
||||
pub mod plugins;
|
||||
pub mod settings;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::{flake8_bugbear, Settings};
|
||||
|
||||
#[test]
|
||||
fn extend_immutable_calls() -> Result<()> {
|
||||
let snapshot = "extend_immutable_calls".to_string();
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/B008_extended.py"),
|
||||
&Settings {
|
||||
flake8_bugbear: flake8_bugbear::settings::Settings {
|
||||
extend_immutable_calls: vec![
|
||||
"fastapi.Depends".to_string(),
|
||||
"fastapi.Query".to_string(),
|
||||
],
|
||||
},
|
||||
..Settings::for_rules(vec![CheckCode::B008])
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
129
src/flake8_bugbear/plugins/abstract_base_class.rs
Normal file
129
src/flake8_bugbear/plugins/abstract_base_class.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
use rustpython_ast::{Constant, Expr, ExprKind, Keyword, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::helpers::match_module_member;
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
|
||||
fn is_abc_class(
|
||||
bases: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
|
||||
import_aliases: &FnvHashMap<&str, &str>,
|
||||
) -> bool {
|
||||
keywords.iter().any(|keyword| {
|
||||
keyword
|
||||
.node
|
||||
.arg
|
||||
.as_ref()
|
||||
.map(|a| a == "metaclass")
|
||||
.unwrap_or(false)
|
||||
&& match_module_member(
|
||||
&keyword.node.value,
|
||||
"abc",
|
||||
"ABCMeta",
|
||||
from_imports,
|
||||
import_aliases,
|
||||
)
|
||||
}) || bases
|
||||
.iter()
|
||||
.any(|base| match_module_member(base, "abc", "ABC", from_imports, import_aliases))
|
||||
}
|
||||
|
||||
fn is_empty_body(body: &[Stmt]) -> bool {
|
||||
body.iter().all(|stmt| match &stmt.node {
|
||||
StmtKind::Pass => true,
|
||||
StmtKind::Expr { value } => match &value.node {
|
||||
ExprKind::Constant { value, .. } => {
|
||||
matches!(value, Constant::Str(..) | Constant::Ellipsis)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_abstractmethod(
|
||||
expr: &Expr,
|
||||
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
|
||||
import_aliases: &FnvHashMap<&str, &str>,
|
||||
) -> bool {
|
||||
match_module_member(expr, "abc", "abstractmethod", from_imports, import_aliases)
|
||||
}
|
||||
|
||||
fn is_overload(
|
||||
expr: &Expr,
|
||||
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
|
||||
import_aliases: &FnvHashMap<&str, &str>,
|
||||
) -> bool {
|
||||
match_module_member(expr, "typing", "overload", from_imports, import_aliases)
|
||||
}
|
||||
|
||||
pub fn abstract_base_class(
|
||||
checker: &mut Checker,
|
||||
stmt: &Stmt,
|
||||
name: &str,
|
||||
bases: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
body: &[Stmt],
|
||||
) {
|
||||
if bases.len() + keywords.len() == 1
|
||||
&& is_abc_class(
|
||||
bases,
|
||||
keywords,
|
||||
&checker.from_imports,
|
||||
&checker.import_aliases,
|
||||
)
|
||||
{
|
||||
let mut has_abstract_method = false;
|
||||
for stmt in body {
|
||||
// https://github.com/PyCQA/flake8-bugbear/issues/293
|
||||
// Ignore abc's that declares a class attribute that must be set
|
||||
if let StmtKind::AnnAssign { .. } | StmtKind::Assign { .. } = &stmt.node {
|
||||
has_abstract_method = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if let StmtKind::FunctionDef {
|
||||
decorator_list,
|
||||
body,
|
||||
..
|
||||
}
|
||||
| StmtKind::AsyncFunctionDef {
|
||||
decorator_list,
|
||||
body,
|
||||
..
|
||||
} = &stmt.node
|
||||
{
|
||||
let has_abstract_decorator = decorator_list
|
||||
.iter()
|
||||
.any(|d| is_abstractmethod(d, &checker.from_imports, &checker.import_aliases));
|
||||
|
||||
has_abstract_method |= has_abstract_decorator;
|
||||
|
||||
if checker.settings.enabled.contains(&CheckCode::B027) {
|
||||
if !has_abstract_decorator
|
||||
&& is_empty_body(body)
|
||||
&& !decorator_list
|
||||
.iter()
|
||||
.any(|d| is_overload(d, &checker.from_imports, &checker.import_aliases))
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::EmptyMethodWithoutAbstractDecorator(name.to_string()),
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if checker.settings.enabled.contains(&CheckCode::B024) {
|
||||
if !has_abstract_method {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::AbstractBaseClassWithoutAbstractMethod(name.to_string()),
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
|
||||
fn assertion_error(msg: &Option<Box<Expr>>) -> Stmt {
|
||||
fn assertion_error(msg: Option<&Expr>) -> Stmt {
|
||||
Stmt::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
@@ -24,7 +24,7 @@ fn assertion_error(msg: &Option<Box<Expr>>) -> Stmt {
|
||||
},
|
||||
)),
|
||||
args: if let Some(msg) = msg {
|
||||
vec![*msg.clone()]
|
||||
vec![msg.clone()]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
@@ -36,7 +36,7 @@ fn assertion_error(msg: &Option<Box<Expr>>) -> Stmt {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: &Option<Box<Expr>>) {
|
||||
pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: Option<&Expr>) {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Bool(false),
|
||||
..
|
||||
|
||||
@@ -10,10 +10,10 @@ pub fn assert_raises_exception(checker: &mut Checker, stmt: &Stmt, items: &[With
|
||||
if let Some(item) = items.first() {
|
||||
let item_context = &item.context_expr;
|
||||
if let ExprKind::Call { func, args, .. } = &item_context.node {
|
||||
if match_name_or_attr(func, "assertRaises")
|
||||
&& args.len() == 1
|
||||
&& match_name_or_attr(args.first().unwrap(), "Exception")
|
||||
if args.len() == 1
|
||||
&& item.optional_vars.is_none()
|
||||
&& match_name_or_attr(func, "assertRaises")
|
||||
&& match_name_or_attr(args.first().unwrap(), "Exception")
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::NoAssertRaisesException,
|
||||
|
||||
41
src/flake8_bugbear/plugins/cached_instance_method.rs
Normal file
41
src/flake8_bugbear/plugins/cached_instance_method.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
|
||||
use crate::ast::helpers::{collect_call_paths, dealias_call_path, match_call_path};
|
||||
use crate::ast::types::{Range, ScopeKind};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
fn is_cache_func(checker: &Checker, expr: &Expr) -> bool {
|
||||
let call_path = dealias_call_path(collect_call_paths(expr), &checker.import_aliases);
|
||||
match_call_path(&call_path, "functools", "lru_cache", &checker.from_imports)
|
||||
|| match_call_path(&call_path, "functools", "cache", &checker.from_imports)
|
||||
}
|
||||
|
||||
/// B019
|
||||
pub fn cached_instance_method(checker: &mut Checker, decorator_list: &[Expr]) {
|
||||
if matches!(checker.current_scope().kind, ScopeKind::Class(_)) {
|
||||
for decorator in decorator_list {
|
||||
// TODO(charlie): This should take into account `classmethod-decorators` and
|
||||
// `staticmethod-decorators`.
|
||||
if let ExprKind::Name { id, .. } = &decorator.node {
|
||||
if id == "classmethod" || id == "staticmethod" {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
for decorator in decorator_list {
|
||||
if is_cache_func(
|
||||
checker,
|
||||
match &decorator.node {
|
||||
ExprKind::Call { func, .. } => func,
|
||||
_ => decorator,
|
||||
},
|
||||
) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::CachedInstanceMethod,
|
||||
Range::from_located(decorator),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user