Compare commits

...

55 Commits

Author SHA1 Message Date
Charlie Marsh
f1d3e3698a Bump version to 0.0.165 2022-12-06 00:03:30 -05:00
Charlie Marsh
080411bc89 Re-create ruff snapshots 2022-12-06 00:03:14 -05:00
Charlie Marsh
f2ad915224 Bump version to 0.0.164 2022-12-05 23:37:22 -05:00
Charlie Marsh
71543eeabc Rename rules mod to ruff (#1104) 2022-12-05 23:35:36 -05:00
Charlie Marsh
44025f1c92 Improve F841's Flake8 parity for unpacking assignments (#1103) 2022-12-05 23:34:40 -05:00
Charlie Marsh
971bf6d232 Run cargo fmt 2022-12-05 23:01:47 -05:00
Charlie Marsh
0acc47386a Use pyproject.toml parent as project root when explicitly provided (#1101) 2022-12-05 23:00:59 -05:00
Reiner Gerecke
982ac6b0ad Auto-generate options in README from field attributes (#1015) 2022-12-05 22:34:40 -05:00
Charlie Marsh
541440f7a8 Re-support F841 detection for single context managers (#1099) 2022-12-05 22:09:45 -05:00
Charlie Marsh
66dde46e03 Track nested imports without column number detection (#1097) 2022-12-05 21:16:44 -05:00
Charlie Marsh
1339e2a002 Bump version to 0.0.163 2022-12-05 20:45:24 -05:00
Charlie Marsh
38ad10f60d Treat nested classes and functions as "standard" siblings (#1095) 2022-12-05 20:45:12 -05:00
Charlie Marsh
88e78c5cde Add missing D415 fixture 2022-12-05 20:42:21 -05:00
Charlie Marsh
436aeed20a Implement autofix for D400 and D415 (#1094) 2022-12-05 20:24:56 -05:00
Charlie Marsh
b94169a8bb Don't autofix D210 by introducing a syntax error (#1093) 2022-12-05 19:13:22 -05:00
Charlie Marsh
995994be3e Bump version to 0.0.162 2022-12-05 19:07:44 -05:00
Charlie Marsh
f001305b2e Only autofix D205 by deleting blank lines (#1091) 2022-12-05 19:01:32 -05:00
Charlie Marsh
e88093541f Avoid wrapping import-star statements (#1089) 2022-12-05 18:39:16 -05:00
Charlie Marsh
da41a495f1 Remove extraneous plugin creation script 2022-12-05 18:31:19 -05:00
Charlie Marsh
55b7ec8f85 Ignore newline enforcement when imports break indentation boundaries (#1085) 2022-12-05 18:02:41 -05:00
Charlie Marsh
4b41ae3f53 Bump version to 0.0.161 2022-12-05 17:02:05 -05:00
Charlie Marsh
f944e1e1cf Add action comments to README.md (#1082) 2022-12-05 16:56:28 -05:00
Charlie Marsh
4fbc1082de Support isort: split directive (#1081) 2022-12-05 16:48:10 -05:00
Charlie Marsh
cf2e887e38 Tweak summary message to include total error counts (#1067) 2022-12-05 16:12:12 -05:00
Charlie Marsh
ee994e8c07 Import compatibility with isort newline-insertion behavior (#1078) 2022-12-05 16:07:07 -05:00
Charlie Marsh
c69c4fd655 Support isort: skip_file directive (#1075) 2022-12-05 15:02:01 -05:00
Charlie Marsh
e01e45ca35 Remove extraneous test file 2022-12-05 14:58:54 -05:00
Charlie Marsh
4be74785fe Support unterminated isort: off directives (#1074) 2022-12-05 14:54:47 -05:00
Charlie Marsh
40b7c64f7d Bump version to 0.0.160 2022-12-05 12:56:38 -05:00
Jonathan Plasse
a76c5d1226 Add allowed-confusable settings (#1059) 2022-12-05 12:53:55 -05:00
Charlie Marsh
5aeddeb825 Include pyproject.toml path in error message (#1068) 2022-12-05 12:04:50 -05:00
Charlie Marsh
5f8294aea4 Preserve star imports when re-formatting import blocks (#1066) 2022-12-05 11:48:38 -05:00
Charlie Marsh
e07d3f6313 Fix clippy 2022-12-05 11:47:42 -05:00
Charlie Marsh
1d1662cb9c Bump version to 0.0.159 2022-12-05 11:22:02 -05:00
Charlie Marsh
55ce7bd0df Migrate invalid_literal_comparisons fix to token-based logic (#1065) 2022-12-05 11:16:59 -05:00
Charlie Marsh
e695f6eb25 Avoid false-positive on PLR1701 for multi-type isinstance calls (#1063) 2022-12-05 10:07:05 -05:00
messense
fb2c457a9b Upgrade to notify 5.0.0 (#1048) 2022-12-05 09:58:42 -05:00
Jeong YunWon
523cf62eda Style fixes (#1049) 2022-12-05 09:57:48 -05:00
Charlie Marsh
7024ad7cc7 Bump version to 0.0.158 2022-12-04 21:22:24 -05:00
Charlie Marsh
871ac511ae Add an option to force one-member-per-line for aliased import-froms (#1047) 2022-12-04 21:22:00 -05:00
Charlie Marsh
0685af8a4f Update RustPython (#1045) 2022-12-04 20:09:28 -05:00
Charlie Marsh
5e9a8fcf53 Bump version to 0.0.157 2022-12-04 14:46:46 -05:00
Charlie Marsh
76439235af Remove unused imports in __init__.py files by default (#1042) 2022-12-04 14:45:54 -05:00
Charlie Marsh
0c9c6a1c1c Remove extraneous test file 2022-12-04 13:59:55 -05:00
Charlie Marsh
46a99243cd Fix D205 autofix by detecting summary line (#1041) 2022-12-04 13:56:51 -05:00
Charlie Marsh
d06dc4c72d Bump version to 0.0.156 2022-12-04 10:22:09 -05:00
Jonathan Plasse
8f6b2fb32b Extend and rename RUF004 to PLR1722 (#1036) 2022-12-04 10:20:12 -05:00
Charlie Marsh
1d61db5b62 Allow import builtins under T100 (#1037) 2022-12-04 10:13:17 -05:00
Charlie Marsh
e15c0c68cb Fix PLW0120 snapshot 2022-12-04 10:10:00 -05:00
Harutaka Kawamura
abb2adc4d8 Implement useless-import-alias (#1025) 2022-12-04 09:48:56 -05:00
Harutaka Kawamura
e070166409 Implement useless-else-on-loop (#1031) 2022-12-04 09:22:04 -05:00
Jonathan Plasse
0ae6890094 Fix Table of Contents (#1030) 2022-12-04 09:14:09 -05:00
Harutaka Kawamura
21cace0973 Fix PLR0402 (#1024) 2022-12-04 09:13:47 -05:00
Jakub Kuczys
4994b72ba2 Fix README header links in isort config section (#1033) 2022-12-04 09:12:25 -05:00
Anders Kaseorg
dbb1a6e44b Remove sloppy match_name_or_attr helper (#1027) 2022-12-04 09:12:03 -05:00
189 changed files with 6380 additions and 2889 deletions

View File

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

410
Cargo.lock generated
View File

@@ -21,9 +21,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "0.7.19"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
@@ -82,9 +82,9 @@ dependencies = [
[[package]]
name = "assert_cmd"
version = "2.0.6"
version = "2.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba45b8163c49ab5f972e59a8a5a03b6d2972619d486e19ec9fe744f7c2753d3c"
checksum = "fa3d466004a8b4cb1bc34044240a2fd29d17607e2e3bd613eb44fd48e8100da3"
dependencies = [
"bstr 1.0.1",
"doc-comment",
@@ -100,9 +100,9 @@ version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"hermit-abi 0.1.19",
"libc",
"winapi 0.3.9",
"winapi",
]
[[package]]
@@ -193,9 +193,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.0.75"
version = "1.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41ca34107f97baef6cfb231b32f36115781856b8f8208e8c580e0bcaea374842"
checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4"
[[package]]
name = "cfg-if"
@@ -230,7 +230,7 @@ dependencies = [
"num-traits",
"time",
"wasm-bindgen",
"winapi 0.3.9",
"winapi",
]
[[package]]
@@ -280,14 +280,14 @@ dependencies = [
[[package]]
name = "clap"
version = "4.0.22"
version = "4.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91b9970d7505127a162fdaa9b96428d28a479ba78c9ec7550a63a5d9863db682"
checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex 0.3.0",
"is-terminal",
"once_cell",
"strsim",
"termcolor",
@@ -299,7 +299,7 @@ version = "4.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7b3c9eae0de7bf8e3f904a5e40612b21fb2e2e566456d177809a48b892d24da"
dependencies = [
"clap 4.0.22",
"clap 4.0.29",
]
[[package]]
@@ -308,7 +308,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4160b4a4f72ef58bd766bad27c09e6ef1cc9d82a22f6a0f55d152985a4a48e31"
dependencies = [
"clap 4.0.22",
"clap 4.0.29",
"clap_complete",
"clap_complete_fig",
]
@@ -319,7 +319,7 @@ version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46b30e010e669cd021e5004f3be26cff6b7c08d2a8a0d65b48d43a8cc0efd6c3"
dependencies = [
"clap 4.0.22",
"clap 4.0.29",
"clap_complete",
]
@@ -364,7 +364,7 @@ dependencies = [
"terminfo",
"thiserror",
"which",
"winapi 0.3.9",
"winapi",
]
[[package]]
@@ -394,7 +394,7 @@ checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
dependencies = [
"atty",
"lazy_static",
"winapi 0.3.9",
"winapi",
]
[[package]]
@@ -419,7 +419,7 @@ dependencies = [
"lazy_static",
"libc",
"terminal_size",
"winapi 0.3.9",
"winapi",
]
[[package]]
@@ -496,9 +496,9 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
version = "0.9.11"
version = "0.9.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348"
checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
@@ -509,9 +509,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.12"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [
"cfg-if 1.0.0",
]
@@ -524,9 +524,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "cxx"
version = "1.0.81"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888"
checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -536,9 +536,9 @@ dependencies = [
[[package]]
name = "cxx-build"
version = "1.0.81"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3"
checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39"
dependencies = [
"cc",
"codespan-reporting",
@@ -551,15 +551,15 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
version = "1.0.81"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f"
checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12"
[[package]]
name = "cxxbridge-macro"
version = "1.0.81"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704"
checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6"
dependencies = [
"proc-macro2",
"quote",
@@ -624,7 +624,7 @@ checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
"libc",
"redox_users",
"winapi 0.3.9",
"winapi",
]
[[package]]
@@ -635,7 +635,7 @@ checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi 0.3.9",
"winapi",
]
[[package]]
@@ -665,6 +665,27 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "errno"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "fastrand"
version = "1.8.0"
@@ -703,10 +724,10 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.155-dev.0"
version = "0.0.165-dev.0"
dependencies = [
"anyhow",
"clap 4.0.22",
"clap 4.0.29",
"configparser",
"once_cell",
"regex",
@@ -719,9 +740,9 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.0.24"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
dependencies = [
"crc32fast",
"miniz_oxide",
@@ -742,41 +763,15 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fsevent"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6"
dependencies = [
"bitflags",
"fsevent-sys",
]
[[package]]
name = "fsevent-sys"
version = "2.0.1"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0"
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
dependencies = [
"libc",
]
[[package]]
name = "fuchsia-zircon"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
dependencies = [
"bitflags",
"fuchsia-zircon-sys",
]
[[package]]
name = "fuchsia-zircon-sys"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
[[package]]
name = "getrandom"
version = "0.1.16"
@@ -844,6 +839,15 @@ dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "hexf-parse"
version = "0.2.1"
@@ -861,7 +865,7 @@ dependencies = [
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"winapi 0.3.9",
"winapi",
]
[[package]]
@@ -896,9 +900,9 @@ dependencies = [
[[package]]
name = "inotify"
version = "0.7.1"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f"
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
dependencies = [
"bitflags",
"inotify-sys",
@@ -916,9 +920,9 @@ dependencies = [
[[package]]
name = "insta"
version = "1.21.0"
version = "1.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581d4e3314cae4536e5d22ffd23189d4a374696c5ef733eadafae0ed273fd303"
checksum = "197f4e300af8b23664d4077bf5c40e0afa9ba66a567bb5a51d3def3c7b287d1c"
dependencies = [
"console",
"lazy_static",
@@ -938,12 +942,25 @@ dependencies = [
]
[[package]]
name = "iovec"
version = "0.1.4"
name = "io-lifetimes"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330"
dependencies = [
"hermit-abi 0.2.6",
"io-lifetimes",
"rustix",
"windows-sys",
]
[[package]]
@@ -977,13 +994,23 @@ dependencies = [
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
name = "kqueue"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98"
dependencies = [
"winapi 0.2.8",
"winapi-build",
"kqueue-sys",
"libc",
]
[[package]]
name = "kqueue-sys"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587"
dependencies = [
"bitflags",
"libc",
]
[[package]]
@@ -1024,12 +1051,6 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "lexical-parse-float"
version = "0.8.5"
@@ -1062,9 +1083,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.137"
version = "0.2.138"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
[[package]]
name = "libcst"
@@ -1105,6 +1126,12 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f"
[[package]]
name = "lock_api"
version = "0.4.9"
@@ -1147,74 +1174,32 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.6.5"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
[[package]]
name = "miniz_oxide"
version = "0.5.4"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
dependencies = [
"adler",
]
[[package]]
name = "mio"
version = "0.6.23"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
dependencies = [
"cfg-if 0.1.10",
"fuchsia-zircon",
"fuchsia-zircon-sys",
"iovec",
"kernel32-sys",
"libc",
"log",
"miow",
"net2",
"slab",
"winapi 0.2.8",
]
[[package]]
name = "mio-extras"
version = "2.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
dependencies = [
"lazycell",
"log",
"mio",
"slab",
]
[[package]]
name = "miow"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
dependencies = [
"kernel32-sys",
"net2",
"winapi 0.2.8",
"ws2_32-sys",
]
[[package]]
name = "net2"
version = "0.2.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d0df99cfcd2530b2e694f6e17e7f37b8e26bb23983ac530c0c97408837c631"
dependencies = [
"cfg-if 0.1.10",
"libc",
"winapi 0.3.9",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys",
]
[[package]]
@@ -1231,9 +1216,9 @@ checksum = "d906846a98739ed9d73d66e62c2641eef8321f1734b7a1156ab045a0248fb2b3"
[[package]]
name = "nix"
version = "0.24.2"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc"
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
@@ -1258,20 +1243,20 @@ dependencies = [
[[package]]
name = "notify"
version = "4.0.17"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257"
checksum = "ed2c66da08abae1c024c01d635253e402341b4060a12e99b31c7594063bf490a"
dependencies = [
"bitflags",
"crossbeam-channel",
"filetime",
"fsevent",
"fsevent-sys",
"inotify",
"kqueue",
"libc",
"mio",
"mio-extras",
"walkdir",
"winapi 0.3.9",
"winapi",
]
[[package]]
@@ -1321,7 +1306,7 @@ version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
dependencies = [
"hermit-abi",
"hermit-abi 0.1.19",
"libc",
]
@@ -1339,9 +1324,9 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "os_str_bytes"
version = "6.3.1"
version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "parking_lot"
@@ -1355,9 +1340,9 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.9.4"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0"
checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba"
dependencies = [
"cfg-if 1.0.0",
"libc",
@@ -1557,9 +1542,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "predicates"
version = "2.1.2"
version = "2.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab68289ded120dcbf9d571afcf70163233229052aec9b08ab09532f698d0e1e6"
checksum = "f54fc5dc63ed3bbf19494623db4f3af16842c0d975818e469022d09e53f0aa05"
dependencies = [
"difflib",
"itertools",
@@ -1568,15 +1553,15 @@ dependencies = [
[[package]]
name = "predicates-core"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6e7125585d872860e9955ca571650b27a4979c5823084168c5ed5bbfb016b56"
checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2"
[[package]]
name = "predicates-tree"
version = "1.0.6"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad3f7fa8d61e139cbc7c3edfebf3b6678883a53f5ffac65d1259329a93ee43a5"
checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d"
dependencies = [
"predicates-core",
"termtree",
@@ -1736,11 +1721,10 @@ dependencies = [
[[package]]
name = "rayon"
version = "1.5.3"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
checksum = "1e060280438193c554f654141c9ea9417886713b7acd75974c85b18a69a88e0b"
dependencies = [
"autocfg",
"crossbeam-deque",
"either",
"rayon-core",
@@ -1748,9 +1732,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.9.3"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
@@ -1807,7 +1791,7 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi 0.3.9",
"winapi",
]
[[package]]
@@ -1822,7 +1806,7 @@ dependencies = [
"spin",
"untrusted",
"web-sys",
"winapi 0.3.9",
"winapi",
]
[[package]]
@@ -1837,7 +1821,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.155"
version = "0.0.165"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1847,7 +1831,7 @@ dependencies = [
"bitflags",
"cachedir",
"chrono",
"clap 4.0.22",
"clap 4.0.29",
"clap_complete_command",
"clearscreen",
"colored",
@@ -1871,6 +1855,7 @@ dependencies = [
"rayon",
"regex",
"ropey",
"ruff_macros",
"rustc-hash",
"rustpython-ast",
"rustpython-common",
@@ -1889,10 +1874,10 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.155"
version = "0.0.165"
dependencies = [
"anyhow",
"clap 4.0.22",
"clap 4.0.29",
"codegen",
"itertools",
"libcst",
@@ -1905,12 +1890,36 @@ dependencies = [
"strum_macros",
]
[[package]]
name = "ruff_macros"
version = "0.0.161"
dependencies = [
"proc-macro2",
"quote",
"syn",
"textwrap",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.36.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb93e85278e08bb5788653183213d3a60fc242b10cb9be96586f5a73dcb67c23"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "rustls"
version = "0.20.7"
@@ -1926,7 +1935,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.1.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=f885db8c61514f069979861f6b3bd83292086231#f885db8c61514f069979861f6b3bd83292086231"
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
dependencies = [
"num-bigint",
"rustpython-common",
@@ -1936,7 +1945,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.0.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=f885db8c61514f069979861f6b3bd83292086231#f885db8c61514f069979861f6b3bd83292086231"
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
dependencies = [
"ascii",
"cfg-if 1.0.0",
@@ -1959,7 +1968,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=f885db8c61514f069979861f6b3bd83292086231#f885db8c61514f069979861f6b3bd83292086231"
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
dependencies = [
"bincode",
"bitflags",
@@ -1976,7 +1985,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=f885db8c61514f069979861f6b3bd83292086231#f885db8c61514f069979861f6b3bd83292086231"
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
dependencies = [
"ahash",
"anyhow",
@@ -2049,18 +2058,18 @@ checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
[[package]]
name = "serde"
version = "1.0.147"
version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.147"
version = "1.0.148"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c"
dependencies = [
"proc-macro2",
"quote",
@@ -2069,9 +2078,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.87"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
dependencies = [
"itoa",
"ryu",
@@ -2080,9 +2089,9 @@ dependencies = [
[[package]]
name = "similar"
version = "2.2.0"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803"
checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf"
[[package]]
name = "siphasher"
@@ -2090,15 +2099,6 @@ version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
[[package]]
name = "slab"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
version = "1.10.0"
@@ -2172,9 +2172,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.103"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
dependencies = [
"proc-macro2",
"quote",
@@ -2192,7 +2192,7 @@ dependencies = [
"libc",
"redox_syscall",
"remove_dir_all",
"winapi 0.3.9",
"winapi",
]
[[package]]
@@ -2203,7 +2203,7 @@ checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
dependencies = [
"dirs-next",
"rustversion",
"winapi 0.3.9",
"winapi",
]
[[package]]
@@ -2222,7 +2222,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc",
"winapi 0.3.9",
"winapi",
]
[[package]]
@@ -2305,7 +2305,7 @@ checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi 0.3.9",
"winapi",
]
[[package]]
@@ -2568,7 +2568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi 0.3.9",
"winapi",
"winapi-util",
]
@@ -2690,12 +2690,6 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983"
[[package]]
name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
[[package]]
name = "winapi"
version = "0.3.9"
@@ -2706,12 +2700,6 @@ dependencies = [
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-build"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
@@ -2724,7 +2712,7 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi 0.3.9",
"winapi",
]
[[package]]
@@ -2790,16 +2778,6 @@ version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
[[package]]
name = "ws2_32-sys"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
dependencies = [
"winapi 0.2.8",
"winapi-build",
]
[[package]]
name = "yaml-rust"
version = "0.4.5"
@@ -2815,5 +2793,5 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1"
dependencies = [
"winapi 0.3.9",
"winapi",
]

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.155"
version = "0.0.165"
edition = "2021"
rust-version = "1.65.0"
@@ -33,7 +33,7 @@ itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
log = { version = "0.4.17" }
nohash-hasher = { version = "0.2.0" }
notify = { version = "4.0.17" }
notify = { version = "5.0.0" }
num-bigint = { version = "0.4.3" }
once_cell = { version = "1.16.0" }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
@@ -41,10 +41,11 @@ quick-junit = { version = "0.3.2" }
rayon = { version = "1.5.3" }
regex = { version = "1.6.0" }
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
ruff_macros = { version = "0.0.161", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }
strum = { version = "0.24.1", features = ["strum_macros"] }

500
README.md
View File

@@ -87,13 +87,12 @@ of [Conda](https://docs.conda.io/en/latest/):
1. [flake8-debugger (T10)](#flake8-debugger)
1. [flake8-print (T20)](#flake8-print)
1. [flake8-quotes (Q)](#flake8-quotes)
1. [flake8-return (ET)](#flake8-return)
1. [flake8-return (RET)](#flake8-return)
1. [flake8-tidy-imports (I25)](#flake8-tidy-imports)
1. [eradicate (ERA)](#eradicate)
1. [pygrep-hooks (PGH)](#pygrep-hooks)
1. [Pylint (PL)](#pylint)
1. [Ruff-specific rules (RUF)](#ruff-specific-rules)
1. [Meta rules (M)](#meta-rules)
1. [Editor Integrations](#editor-integrations)
1. [FAQ](#faq)
1. [Development](#development)
@@ -146,7 +145,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.155
rev: v0.0.165
hooks:
- id: ruff
```
@@ -351,6 +350,16 @@ error reporting for the entire file.
For targeted exclusions across entire files (e.g., "Ignore all F841 violations in
`/path/to/file.py`"), see the [`per-file-ignores`](#per-file-ignores) configuration setting.
### "Action Comments"
Ruff respects `isort`'s ["Action Comments"](https://pycqa.github.io/isort/docs/configuration/action_comments.html)
(`# isort: skip_file`, `# isort: on`, `# isort: off`, `# isort: skip`, and `isort: split`), which
enable selectively enabling and disabling import sorting for blocks of code and other inline
configuration.
See the [`isort` documentation](https://pycqa.github.io/isort/docs/configuration/action_comments.html)
for more.
### Automating `noqa` Directives
Ruff supports several workflows to aid in `noqa` management.
@@ -498,7 +507,7 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI.
| D214 | SectionNotOverIndented | Section is over-indented ("Returns") | 🛠 |
| D215 | SectionUnderlineNotOverIndented | Section underline is over-indented ("Returns") | 🛠 |
| D300 | UsesTripleQuotes | Use """triple double quotes""" | |
| D400 | EndsInPeriod | First line should end with a period | |
| 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" | |
@@ -512,7 +521,7 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI.
| D412 | NoBlankLinesBetweenHeaderAndContent | No blank lines allowed between a section header and its content ("Returns") | 🛠 |
| D413 | BlankLineAfterLastSection | Missing blank line after last section ("Returns") | 🛠 |
| D414 | NonEmptySection | Section has no content ("Returns") | |
| D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point | |
| D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point | 🛠 |
| D416 | SectionNameEndsInColon | Section name should end with a colon ("Returns") | 🛠 |
| D417 | DocumentAllArguments | Missing argument descriptions in the docstring: `x`, `y` | |
| D418 | SkipDocstring | Function decorated with `@overload` shouldn't contain a docstring | |
@@ -767,12 +776,15 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| PLC0414 | UselessImportAlias | Import alias does not rename original package | 🛠 |
| PLC2201 | MisplacedComparisonConstant | Comparison should be ... | 🛠 |
| PLC3002 | UnnecessaryDirectLambdaCall | Lambda expression called directly. Execute the expression inline instead. | |
| PLE1142 | AwaitOutsideAsync | `await` should be used within an async function | |
| PLR0206 | PropertyWithParameters | Cannot have defined parameters for properties | |
| PLR0402 | ConsiderUsingFromImport | Consider using `from ... import ...` | |
| PLR1701 | ConsiderMergingIsinstance | Consider merging these isinstance calls: `isinstance(..., (...))` | |
| PLR1722 | ConsiderUsingSysExit | Consider using `sys.exit()` | 🛠 |
| PLW0120 | UselessElseOnLoop | Else clause on loop without a break statement, remove the else and de-indent all the code inside it | |
### Ruff-specific rules
@@ -781,7 +793,6 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
| RUF001 | AmbiguousUnicodeCharacterString | String contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
| RUF002 | AmbiguousUnicodeCharacterDocstring | Docstring contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
| RUF003 | AmbiguousUnicodeCharacterComment | Comment contains ambiguous unicode character '𝐁' (did you mean 'B'?) | |
| RUF004 | ConvertExitToSysExit | `exit()` is only available in the interpreter, use `sys.exit()` instead | 🛠 |
| RUF100 | UnusedNOQA | Unused `noqa` directive | 🛠 |
<!-- End auto-generated sections. -->
@@ -1269,12 +1280,36 @@ Summary
### Options
#### [`dummy_variable_rgx`](#dummy_variable_rgx)
<!-- Sections automatically generated by `cargo dev generate-options`. -->
<!-- Begin auto-generated options sections. -->
A regular expression used to identify "dummy" variables, or those which should be ignored when evaluating
(e.g.) unused-variable checks.
#### [`allowed-confusables`](#allowed-confusables)
**Default value**: `"^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"` (matches `_`, `__`, and `_var`, but not `_var_`)
A list of allowed "confusable" Unicode characters to ignore when enforcing `RUF001`,
`RUF002`, and `RUF003`.
**Default value**: `[]`
**Type**: `Vec<char>`
**Example usage**:
```toml
[tool.ruff]
# Allow minus-sign (U+2212), greek-small-letter-rho (U+03C1), and the asterisk-operator (U+2217),
# which could be confused for "-", "p", and "*", respectively.
allowed-confusables = ["", "ρ", ""]
```
---
#### [`dummy-variable-rgx`](#dummy-variable-rgx)
A regular expression used to identify "dummy" variables, or those which should be
ignored when evaluating (e.g.) unused-variable checks. The default expression matches
`_`, `__`, and `_var`, but not `_var_`.
**Default value**: `"^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"`
**Type**: `Regex`
@@ -1301,8 +1336,8 @@ Exclusions are based on globs, and can be either:
(to exclude any Python files in `directory`). Note that these paths are relative to the
project root (e.g., the directory containing your `pyproject.toml`).
Note that you'll typically want to use [`extend_exclude`](#extend-exclude) to modify the excluded
paths.
Note that you'll typically want to use [`extend_exclude`](#extend_exclude) to modify
the excluded paths.
**Default value**: `[".bzr", ".direnv", ".eggs", ".git", ".hg", ".mypy_cache", ".nox", ".pants.d", ".ruff_cache", ".svn", ".tox", ".venv", "__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", "venv"]`
@@ -1313,7 +1348,7 @@ paths.
```toml
[tool.ruff]
exclude = [".venv"]
````
```
---
@@ -1331,28 +1366,6 @@ A list of file patterns to omit from linting, in addition to those specified by
[tool.ruff]
# In addition to the standard set of exclusions, omit all tests, plus a specific file.
extend-exclude = ["tests", "src/bad.py"]
````
---
#### [`ignore`](#ignore)
A list of check code prefixes to ignore. Prefixes can specify exact checks (like `F841`), entire
categories (like `F`), or anything in between.
When breaking ties between enabled and disabled checks (via `select` and `ignore`, respectively),
more specific prefixes override less specific prefixes.
**Default value**: `[]`
**Type**: `Vec<CheckCodePrefix>`
**Example usage**:
```toml
[tool.ruff]
# Skip unused variable checks (`F841`).
ignore = ["F841"]
```
---
@@ -1375,28 +1388,6 @@ extend-ignore = ["F841"]
---
#### [`select`](#select)
A list of check code prefixes to enable. Prefixes can specify exact checks (like `F841`), entire
categories (like `F`), or anything in between.
When breaking ties between enabled and disabled checks (via `select` and `ignore`, respectively),
more specific prefixes override less specific prefixes.
**Default value**: `["E", "F"]`
**Type**: `Vec<CheckCodePrefix>`
**Example usage**:
```toml
[tool.ruff]
# On top of the defaults (`E`, `F`), enable flake8-bugbear (`B`) and flake8-quotes (`Q`).
select = ["E", "F", "B", "Q"]
```
---
#### [`extend-select`](#extend-select)
A list of check code prefixes to enable, in addition to those specified by `select`.
@@ -1429,8 +1420,8 @@ yet implemented in Ruff.
```toml
[tool.ruff]
# Avoiding flagging (and removing) `V101` from any `# noqa` directives, despite Ruff's lack of
# support for `vulture`.
# Avoiding flagging (and removing) `V101` from any `# noqa`
# directives, despite Ruff's lack of support for `vulture`.
external = ["V101"]
```
@@ -1438,8 +1429,8 @@ external = ["V101"]
#### [`fix`](#fix)
Enable autofix behavior by-default when running `ruff` (overridden by the `--fix` and `--no-fix`
command-line flags).
Enable autofix behavior by-default when running `ruff` (overridden
by the `--fix` and `--no-fix` command-line flags).
**Default value**: `false`
@@ -1472,9 +1463,33 @@ fixable = ["E", "F"]
---
#### [`unfixable`](#unfixable)
#### [`format`](#format)
A list of check code prefixes to consider un-autofix-able.
The style in which violation messages should be formatted: `"text"` (default),
`"grouped"` (group messages by file), `"json"` (machine-readable), `"junit"`
(machine-readable XML), or `"github"` (GitHub Actions annotations).
**Default value**: `"text"`
**Type**: `SerializationType`
**Example usage**:
```toml
[tool.ruff]
# Group violations by containing file.
format = "grouped"
```
---
#### [`ignore`](#ignore)
A list of check code prefixes to ignore. Prefixes can specify exact checks (like
`F841`), entire categories (like `F`), or anything in between.
When breaking ties between enabled and disabled checks (via `select` and `ignore`,
respectively), more specific prefixes override less specific prefixes.
**Default value**: `[]`
@@ -1484,8 +1499,28 @@ A list of check code prefixes to consider un-autofix-able.
```toml
[tool.ruff]
# Disable autofix for unused imports (`F401`).
unfixable = ["F401"]
# Skip unused variable checks (`F841`).
ignore = ["F841"]
```
---
#### [`ignore-init-module-imports`](#ignore-init-module-imports)
Avoid automatically removing unused imports in `__init__.py` files. Such imports will
still be +flagged, but with a dedicated message suggesting that the import is either
added to the module' +`__all__` symbol, or re-exported with a redundant alias (e.g.,
`import os as os`).
**Default value**: `false`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff]
ignore-init-module-imports = true
```
---
@@ -1508,29 +1543,10 @@ line-length = 120
---
#### [`format`](#format)
The style in which violation messages should be formatted: `"text"` (default), `"grouped"`
(group messages by file), `"json"` (machine-readable), `"junit"` (machine-readable XML), or `"github"` (GitHub Actions annotations).
**Default value**: `"text"`
**Type**: `SerializationFormat`
**Example usage**:
```toml
[tool.ruff]
# Group violations by containing file.
format = "grouped"
```
---
#### [`per-file-ignores`](#per-file-ignores)
A list of mappings from file pattern to check code prefixes to exclude, when considering any
matching files.
A list of mappings from file pattern to check code prefixes to exclude, when considering
any matching files.
**Default value**: `{}`
@@ -1548,10 +1564,32 @@ matching files.
---
#### [`select`](#select)
A list of check code prefixes to enable. Prefixes can specify exact checks (like
`F841`), entire categories (like `F`), or anything in between.
When breaking ties between enabled and disabled checks (via `select` and `ignore`,
respectively), more specific prefixes override less specific prefixes.
**Default value**: `["E", "F"]`
**Type**: `Vec<CheckCodePrefix>`
**Example usage**:
```toml
[tool.ruff]
# On top of the defaults (`E`, `F`), enable flake8-bugbear (`B`) and flake8-quotes (`Q`).
select = ["E", "F", "B", "Q"]
```
---
#### [`show-source`](#show-source)
Whether to show source code snippets when reporting lint error violations (overridden by the
`--show-source` command-line flag).
Whether to show source code snippets when reporting lint error violations (overridden by
the `--show-source` command-line flag).
**Default value**: `false`
@@ -1587,9 +1625,9 @@ src = ["src", "test"]
#### [`target-version`](#target-version)
The Python version to target, e.g., when considering automatic code upgrades, like rewriting type
annotations. Note that the target version will _not_ be inferred from the _current_ Python version,
and instead must be specified explicitly (as seen below).
The Python version to target, e.g., when considering automatic code upgrades, like
rewriting type annotations. Note that the target version will _not_ be inferred from the
_current_ Python version, and instead must be specified explicitly (as seen below).
**Default value**: `"py310"`
@@ -1603,12 +1641,49 @@ and instead must be specified explicitly (as seen below).
target-version = "py37"
```
---
#### [`unfixable`](#unfixable)
A list of check code prefixes to consider un-autofix-able.
**Default value**: `[]`
**Type**: `Vec<CheckCodePrefix>`
**Example usage**:
```toml
[tool.ruff]
# Disable autofix for unused imports (`F401`).
unfixable = ["F401"]
```
---
### `flake8-annotations`
#### [`allow-star-arg-any`](#allow-star-arg-any)
Whether to suppress `ANN401` for dynamically typed `*args` and `**kwargs` arguments.
**Default value**: `false`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.flake8-annotations]
allow-star-arg-any = true
```
---
#### [`mypy-init-return`](#mypy-init-return)
Whether to allow the omission of a return type hint for `__init__` if at least one argument is
annotated.
Whether to allow the omission of a return type hint for `__init__` if at least one
argument is annotated.
**Default value**: `false`
@@ -1625,8 +1700,8 @@ mypy-init-return = true
#### [`suppress-dummy-args`](#suppress-dummy-args)
Whether to suppress `ANN000`-level errors for arguments matching the "dummy" variable regex (like
`_`).
Whether to suppress `ANN000`-level errors for arguments matching the "dummy" variable
regex (like `_`).
**Default value**: `false`
@@ -1643,7 +1718,8 @@ suppress-dummy-args = true
#### [`suppress-none-returning`](#suppress-none-returning)
Whether to suppress `ANN200`-level errors for functions that meet either of the following criteria:
Whether to suppress `ANN200`-level errors for functions that meet either of the
following criteria:
- Contain no `return` statement.
- Explicit `return` statement(s) all return `None` (explicitly or implicitly).
@@ -1661,27 +1737,12 @@ suppress-none-returning = true
---
#### [`allow-star-arg-any`](#allow-star-arg-any)
Whether to suppress `ANN401` for dynamically typed `*args` and `**kwargs` arguments.
**Default value**: `false`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.flake8-annotations]
allow-star-arg-any = true
```
### `flake8-bugbear`
#### [`extend-immutable-calls`](#extend-immutable-calls)
Additional callable functions to consider "immutable" when evaluating, e.g., no-mutable-default-argument
checks (`B006`).
Additional callable functions to consider "immutable" when evaluating, e.g.,
`no-mutable-default-argument` checks (`B006`).
**Default value**: `[]`
@@ -1695,8 +1756,47 @@ checks (`B006`).
extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"]
```
---
### `flake8-quotes`
#### [`avoid-escape`](#avoid-escape)
Whether to avoid using single quotes if a string contains single quotes, or vice-versa
with double quotes, as per [PEP8](https://peps.python.org/pep-0008/#string-quotes).
This minimizes the need to escape quotation marks within strings.
**Default value**: `true`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.flake8-quotes]
# Don't bother trying to avoid escapes.
avoid-escape = false
```
---
#### [`docstring-quotes`](#docstring-quotes)
Quote style to prefer for docstrings (either "single" (`'`) or "double" (`"`)).
**Default value**: `"double"`
**Type**: `Quote`
**Example usage**:
```toml
[tool.ruff.flake8-quotes]
docstring-quotes = "single"
```
---
#### [`inline-quotes`](#inline-quotes)
Quote style to prefer for inline strings (either "single" (`'`) or "double" (`"`)).
@@ -1731,47 +1831,12 @@ multiline-quotes = "single"
---
#### [`docstring-quotes`](#docstring-quotes)
Quote style to prefer for docstrings (either "single" (`'`) or "double" (`"`)).
**Default value**: `"double"`
**Type**: `Quote`
**Example usage**:
```toml
[tool.ruff.flake8-quotes]
docstring-quotes = "single"
```
---
#### [`avoid-escape`](#avoid-escape)
Whether to avoid using single quotes if a string contains single quotes, or vice-versa with
double quotes, as per [PEP8](https://peps.python.org/pep-0008/#string-quotes). This minimizes the
need to escape quotation marks within strings.
**Default value**: `true`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.flake8-quotes]
# Don't bother trying to avoid escapes.
avoid-escape = false
```
### `flake8-tidy-imports`
#### [`ban-relative-imports`](#ban-relative-imports)
Whether to ban all relative imports (`"all"`), or only those imports that extend into the parent
module and beyond (`"parents"`).
Whether to ban all relative imports (`"all"`), or only those imports that extend into
the parent module and beyond (`"parents"`).
**Default value**: `"parents"`
@@ -1785,9 +1850,11 @@ module and beyond (`"parents"`).
ban-relative-imports = "all"
```
---
### `isort`
#### [`combine-as-imports`](combine-as-imports)
#### [`combine-as-imports`](#combine-as-imports)
Combines as imports on the same line. See isort's [`combine-as-imports`](https://pycqa.github.io/isort/docs/configuration/options.html#combine-as-imports)
option.
@@ -1805,10 +1872,54 @@ combine-as-imports = true
---
#### [`known-first-party`](known-first-party)
#### [`extra-standard-library`](#extra-standard-library)
A list of modules to consider first-party, regardless of whether they can be identified as such
via introspection of the local filesystem.
A list of modules to consider standard-library, in addition to those known to Ruff in
advance.
**Default value**: `[]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.isort]
extra-standard-library = ["path"]
```
---
#### [`force-wrap-aliases`](#force-wrap-aliases)
Force `import from` statements with multiple members and at least one alias (e.g.,
`import A as B`) to wrap such that every line contains exactly one member. For example,
this formatting would be retained, rather than condensing to a single line:
```py
from .utils import (
test_directory as test_directory,
test_id as test_id
)
```
**Default value**: `false`
**Type**: `bool`
**Example usage**:
```toml
[tool.ruff.isort]
force-wrap-aliases = true
```
---
#### [`known-first-party`](#known-first-party)
A list of modules to consider first-party, regardless of whether they can be identified
as such via introspection of the local filesystem.
**Default value**: `[]`
@@ -1823,10 +1934,10 @@ known-first-party = ["src"]
---
#### [`known-third-party`](known-third-party)
#### [`known-third-party`](#known-third-party)
A list of modules to consider third-party, regardless of whether they can be identified as such
via introspection of the local filesystem.
A list of modules to consider third-party, regardless of whether they can be identified
as such via introspection of the local filesystem.
**Default value**: `[]`
@@ -1836,26 +1947,11 @@ via introspection of the local filesystem.
```toml
[tool.ruff.isort]
known-third-party = ["fastapi"]
known-third-party = ["src"]
```
---
#### [`extra-standard-library`](extra-standard-library)
A list of modules to consider standard-library, in addition to those known to Ruff in advance.
**Default value**: `[]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.isort]
extra-standard-library = ["path"]
```
### `mccabe`
#### [`max-complexity`](#max-complexity)
@@ -1874,8 +1970,30 @@ The maximum McCabe complexity to allow before triggering `C901` errors.
max-complexity = 5
```
---
### `pep8-naming`
#### [`classmethod-decorators`](#classmethod-decorators)
A list of decorators that, when applied to a method, indicate that the method should be
treated as a class method. For example, Ruff will expect that any method decorated by a
decorator in this list takes a `cls` argument as its first argument.
**Default value**: `["classmethod"]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.pep8-naming]
# Allow Pydantic's `@validator` decorator to trigger class method treatment.
classmethod-decorators = ["classmethod", "pydantic.validator"]
```
---
#### [`ignore-names`](#ignore-names)
A list of names to ignore when considering `pep8-naming` violations.
@@ -1893,31 +2011,11 @@ ignore-names = ["callMethod"]
---
#### [`classmethod-decorators`](#classmethod-decorators)
A list of decorators that, when applied to a method, indicate that the method should be treated as
a class method. For example, Ruff will expect that any method decorated by a decorator in this list
takes a `cls` argument as its first argument.
**Default value**: `["classmethod"]`
**Type**: `Vec<String>`
**Example usage**:
```toml
[tool.ruff.pep8-naming]
# Allow Pydantic's `@validator` decorator to trigger class method treatment.
classmethod-decorators = ["classmethod", "pydantic.validator"]
```
---
#### [`staticmethod-decorators`](#staticmethod-decorators)
A list of decorators that, when applied to a method, indicate that the method should be treated as
a static method. For example, Ruff will expect that any method decorated by a decorator in this list
has no `self` or `cls` argument.
A list of decorators that, when applied to a method, indicate that the method should be
treated as a static method. For example, Ruff will expect that any method decorated by a
decorator in this list has no `self` or `cls` argument.
**Default value**: `["staticmethod"]`
@@ -1931,13 +2029,13 @@ has no `self` or `cls` argument.
staticmethod-decorators = ["staticmethod", "stcmthd"]
```
---
### `pyupgrade`
#### [`keep-runtime-typing`](#keep-runtime-typing)
Whether to avoid PEP 585 (`List[int]` -> `list[int]`) and PEP 604 (`Optional[str]` -> `str | None`)
rewrites even if a file imports `from __future__ import annotations`. Note that this setting is
only applicable when the target Python version is below 3.9 and 3.10 respectively.
Whether to avoid PEP 585 (`List[int]` -> `list[int]`) and PEP 604 (`Optional[str]` -> `str | None`) rewrites even if a file imports `from __future__ import annotations`. Note that this setting is only applicable when the target Python version is below 3.9 and 3.10 respectively.
**Default value**: `false`
@@ -1951,6 +2049,10 @@ only applicable when the target Python version is below 3.9 and 3.10 respectivel
keep-runtime-typing = true
```
---
<!-- End auto-generated options sections. -->
## License
MIT

View File

@@ -1,11 +0,0 @@
set -euxo pipefail
NAME=$1
mkdir -p src/$1
mkdir -p resources/test/fixtures/$1
touch src/$1/mod.rs
sed -i "" "s/mod flake8_print;/mod flake8_print; mod flake8_return;/g" src/lib.rs
sed -i "" "s|// flake8-print|// flake8-return\n// flake8-print|g" src/checks.rs

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.155"
version = "0.0.165"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.155"
version = "0.0.165"
dependencies = [
"anyhow",
"bincode",
@@ -2028,7 +2028,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.1.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=f885db8c61514f069979861f6b3bd83292086231#f885db8c61514f069979861f6b3bd83292086231"
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
dependencies = [
"num-bigint",
"rustpython-common",
@@ -2038,7 +2038,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.0.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=f885db8c61514f069979861f6b3bd83292086231#f885db8c61514f069979861f6b3bd83292086231"
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
dependencies = [
"ascii",
"cfg-if 1.0.0",
@@ -2061,7 +2061,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=f885db8c61514f069979861f6b3bd83292086231#f885db8c61514f069979861f6b3bd83292086231"
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
dependencies = [
"bincode",
"bitflags",
@@ -2078,7 +2078,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=f885db8c61514f069979861f6b3bd83292086231#f885db8c61514f069979861f6b3bd83292086231"
source = "git+https://github.com/RustPython/RustPython.git?rev=28f9f65ccc625f00835d84bbb5fba274dce5aa89#28f9f65ccc625f00835d84bbb5fba274dce5aa89"
dependencies = [
"ahash",
"anyhow",

View File

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

View File

@@ -243,6 +243,7 @@ mod tests {
fn it_converts_empty() -> Result<()> {
let actual = convert(&HashMap::from([]), None)?;
let expected = Pyproject::new(Options {
allowed_confusables: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
@@ -253,6 +254,7 @@ mod tests {
fixable: None,
format: None,
ignore: Some(vec![]),
ignore_init_module_imports: None,
line_length: None,
per_file_ignores: None,
select: Some(vec![
@@ -285,6 +287,7 @@ mod tests {
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
allowed_confusables: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
@@ -295,6 +298,7 @@ mod tests {
fixable: None,
format: None,
ignore: Some(vec![]),
ignore_init_module_imports: None,
line_length: Some(100),
per_file_ignores: None,
select: Some(vec![
@@ -327,6 +331,7 @@ mod tests {
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
allowed_confusables: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
@@ -337,6 +342,7 @@ mod tests {
fixable: None,
format: None,
ignore: Some(vec![]),
ignore_init_module_imports: None,
line_length: Some(100),
per_file_ignores: None,
select: Some(vec![
@@ -369,6 +375,7 @@ mod tests {
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
allowed_confusables: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
@@ -379,6 +386,7 @@ mod tests {
fixable: None,
format: None,
ignore: Some(vec![]),
ignore_init_module_imports: None,
line_length: None,
per_file_ignores: None,
select: Some(vec![
@@ -411,6 +419,7 @@ mod tests {
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
allowed_confusables: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
@@ -421,6 +430,7 @@ mod tests {
fixable: None,
format: None,
ignore: Some(vec![]),
ignore_init_module_imports: None,
line_length: None,
per_file_ignores: None,
select: Some(vec![
@@ -461,6 +471,7 @@ mod tests {
Some(vec![Plugin::Flake8Docstrings]),
)?;
let expected = Pyproject::new(Options {
allowed_confusables: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
@@ -471,6 +482,7 @@ mod tests {
fixable: None,
format: None,
ignore: Some(vec![]),
ignore_init_module_imports: None,
line_length: None,
per_file_ignores: None,
select: Some(vec![
@@ -538,6 +550,7 @@ mod tests {
None,
)?;
let expected = Pyproject::new(Options {
allowed_confusables: None,
dummy_variable_rgx: None,
exclude: None,
extend_exclude: None,
@@ -548,6 +561,7 @@ mod tests {
fixable: None,
format: None,
ignore: Some(vec![]),
ignore_init_module_imports: None,
line_length: None,
per_file_ignores: None,
select: Some(vec![

View File

@@ -31,3 +31,7 @@ build-backend = "maturin"
[tool.maturin]
bindings = "bin"
strip = true
[tool.ruff.isort]
force-wrap-aliases = true
combine-as-imports = true

View File

@@ -2,6 +2,7 @@ breakpoint()
import pdb
import builtins
from builtins import breakpoint
from pdb import set_trace as st
from celery.contrib.rdb import set_trace

View File

@@ -0,0 +1,3 @@
from .a import a1 as a1, a2 as a2
from .b import b1 as b1
from .c import c1

View File

@@ -0,0 +1,41 @@
import a
import b
x = 1
import os
import sys
def f():
pass
if True:
x = 1
import collections
import typing
class X: pass
y = 1
import os
import sys
"""Docstring"""
if True:
import os
def f():
pass
if True:
import os
def f():
pass
if True:
x = 1
import collections
import typing
class X: pass
if True:
x = 1
import collections
import typing
def f(): pass

View File

@@ -0,0 +1 @@
from .subscription import * # type: ignore # some very long comment explaining why this needs a type ignore

View File

@@ -0,0 +1,6 @@
from some_other_module import some_class
from some_other_module import *
# Above
from some_module import some_class # Aside
# Above
from some_module import * # Aside

View File

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

View File

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

View File

@@ -571,3 +571,11 @@ def multiline_trailing_and_leading_space():
"""
pass
@expect('D210: No whitespaces allowed surrounding docstring text')
@expect("D400: First line should end with a period (not '\"')")
@expect("D415: First line should end with a period, question mark, "
"or exclamation point (not '\"')")
def endswith_quote():
"""Whitespace at the end, but also a quote" """

View File

@@ -0,0 +1,73 @@
def f():
"Here's a line without a period"
...
def f():
"""Here's a line without a period"""
...
def f():
"""
Here's a line without a period,
but here's the next line
"""
...
def f():
"""Here's a line without a period"""
...
def f():
"""
Here's a line without a period,
but here's the next line"""
...
def f():
"""
Here's a line without a period,
but here's the next line with trailing space """
...
def f():
r"Here's a line without a period"
...
def f():
r"""Here's a line without a period"""
...
def f():
r"""
Here's a line without a period,
but here's the next line
"""
...
def f():
r"""Here's a line without a period"""
...
def f():
r"""
Here's a line without a period,
but here's the next line"""
...
def f():
r"""
Here's a line without a period,
but here's the next line with trailing space """
...

View File

@@ -4,5 +4,18 @@ if x is "abc":
if 123 is not y:
pass
if 123 is \
not y:
pass
if "123" is x < 3:
pass
if "123" != x is 3:
pass
if ("123" != x) is 3:
pass
if "123" != (x is 3):
pass

View File

@@ -65,3 +65,8 @@ def f7():
with connect() as (connection, cursor):
cursor.execute("SELECT * FROM users")
def f8():
with open("file") as f, open("") as ((a, b)):
print("hello")

View File

@@ -0,0 +1,24 @@
def f(tup):
x, y = tup # this does NOT trigger F841
def f():
x, y = 1, 2 # this triggers F841 as it's just a simple assignment where unpacking isn't needed
def f():
(x, y) = coords = 1, 2 # this does NOT trigger F841
if x > 1:
print(coords)
def f():
(x, y) = coords = 1, 2 # this triggers F841 on coords
def f():
coords = (x, y) = 1, 2 # this triggers F841 on coords
def f():
(a, b) = (x, y) = 1, 2 # this triggers F841 on everything

View File

@@ -34,4 +34,5 @@ def isinstances():
result = isinstance(var[7], int) or not isinstance(var[7], float)
result = isinstance(var[6], int) or isinstance(var[7], float)
result = isinstance(var[6], int) or isinstance(var[7], int)
result = isinstance(var[6], (float, int)) or False
return result

View File

@@ -1,5 +1,7 @@
exit(0)
quit(0)
def main():
exit(2)
quit(2)

View File

@@ -1,10 +1,12 @@
import sys
exit(0)
quit(0)
def main():
exit(1)
quit(1)
sys.exit(2)

View File

@@ -1,7 +1,9 @@
import sys as sys2
exit(0)
quit(0)
def main():
exit(1)
quit(1)

View File

@@ -1,7 +1,9 @@
from sys import exit
exit(0)
quit(0)
def main():
exit(1)
quit(1)

View File

@@ -1,7 +1,9 @@
from sys import exit as exit2
exit(0)
quit(0)
def main():
exit(1)
quit(1)

View File

@@ -1,7 +1,9 @@
from sys import *
exit(0)
quit(0)
def main():
exit(1)
quit(1)

View File

@@ -1,12 +1,19 @@
exit(0)
quit(0)
def exit(e):
pass
def quit(e):
pass
exit(1)
quit(1)
def main():
exit(2)
quit(2)

View File

@@ -3,6 +3,7 @@
# 1. useless-import-alias
# 2. consider-using-from-import
import collections as collections # [useless-import-alias]
from collections import OrderedDict as OrderedDict # [useless-import-alias]
from collections import OrderedDict as o_dict
import os.path as path # [consider-using-from-import]

View File

@@ -0,0 +1,103 @@
"""Check for else branches on loops with break and return only."""
def test_return_for():
"""else + return is not acceptable."""
for i in range(10):
if i % 2:
return i
else: # [useless-else-on-loop]
print("math is broken")
return None
def test_return_while():
"""else + return is not acceptable."""
while True:
return 1
else: # [useless-else-on-loop]
print("math is broken")
return None
while True:
def short_fun():
"""A function with a loop."""
for _ in range(10):
break
else: # [useless-else-on-loop]
print("or else!")
while True:
while False:
break
else: # [useless-else-on-loop]
print("or else!")
for j in range(10):
pass
else: # [useless-else-on-loop]
print("fat chance")
for j in range(10):
break
def test_return_for2():
"""no false positive for break in else
https://bitbucket.org/logilab/pylint/issue/117/useless-else-on-loop-false-positives
"""
for i in range(10):
for _ in range(i):
if i % 2:
break
else:
break
else:
print("great math")
def test_break_in_orelse_deep():
"""no false positive for break in else deeply nested"""
for _ in range(10):
if 1 < 2: # pylint: disable=comparison-of-constants
for _ in range(3):
if 3 < 2: # pylint: disable=comparison-of-constants
break
else:
break
else:
return True
return False
def test_break_in_orelse_deep2():
"""should rise a useless-else-on-loop message, as the break statement is only
for the inner for loop
"""
for _ in range(10):
if 1 < 2: # pylint: disable=comparison-of-constants
for _ in range(3):
if 3 < 2: # pylint: disable=comparison-of-constants
break
else:
print("all right")
else: # [useless-else-on-loop]
return True
return False
def test_break_in_orelse_deep3():
"""no false positive for break deeply nested in else"""
for _ in range(10):
for _ in range(3):
pass
else:
if 1 < 2: # pylint: disable=comparison-of-constants
break
else:
return True
return False

View File

@@ -1,4 +1,5 @@
[tool.ruff]
allowed-confusables = ["", "ρ", ""]
line-length = 88
extend-exclude = [
"excluded_file.py",
@@ -35,13 +36,8 @@ ignore-names = [
"longMessage",
"maxDiff",
]
classmethod-decorators = [
"classmethod",
"pydantic.validator",
]
staticmethod-decorators = [
"staticmethod",
]
classmethod-decorators = ["classmethod", "pydantic.validator"]
staticmethod-decorators = ["staticmethod"]
[tool.ruff.flake8-tidy-imports]
ban-relative-imports = "parents"

View File

@@ -1,7 +0,0 @@
x = "𝐁ad string"
def f():
"""Here's a docstring with an unusual parenthesis: """
# And here's a comment with an unusual punctuation mark:
...

View File

@@ -1,7 +0,0 @@
x = "𝐁ad string"
def f():
"""Here's a docstring with an unusual parenthesis: """
# And here's a comment with an unusual punctuation mark:
...

View File

@@ -1,7 +1,14 @@
x = "𝐁ad string"
y = ""
def f():
"""Here's a docstring with an unusual parenthesis: """
# And here's a comment with an unusual punctuation mark:
...
def g():
"""Here's a docstring with a greek rho: ρ"""
# And here's a comment with a greek alpha:
...

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.155"
version = "0.0.165"
edition = "2021"
[dependencies]
@@ -11,8 +11,8 @@ itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "f2f0b7a487a8725d161fe8b3ed73a6758b21e177" }
once_cell = { version = "1.16.0" }
ruff = { path = ".." }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "f885db8c61514f069979861f6b3bd83292086231" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }

View File

@@ -0,0 +1,125 @@
//! Generate a Markdown-compatible listing of configuration options.
use std::fs;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use itertools::Itertools;
use ruff::settings::options::Options;
use ruff::settings::options_base::{ConfigurationOptions, OptionEntry, OptionField};
const BEGIN_PRAGMA: &str = "<!-- Begin auto-generated options sections. -->";
const END_PRAGMA: &str = "<!-- End auto-generated options sections. -->";
#[derive(Args)]
pub struct Cli {
/// Write the generated table to stdout (rather than to `README.md`).
#[arg(long)]
dry_run: bool,
}
fn emit_field(output: &mut String, field: &OptionField, group_name: Option<&str>) {
output.push_str(&format!("#### [`{0}`](#{0})\n", field.name));
output.push('\n');
output.push_str(field.doc);
output.push_str("\n\n");
output.push_str(&format!("**Default value**: `{}`\n", field.default));
output.push('\n');
output.push_str(&format!("**Type**: `{}`\n", field.value_type));
output.push('\n');
output.push_str(&format!(
"**Example usage**:\n\n```toml\n[tool.ruff{}]\n{}\n```\n",
if group_name.is_some() {
format!(".{}", group_name.unwrap())
} else {
String::new()
},
field.example
));
output.push('\n');
}
pub fn main(cli: &Cli) -> Result<()> {
let mut output = String::new();
// Generate all the top-level fields.
for field in Options::get_available_options()
.into_iter()
.filter_map(|entry| {
if let OptionEntry::Field(field) = entry {
Some(field)
} else {
None
}
})
.sorted_by_key(|field| field.name)
{
emit_field(&mut output, &field, None);
output.push_str("---\n\n");
}
// Generate all the sub-groups.
for group in Options::get_available_options()
.into_iter()
.filter_map(|entry| {
if let OptionEntry::Group(group) = entry {
Some(group)
} else {
None
}
})
.sorted_by_key(|group| group.name)
{
output.push_str(&format!("### `{}`\n", group.name));
output.push('\n');
for field in group
.fields
.iter()
.filter_map(|entry| {
if let OptionEntry::Field(field) = entry {
Some(field)
} else {
None
}
})
.sorted_by_key(|field| field.name)
{
emit_field(&mut output, field, Some(group.name));
output.push_str("---\n\n");
}
}
if cli.dry_run {
print!("{output}");
} else {
// Read the existing file.
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("Failed to find root directory")
.join("README.md");
let existing = fs::read_to_string(&file)?;
// Extract the prefix.
let index = existing
.find(BEGIN_PRAGMA)
.expect("Unable to find begin pragma");
let prefix = &existing[..index + BEGIN_PRAGMA.len()];
// Extract the suffix.
let index = existing
.find(END_PRAGMA)
.expect("Unable to find end pragma");
let suffix = &existing[index..];
// Write the prefix, new contents, and suffix.
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
write!(f, "{prefix}\n\n")?;
write!(f, "{output}")?;
write!(f, "{suffix}")?;
}
Ok(())
}

View File

@@ -12,6 +12,7 @@
)]
pub mod generate_check_code_prefix;
pub mod generate_options;
pub mod generate_rules_table;
pub mod generate_source_code;
pub mod print_ast;

View File

@@ -14,8 +14,8 @@
use anyhow::Result;
use clap::{Parser, Subcommand};
use ruff_dev::{
generate_check_code_prefix, generate_rules_table, generate_source_code, print_ast, print_cst,
print_tokens,
generate_check_code_prefix, generate_options, generate_rules_table, generate_source_code,
print_ast, print_cst, print_tokens,
};
#[derive(Parser)]
@@ -32,6 +32,8 @@ enum Commands {
GenerateCheckCodePrefix(generate_check_code_prefix::Cli),
/// Generate a Markdown-compatible table of supported lint rules.
GenerateRulesTable(generate_rules_table::Cli),
/// Generate a Markdown-compatible listing of configuration options.
GenerateOptions(generate_options::Cli),
/// Run round-trip source code generation on a given Python file.
GenerateSourceCode(generate_source_code::Cli),
/// Print the AST for a given Python file.
@@ -48,6 +50,7 @@ fn main() -> Result<()> {
Commands::GenerateCheckCodePrefix(args) => generate_check_code_prefix::main(args)?,
Commands::GenerateRulesTable(args) => generate_rules_table::main(args)?,
Commands::GenerateSourceCode(args) => generate_source_code::main(args)?,
Commands::GenerateOptions(args) => generate_options::main(args)?,
Commands::PrintAST(args) => print_ast::main(args)?,
Commands::PrintCST(args) => print_cst::main(args)?,
Commands::PrintTokens(args) => print_tokens::main(args)?,

13
ruff_macros/Cargo.toml Normal file
View File

@@ -0,0 +1,13 @@
[package]
name = "ruff_macros"
version = "0.0.161"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = { version = "1.0.47" }
quote = { version = "1.0.21" }
syn = { version = "1.0.103", features = ["derive", "parsing"] }
textwrap = { version = "0.16.0" }

177
ruff_macros/src/lib.rs Normal file
View File

@@ -0,0 +1,177 @@
#![allow(
clippy::collapsible_else_if,
clippy::collapsible_if,
clippy::implicit_hasher,
clippy::match_same_arms,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
clippy::module_name_repetitions,
clippy::must_use_candidate,
clippy::similar_names,
clippy::too_many_lines
)]
use quote::{quote, quote_spanned};
use syn::parse::{Parse, ParseStream};
use syn::token::Comma;
use syn::{
parse_macro_input, AngleBracketedGenericArguments, Attribute, Data, DataStruct, DeriveInput,
Field, Fields, Lit, LitStr, Path, PathArguments, PathSegment, Token, Type, TypePath,
};
#[proc_macro_derive(ConfigurationOptions, attributes(option, option_group))]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
derive_impl(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let DeriveInput { ident, data, .. } = input;
match data {
Data::Struct(DataStruct {
fields: Fields::Named(fields),
..
}) => {
let mut output = vec![];
for field in fields.named.iter() {
if let Some(attr) = field.attrs.iter().find(|a| a.path.is_ident("option")) {
output.push(handle_option(field, attr)?);
};
if field.attrs.iter().any(|a| a.path.is_ident("option_group")) {
output.push(handle_option_group(field)?);
};
}
Ok(quote! {
use crate::settings::options_base::{OptionEntry, OptionField, OptionGroup, ConfigurationOptions};
#[automatically_derived]
impl ConfigurationOptions for #ident {
fn get_available_options() -> Vec<OptionEntry> {
vec![#(#output),*]
}
}
})
}
_ => Err(syn::Error::new(
ident.span(),
"Can only derive ConfigurationOptions from structs with named fields.",
)),
}
}
/// For a field with type `Option<Foobar>` where `Foobar` itself is a struct
/// deriving `ConfigurationOptions`, create code that calls retrieves options
/// from that group: `Foobar::get_available_options()`
fn handle_option_group(field: &Field) -> syn::Result<proc_macro2::TokenStream> {
// unwrap is safe because we're only going over named fields
let ident = field.ident.as_ref().unwrap();
match &field.ty {
Type::Path(TypePath {
path: Path { segments, .. },
..
}) => match segments.first() {
Some(PathSegment {
ident: type_ident,
arguments:
PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }),
..
}) if type_ident == "Option" => {
let path = &args[0];
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
Ok(quote_spanned!(
ident.span() => OptionEntry::Group(OptionGroup {
name: #kebab_name,
fields: #path::get_available_options(),
})
))
}
_ => Err(syn::Error::new(
ident.span(),
"Expected `Option<_>` as type.",
)),
},
_ => Err(syn::Error::new(ident.span(), "Expected type.")),
}
}
/// Parse an `#[option(doc="...", default="...", value_type="...",
/// example="...")]` attribute and return data in the form of an `OptionField`.
fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::TokenStream> {
// unwrap is safe because we're only going over named fields
let ident = field.ident.as_ref().unwrap();
let FieldAttributes {
doc,
default,
value_type,
example,
} = attr.parse_args::<FieldAttributes>()?;
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
Ok(quote_spanned!(
ident.span() => OptionEntry::Field(OptionField {
name: #kebab_name,
doc: &#doc,
default: &#default,
value_type: &#value_type,
example: &#example,
})
))
}
#[derive(Debug)]
struct FieldAttributes {
doc: String,
default: String,
value_type: String,
example: String,
}
impl Parse for FieldAttributes {
fn parse(input: ParseStream) -> syn::Result<Self> {
let doc = _parse_key_value(input, "doc")?;
input.parse::<Comma>()?;
let default = _parse_key_value(input, "default")?;
input.parse::<Comma>()?;
let value_type = _parse_key_value(input, "value_type")?;
input.parse::<Comma>()?;
let example = _parse_key_value(input, "example")?;
if !input.is_empty() {
input.parse::<Comma>()?;
}
Ok(FieldAttributes {
doc: textwrap::dedent(&doc).trim_matches('\n').to_string(),
default,
value_type,
example: textwrap::dedent(&example).trim_matches('\n').to_string(),
})
}
}
fn _parse_key_value(input: ParseStream, name: &str) -> syn::Result<String> {
let ident: proc_macro2::Ident = input.parse()?;
if ident != name {
return Err(syn::Error::new(
ident.span(),
format!("Expected `{name}` name"),
));
}
input.parse::<Token![=]>()?;
let value: Lit = input.parse()?;
match &value {
Lit::Str(v) => Ok(v.value()),
_ => Err(syn::Error::new(value.span(), "Expected literal string")),
}
}

View File

@@ -61,15 +61,6 @@ pub fn dealias_call_path<'a>(
}
}
/// 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 {
ExprKind::Attribute { attr, .. } => target == attr,
ExprKind::Name { id, .. } => target == id,
_ => false,
}
}
/// Return `true` if the `Expr` is a reference to `${module}.${target}`.
///
/// Useful for, e.g., ensuring that a `Union` reference represents
@@ -310,6 +301,16 @@ pub fn match_trailing_content(stmt: &Stmt, locator: &SourceCodeLocator) -> bool
false
}
/// Return the number of trailing empty lines following a statement.
pub fn count_trailing_lines(stmt: &Stmt, locator: &SourceCodeLocator) -> usize {
let suffix =
locator.slice_source_code_at(&Location::new(stmt.end_location.unwrap().row() + 1, 0));
suffix
.lines()
.take_while(|line| line.trim().is_empty())
.count()
}
#[cfg(test)]
mod tests {
use anyhow::Result;

View File

@@ -1,4 +1,7 @@
use rustpython_ast::{Cmpop, Located};
use rustpython_parser::ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
use rustpython_parser::lexer;
use rustpython_parser::lexer::Tok;
use crate::ast::types::{BindingKind, Scope};
@@ -91,22 +94,275 @@ pub fn in_nested_block<'a>(parents: &mut impl Iterator<Item = &'a Stmt>) -> bool
})
}
/// Check if a node represents an unpacking assignment.
pub fn is_unpacking_assignment(stmt: &Stmt) -> bool {
if let StmtKind::Assign { targets, value, .. } = &stmt.node {
if !targets.iter().any(|child| {
matches!(
child.node,
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. }
)
}) {
return false;
/// Returns `true` if `parent` contains `child`.
fn contains(parent: &Expr, child: &Expr) -> bool {
match &parent.node {
ExprKind::BoolOp { values, .. } => values.iter().any(|parent| contains(parent, child)),
ExprKind::NamedExpr { target, value } => contains(target, child) || contains(value, child),
ExprKind::BinOp { left, right, .. } => contains(left, child) || contains(right, child),
ExprKind::UnaryOp { operand, .. } => contains(operand, child),
ExprKind::Lambda { body, .. } => contains(body, child),
ExprKind::IfExp { test, body, orelse } => {
contains(test, child) || contains(body, child) || contains(orelse, child)
}
match &value.node {
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. } => return false,
_ => {}
ExprKind::Dict { keys, values } => keys
.iter()
.chain(values.iter())
.any(|parent| contains(parent, child)),
ExprKind::Set { elts } => elts.iter().any(|parent| contains(parent, child)),
ExprKind::ListComp { elt, .. } => contains(elt, child),
ExprKind::SetComp { elt, .. } => contains(elt, child),
ExprKind::DictComp { key, value, .. } => contains(key, child) || contains(value, child),
ExprKind::GeneratorExp { elt, .. } => contains(elt, child),
ExprKind::Await { value } => contains(value, child),
ExprKind::Yield { value } => value.as_ref().map_or(false, |value| contains(value, child)),
ExprKind::YieldFrom { value } => contains(value, child),
ExprKind::Compare {
left, comparators, ..
} => contains(left, child) || comparators.iter().any(|parent| contains(parent, child)),
ExprKind::Call {
func,
args,
keywords,
} => {
contains(func, child)
|| args.iter().any(|parent| contains(parent, child))
|| keywords
.iter()
.any(|keyword| contains(&keyword.node.value, child))
}
ExprKind::FormattedValue {
value, format_spec, ..
} => {
contains(value, child)
|| format_spec
.as_ref()
.map_or(false, |value| contains(value, child))
}
ExprKind::JoinedStr { values } => values.iter().any(|parent| contains(parent, child)),
ExprKind::Constant { .. } => false,
ExprKind::Attribute { value, .. } => contains(value, child),
ExprKind::Subscript { value, slice, .. } => {
contains(value, child) || contains(slice, child)
}
ExprKind::Starred { value, .. } => contains(value, child),
ExprKind::Name { .. } => parent == child,
ExprKind::List { elts, .. } => elts.iter().any(|parent| contains(parent, child)),
ExprKind::Tuple { elts, .. } => elts.iter().any(|parent| contains(parent, child)),
ExprKind::Slice { lower, upper, step } => {
lower.as_ref().map_or(false, |value| contains(value, child))
|| upper.as_ref().map_or(false, |value| contains(value, child))
|| step.as_ref().map_or(false, |value| contains(value, child))
}
return true;
}
false
}
/// Check if a node represents an unpacking assignment.
pub fn is_unpacking_assignment(parent: &Stmt, child: &Expr) -> bool {
match &parent.node {
StmtKind::With { items, .. } => items.iter().any(|item| {
if let Some(optional_vars) = &item.optional_vars {
if matches!(optional_vars.node, ExprKind::Tuple { .. }) {
if contains(optional_vars, child) {
return true;
}
}
}
false
}),
StmtKind::Assign { targets, value, .. } => {
// In `(a, b) = (1, 2)`, `(1, 2)` is the target, and it is a tuple.
let value_is_tuple = matches!(
&value.node,
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. }
);
// In `(a, b) = coords = (1, 2)`, `(a, b)` and `coords` are the targets, and
// `(a, b`) is a tuple. (We use "tuple" as a placeholder for any
// unpackable expression.)
let targets_are_tuples = targets.iter().all(|item| {
matches!(
item.node,
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. }
)
});
// If we're looking at `a` in `(a, b) = coords = (1, 2)`, then we should
// identify that the current expression is in a tuple.
let child_in_tuple = targets_are_tuples
|| targets.iter().any(|item| {
matches!(
item.node,
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. }
) && contains(item, child)
});
// If our child is a tuple, and value is not, it's always an unpacking
// expression. Ex) `x, y = tup`
if child_in_tuple && !value_is_tuple {
return true;
}
// If our child isn't a tuple, but value is, it's never an unpacking expression.
// Ex) `coords = (1, 2)`
if !child_in_tuple && value_is_tuple {
return false;
}
// If our target and the value are both tuples, then it's an unpacking
// expression assuming there's at least one non-tuple child.
// Ex) Given `(x, y) = coords = 1, 2`, `(x, y)` is considered an unpacking
// expression. Ex) Given `(x, y) = (a, b) = 1, 2`, `(x, y)` isn't
// considered an unpacking expression.
if child_in_tuple && value_is_tuple {
return !targets_are_tuples;
}
false
}
_ => false,
}
}
pub type LocatedCmpop<U = ()> = Located<Cmpop, U>;
/// Extract all `Cmpop` operators from a source code snippet, with appropriate
/// ranges.
///
/// `RustPython` doesn't include line and column information on `Cmpop` nodes.
/// `CPython` doesn't either. This method iterates over the token stream and
/// re-identifies `Cmpop` nodes, annotating them with valid ranges.
pub fn locate_cmpops(contents: &str) -> Vec<LocatedCmpop> {
let mut tok_iter = lexer::make_tokenizer(contents)
.flatten()
.into_iter()
.peekable();
let mut ops: Vec<LocatedCmpop> = vec![];
let mut count: usize = 0;
loop {
let Some((start, tok, end)) = tok_iter.next() else {
break;
};
if matches!(tok, Tok::Lpar) {
count += 1;
continue;
} else if matches!(tok, Tok::Rpar) {
count -= 1;
continue;
}
if count == 0 {
match tok {
Tok::Not => {
if let Some((_, _, end)) =
tok_iter.next_if(|(_, tok, _)| matches!(tok, Tok::In))
{
ops.push(LocatedCmpop::new(start, end, Cmpop::NotIn));
}
}
Tok::In => {
ops.push(LocatedCmpop::new(start, end, Cmpop::In));
}
Tok::Is => {
if let Some((_, _, end)) =
tok_iter.next_if(|(_, tok, _)| matches!(tok, Tok::Not))
{
ops.push(LocatedCmpop::new(start, end, Cmpop::IsNot));
} else {
ops.push(LocatedCmpop::new(start, end, Cmpop::Is));
}
}
Tok::NotEqual => {
ops.push(LocatedCmpop::new(start, end, Cmpop::NotEq));
}
Tok::EqEqual => {
ops.push(LocatedCmpop::new(start, end, Cmpop::Eq));
}
Tok::GreaterEqual => {
ops.push(LocatedCmpop::new(start, end, Cmpop::GtE));
}
Tok::Greater => {
ops.push(LocatedCmpop::new(start, end, Cmpop::Gt));
}
Tok::LessEqual => {
ops.push(LocatedCmpop::new(start, end, Cmpop::LtE));
}
Tok::Less => {
ops.push(LocatedCmpop::new(start, end, Cmpop::Lt));
}
_ => {}
}
}
}
ops
}
#[cfg(test)]
mod tests {
use rustpython_ast::{Cmpop, Location};
use crate::ast::operations::{locate_cmpops, LocatedCmpop};
#[test]
fn locates_cmpops() {
assert_eq!(
locate_cmpops("x == 1"),
vec![LocatedCmpop::new(
Location::new(1, 2),
Location::new(1, 4),
Cmpop::Eq
)]
);
assert_eq!(
locate_cmpops("x != 1"),
vec![LocatedCmpop::new(
Location::new(1, 2),
Location::new(1, 4),
Cmpop::NotEq
)]
);
assert_eq!(
locate_cmpops("x is 1"),
vec![LocatedCmpop::new(
Location::new(1, 2),
Location::new(1, 4),
Cmpop::Is
)]
);
assert_eq!(
locate_cmpops("x is not 1"),
vec![LocatedCmpop::new(
Location::new(1, 2),
Location::new(1, 8),
Cmpop::IsNot
)]
);
assert_eq!(
locate_cmpops("x in 1"),
vec![LocatedCmpop::new(
Location::new(1, 2),
Location::new(1, 4),
Cmpop::In
)]
);
assert_eq!(
locate_cmpops("x not in 1"),
vec![LocatedCmpop::new(
Location::new(1, 2),
Location::new(1, 8),
Cmpop::NotIn
)]
);
assert_eq!(
locate_cmpops("x != (1 is not 2)"),
vec![LocatedCmpop::new(
Location::new(1, 2),
Location::new(1, 4),
Cmpop::NotEq
)]
);
}
}

View File

@@ -92,7 +92,7 @@ fn apply_fixes<'a>(
}
// Add the remaining content.
let slice = locator.slice_source_code_at(last_pos);
let slice = locator.slice_source_code_at(&last_pos);
output.append(&slice);
(Cow::from(output.finish()), num_fixed)

View File

@@ -5,7 +5,6 @@ use std::path::Path;
use log::error;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::Withitem;
use rustpython_parser::ast::{
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
KeywordData, Operator, Stmt, StmtKind, Suite,
@@ -20,7 +19,7 @@ use crate::ast::relocate::relocate_expr;
use crate::ast::types::{
Binding, BindingContext, BindingKind, ClassScope, FunctionScope, Node, Range, Scope, ScopeKind,
};
use crate::ast::visitor::{walk_excepthandler, walk_withitem, Visitor};
use crate::ast::visitor::{walk_excepthandler, Visitor};
use crate::ast::{helpers, operations, visitor};
use crate::checks::{Check, CheckCode, CheckKind, DeferralKeyword};
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
@@ -37,7 +36,7 @@ use crate::{
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_debugger,
flake8_print, flake8_return, flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle,
pyflakes, pygrep_hooks, pylint, pyupgrade, rules,
pyflakes, pygrep_hooks, pylint, pyupgrade,
};
const GLOBAL_SCOPE_INDEX: usize = 0;
@@ -82,7 +81,6 @@ pub struct Checker<'a> {
in_deferred_type_definition: bool,
in_literal: bool,
in_subscript: bool,
in_withitem: bool,
seen_import_boundary: bool,
futures_allowed: bool,
annotations_future_enabled: bool,
@@ -130,7 +128,6 @@ impl<'a> Checker<'a> {
in_deferred_type_definition: false,
in_literal: false,
in_subscript: false,
in_withitem: false,
seen_import_boundary: false,
futures_allowed: true,
annotations_future_enabled: false,
@@ -669,6 +666,9 @@ where
}
// pylint
if self.settings.enabled.contains(&CheckCode::PLC0414) {
pylint::plugins::useless_import_alias(self, alias);
}
if self.settings.enabled.contains(&CheckCode::PLR0402) {
pylint::plugins::consider_using_from_import(self, alias);
}
@@ -965,6 +965,11 @@ where
self.add_check(check);
}
}
// pylint
if self.settings.enabled.contains(&CheckCode::PLC0414) {
pylint::plugins::useless_import_alias(self, alias);
}
}
}
}
@@ -1009,16 +1014,27 @@ where
flake8_bugbear::plugins::assert_raises_exception(self, stmt, items);
}
}
StmtKind::While { .. } => {
StmtKind::While { body, orelse, .. } => {
if self.settings.enabled.contains(&CheckCode::B023) {
flake8_bugbear::plugins::function_uses_loop_variable(self, &Node::Stmt(stmt));
}
if self.settings.enabled.contains(&CheckCode::PLW0120) {
pylint::plugins::useless_else_on_loop(self, stmt, body, orelse);
}
}
StmtKind::For {
target, body, iter, ..
target,
body,
iter,
orelse,
..
}
| StmtKind::AsyncFor {
target, body, iter, ..
target,
body,
iter,
orelse,
..
} => {
if self.settings.enabled.contains(&CheckCode::B007) {
flake8_bugbear::plugins::unused_loop_control_variable(self, target, body);
@@ -1029,6 +1045,9 @@ where
if self.settings.enabled.contains(&CheckCode::B023) {
flake8_bugbear::plugins::function_uses_loop_variable(self, &Node::Stmt(stmt));
}
if self.settings.enabled.contains(&CheckCode::PLW0120) {
pylint::plugins::useless_else_on_loop(self, stmt, body, orelse);
}
}
StmtKind::Try { handlers, .. } => {
if self.settings.enabled.contains(&CheckCode::F707) {
@@ -1735,10 +1754,8 @@ where
if self.settings.enabled.contains(&CheckCode::PLC3002) {
pylint::plugins::unnecessary_direct_lambda_call(self, expr, func);
}
// Ruff
if self.settings.enabled.contains(&CheckCode::RUF004) {
rules::plugins::convert_exit_to_sys_exit(self, func);
if self.settings.enabled.contains(&CheckCode::PLR1722) {
pylint::plugins::consider_using_sys_exit(self, func);
}
}
ExprKind::Dict { keys, .. } => {
@@ -2465,13 +2482,6 @@ where
self.check_builtin_arg_shadowing(&arg.node.arg, Range::from_located(arg));
}
fn visit_withitem(&mut self, withitem: &'b Withitem) {
let prev_in_withitem = self.in_withitem;
self.in_withitem = true;
walk_withitem(self, withitem);
self.in_withitem = prev_in_withitem;
}
}
fn try_mark_used(scope: &mut Scope, scope_id: usize, id: &str, expr: &Expr) -> bool {
@@ -2778,7 +2788,7 @@ impl<'a> Checker<'a> {
return;
}
if self.in_withitem || operations::is_unpacking_assignment(parent) {
if operations::is_unpacking_assignment(parent, expr) {
self.add_binding(
id,
Binding {
@@ -3075,8 +3085,9 @@ impl<'a> Checker<'a> {
let child = self.parents[defined_by];
let parent = defined_in.map(|defined_in| self.parents[defined_in]);
let in_init_py = self.path.ends_with("__init__.py");
let fix = if !in_init_py && self.patch(&CheckCode::F401) {
let ignore_init = self.settings.ignore_init_module_imports
&& self.path.ends_with("__init__.py");
let fix = if !ignore_init && self.patch(&CheckCode::F401) {
let deleted: Vec<&Stmt> = self
.deletions
.iter()
@@ -3096,7 +3107,7 @@ impl<'a> Checker<'a> {
Some(fix)
}
Err(e) => {
error!("Failed to remove unused imports: {}", e);
error!("Failed to remove unused imports: {e}");
None
}
}
@@ -3106,7 +3117,7 @@ impl<'a> Checker<'a> {
for (full_name, range) in unused_imports {
let mut check = Check::new(
CheckKind::UnusedImport(full_name.clone(), in_init_py),
CheckKind::UnusedImport(full_name.clone(), ignore_init),
*range,
);
if let Some(fix) = fix.as_ref() {

View File

@@ -1,10 +1,10 @@
//! Lint rules based on import analysis.
use nohash_hasher::IntSet;
use rustpython_parser::ast::Suite;
use crate::ast::visitor::Visitor;
use crate::checks::Check;
use crate::directives::IsortDirectives;
use crate::isort;
use crate::isort::track::ImportTracker;
use crate::settings::Settings;
@@ -18,7 +18,7 @@ fn check_import_blocks(
) -> Vec<Check> {
let mut checks = vec![];
for block in tracker.into_iter() {
if !block.is_empty() {
if !block.imports.is_empty() {
if let Some(check) = isort::plugins::check_imports(&block, locator, settings, autofix) {
checks.push(check);
}
@@ -30,11 +30,11 @@ fn check_import_blocks(
pub fn check_imports(
python_ast: &Suite,
locator: &SourceCodeLocator,
exclusions: &IntSet<usize>,
directives: &IsortDirectives,
settings: &Settings,
autofix: bool,
) -> Vec<Check> {
let mut tracker = ImportTracker::new(exclusions);
let mut tracker = ImportTracker::new(directives);
for stmt in python_ast {
tracker.visit_stmt(stmt);
}

View File

@@ -4,9 +4,9 @@ use rustpython_parser::lexer::{LexResult, Tok};
use crate::checks::{Check, CheckCode};
use crate::lex::docstring_detection::StateMachine;
use crate::rules::checks::Context;
use crate::ruff::checks::Context;
use crate::source_code_locator::SourceCodeLocator;
use crate::{eradicate, flake8_quotes, pycodestyle, rules, Settings};
use crate::{eradicate, flake8_quotes, pycodestyle, ruff, Settings};
pub fn check_tokens(
locator: &SourceCodeLocator,
@@ -37,7 +37,7 @@ pub fn check_tokens(
// RUF001, RUF002, RUF003
if enforce_ambiguous_unicode_character {
if matches!(tok, Tok::String { .. } | Tok::Comment) {
checks.extend(rules::checks::ambiguous_unicode_character(
checks.extend(ruff::checks::ambiguous_unicode_character(
locator,
start,
end,

View File

@@ -93,12 +93,15 @@ pub enum CheckCode {
F841,
F901,
// pylint
PLC0414,
PLC2201,
PLC3002,
PLE1142,
PLR0206,
PLR0402,
PLR1701,
PLR1722,
PLW0120,
// flake8-builtins
A001,
A002,
@@ -291,7 +294,6 @@ pub enum CheckCode {
RUF001,
RUF002,
RUF003,
RUF004,
RUF100,
// pygrep-hooks
PGH001,
@@ -574,11 +576,14 @@ pub enum CheckKind {
YieldOutsideFunction(DeferralKeyword),
// pylint
ConsiderMergingIsinstance(String, Vec<String>),
UselessImportAlias,
MisplacedComparisonConstant(String),
UnnecessaryDirectLambdaCall,
PropertyWithParameters,
ConsiderUsingFromImport(String, String),
AwaitOutsideAsync,
UselessElseOnLoop,
ConsiderUsingSysExit,
// flake8-builtins
BuiltinVariableShadowing(String),
BuiltinArgumentShadowing(String),
@@ -773,7 +778,6 @@ pub enum CheckKind {
AmbiguousUnicodeCharacterString(char, char),
AmbiguousUnicodeCharacterDocstring(char, char),
AmbiguousUnicodeCharacterComment(char, char),
ConvertExitToSysExit,
UnusedNOQA(Option<Vec<String>>),
}
@@ -871,6 +875,7 @@ impl CheckCode {
CheckCode::F841 => CheckKind::UnusedVariable("...".to_string()),
CheckCode::F901 => CheckKind::RaiseNotImplemented,
// pylint
CheckCode::PLC0414 => CheckKind::UselessImportAlias,
CheckCode::PLC2201 => CheckKind::MisplacedComparisonConstant("...".to_string()),
CheckCode::PLC3002 => CheckKind::UnnecessaryDirectLambdaCall,
CheckCode::PLE1142 => CheckKind::AwaitOutsideAsync,
@@ -881,6 +886,8 @@ impl CheckCode {
CheckCode::PLR1701 => {
CheckKind::ConsiderMergingIsinstance("...".to_string(), vec!["...".to_string()])
}
CheckCode::PLR1722 => CheckKind::ConsiderUsingSysExit,
CheckCode::PLW0120 => CheckKind::UselessElseOnLoop,
// flake8-builtins
CheckCode::A001 => CheckKind::BuiltinVariableShadowing("...".to_string()),
CheckCode::A002 => CheckKind::BuiltinArgumentShadowing("...".to_string()),
@@ -1110,7 +1117,6 @@ impl CheckCode {
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
CheckCode::RUF003 => CheckKind::AmbiguousUnicodeCharacterComment('𝐁', 'B'),
CheckCode::RUF004 => CheckKind::ConvertExitToSysExit,
CheckCode::RUF100 => CheckKind::UnusedNOQA(None),
}
}
@@ -1299,12 +1305,15 @@ impl CheckCode {
CheckCode::N817 => CheckCategory::PEP8Naming,
CheckCode::N818 => CheckCategory::PEP8Naming,
CheckCode::PGH001 => CheckCategory::PygrepHooks,
CheckCode::PLC0414 => CheckCategory::Pylint,
CheckCode::PLC2201 => CheckCategory::Pylint,
CheckCode::PLC3002 => CheckCategory::Pylint,
CheckCode::PLE1142 => CheckCategory::Pylint,
CheckCode::PLR0206 => CheckCategory::Pylint,
CheckCode::PLR0402 => CheckCategory::Pylint,
CheckCode::PLR1701 => CheckCategory::Pylint,
CheckCode::PLR1722 => CheckCategory::Pylint,
CheckCode::PLW0120 => CheckCategory::Pylint,
CheckCode::Q000 => CheckCategory::Flake8Quotes,
CheckCode::Q001 => CheckCategory::Flake8Quotes,
CheckCode::Q002 => CheckCategory::Flake8Quotes,
@@ -1320,7 +1329,6 @@ impl CheckCode {
CheckCode::RUF001 => CheckCategory::Ruff,
CheckCode::RUF002 => CheckCategory::Ruff,
CheckCode::RUF003 => CheckCategory::Ruff,
CheckCode::RUF004 => CheckCategory::Ruff,
CheckCode::RUF100 => CheckCategory::Ruff,
CheckCode::S101 => CheckCategory::Flake8Bandit,
CheckCode::S102 => CheckCategory::Flake8Bandit,
@@ -1426,12 +1434,15 @@ impl CheckKind {
CheckKind::NoNewLineAtEndOfFile => &CheckCode::W292,
CheckKind::InvalidEscapeSequence(_) => &CheckCode::W605,
// pylint
CheckKind::UselessImportAlias => &CheckCode::PLC0414,
CheckKind::MisplacedComparisonConstant(..) => &CheckCode::PLC2201,
CheckKind::UnnecessaryDirectLambdaCall => &CheckCode::PLC3002,
CheckKind::AwaitOutsideAsync => &CheckCode::PLE1142,
CheckKind::ConsiderMergingIsinstance(..) => &CheckCode::PLR1701,
CheckKind::PropertyWithParameters => &CheckCode::PLR0206,
CheckKind::ConsiderUsingFromImport(..) => &CheckCode::PLR0402,
CheckKind::ConsiderUsingSysExit => &CheckCode::PLR1722,
CheckKind::UselessElseOnLoop => &CheckCode::PLW0120,
// flake8-builtins
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
@@ -1626,7 +1637,6 @@ impl CheckKind {
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
CheckKind::AmbiguousUnicodeCharacterComment(..) => &CheckCode::RUF003,
CheckKind::ConvertExitToSysExit => &CheckCode::RUF004,
CheckKind::UnusedNOQA(_) => &CheckCode::RUF100,
}
}
@@ -1793,9 +1803,12 @@ impl CheckKind {
CheckKind::UndefinedName(name) => {
format!("Undefined name `{name}`")
}
CheckKind::UnusedImport(name, in_init_py) => {
if *in_init_py {
format!("`{name}` imported but unused and missing from `__all__`")
CheckKind::UnusedImport(name, ignore_init) => {
if *ignore_init {
format!(
"`{name}` imported but unused; consider adding to `__all__` or using a \
redundant alias"
)
} else {
format!("`{name}` imported but unused")
}
@@ -1812,6 +1825,9 @@ impl CheckKind {
format!("Invalid escape sequence: '\\{char}'")
}
// pylint
CheckKind::UselessImportAlias => {
"Import alias does not rename original package".to_string()
}
CheckKind::ConsiderMergingIsinstance(obj, types) => {
let types = types.join(", ");
format!("Consider merging these isinstance calls: `isinstance({obj}, ({types}))`")
@@ -1831,6 +1847,10 @@ impl CheckKind {
CheckKind::AwaitOutsideAsync => {
"`await` should be used within an async function".to_string()
}
CheckKind::UselessElseOnLoop => "Else clause on loop without a break statement, \
remove the else and de-indent all the code inside it"
.to_string(),
CheckKind::ConsiderUsingSysExit => "Consider using `sys.exit()`".to_string(),
// flake8-builtins
CheckKind::BuiltinVariableShadowing(name) => {
format!("Variable `{name}` is shadowing a python builtin")
@@ -2418,9 +2438,6 @@ impl CheckKind {
'{representant}'?)"
)
}
CheckKind::ConvertExitToSysExit => "`exit()` is only available in the interpreter, \
use `sys.exit()` instead"
.to_string(),
CheckKind::UnusedNOQA(codes) => match codes {
None => "Unused `noqa` directive".to_string(),
Some(codes) => {
@@ -2472,7 +2489,7 @@ impl CheckKind {
| CheckKind::BlankLineBeforeSection(..)
| CheckKind::CapitalizeSectionName(..)
| CheckKind::CommentedOutCode
| CheckKind::ConvertExitToSysExit
| CheckKind::ConsiderUsingSysExit
| CheckKind::ConvertNamedTupleFunctionalToClass(..)
| CheckKind::ConvertTypedDictFunctionalToClass(..)
| CheckKind::DashedUnderlineAfterSection(..)
@@ -2480,6 +2497,8 @@ impl CheckKind {
| CheckKind::DoNotAssertFalse
| CheckKind::DoNotAssignLambda
| CheckKind::DuplicateHandlerException(..)
| CheckKind::EndsInPeriod
| CheckKind::EndsInPunctuation
| CheckKind::GetAttrWithConstant
| CheckKind::ImplicitReturn
| CheckKind::ImplicitReturnValue
@@ -2536,6 +2555,7 @@ impl CheckKind {
| CheckKind::UnusedNOQA(..)
| CheckKind::UsePEP585Annotation(..)
| CheckKind::UsePEP604Annotation
| CheckKind::UselessImportAlias
| CheckKind::UselessMetaclassType
| CheckKind::UselessObjectInheritance(..)
)
@@ -2599,8 +2619,7 @@ mod tests {
for check_code in CheckCode::iter() {
assert!(
CheckCode::from_str(check_code.as_ref()).is_ok(),
"{:?} could not be round-trip serialized.",
check_code
"{check_code:?} could not be round-trip serialized."
);
}
}

View File

@@ -280,6 +280,10 @@ pub enum CheckCodePrefix {
PGH00,
PGH001,
PLC,
PLC0,
PLC04,
PLC041,
PLC0414,
PLC2,
PLC22,
PLC220,
@@ -305,6 +309,13 @@ pub enum CheckCodePrefix {
PLR17,
PLR170,
PLR1701,
PLR172,
PLR1722,
PLW,
PLW0,
PLW01,
PLW012,
PLW0120,
Q,
Q0,
Q00,
@@ -329,7 +340,6 @@ pub enum CheckCodePrefix {
RUF001,
RUF002,
RUF003,
RUF004,
RUF1,
RUF10,
RUF100,
@@ -1200,7 +1210,13 @@ impl CheckCodePrefix {
CheckCodePrefix::PGH0 => vec![CheckCode::PGH001],
CheckCodePrefix::PGH00 => vec![CheckCode::PGH001],
CheckCodePrefix::PGH001 => vec![CheckCode::PGH001],
CheckCodePrefix::PLC => vec![CheckCode::PLC2201, CheckCode::PLC3002],
CheckCodePrefix::PLC => {
vec![CheckCode::PLC0414, CheckCode::PLC2201, CheckCode::PLC3002]
}
CheckCodePrefix::PLC0 => vec![CheckCode::PLC0414],
CheckCodePrefix::PLC04 => vec![CheckCode::PLC0414],
CheckCodePrefix::PLC041 => vec![CheckCode::PLC0414],
CheckCodePrefix::PLC0414 => vec![CheckCode::PLC0414],
CheckCodePrefix::PLC2 => vec![CheckCode::PLC2201],
CheckCodePrefix::PLC22 => vec![CheckCode::PLC2201],
CheckCodePrefix::PLC220 => vec![CheckCode::PLC2201],
@@ -1214,9 +1230,12 @@ impl CheckCodePrefix {
CheckCodePrefix::PLE11 => vec![CheckCode::PLE1142],
CheckCodePrefix::PLE114 => vec![CheckCode::PLE1142],
CheckCodePrefix::PLE1142 => vec![CheckCode::PLE1142],
CheckCodePrefix::PLR => {
vec![CheckCode::PLR0206, CheckCode::PLR0402, CheckCode::PLR1701]
}
CheckCodePrefix::PLR => vec![
CheckCode::PLR0206,
CheckCode::PLR0402,
CheckCode::PLR1701,
CheckCode::PLR1722,
],
CheckCodePrefix::PLR0 => vec![CheckCode::PLR0206, CheckCode::PLR0402],
CheckCodePrefix::PLR02 => vec![CheckCode::PLR0206],
CheckCodePrefix::PLR020 => vec![CheckCode::PLR0206],
@@ -1224,10 +1243,17 @@ impl CheckCodePrefix {
CheckCodePrefix::PLR04 => vec![CheckCode::PLR0402],
CheckCodePrefix::PLR040 => vec![CheckCode::PLR0402],
CheckCodePrefix::PLR0402 => vec![CheckCode::PLR0402],
CheckCodePrefix::PLR1 => vec![CheckCode::PLR1701],
CheckCodePrefix::PLR17 => vec![CheckCode::PLR1701],
CheckCodePrefix::PLR1 => vec![CheckCode::PLR1701, CheckCode::PLR1722],
CheckCodePrefix::PLR17 => vec![CheckCode::PLR1701, CheckCode::PLR1722],
CheckCodePrefix::PLR170 => vec![CheckCode::PLR1701],
CheckCodePrefix::PLR1701 => vec![CheckCode::PLR1701],
CheckCodePrefix::PLR172 => vec![CheckCode::PLR1722],
CheckCodePrefix::PLR1722 => vec![CheckCode::PLR1722],
CheckCodePrefix::PLW => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW0 => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW01 => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW012 => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW0120 => vec![CheckCode::PLW0120],
CheckCodePrefix::Q => vec![
CheckCode::Q000,
CheckCode::Q001,
@@ -1292,25 +1318,13 @@ impl CheckCodePrefix {
CheckCode::RUF001,
CheckCode::RUF002,
CheckCode::RUF003,
CheckCode::RUF004,
CheckCode::RUF100,
],
CheckCodePrefix::RUF0 => vec![
CheckCode::RUF001,
CheckCode::RUF002,
CheckCode::RUF003,
CheckCode::RUF004,
],
CheckCodePrefix::RUF00 => vec![
CheckCode::RUF001,
CheckCode::RUF002,
CheckCode::RUF003,
CheckCode::RUF004,
],
CheckCodePrefix::RUF0 => vec![CheckCode::RUF001, CheckCode::RUF002, CheckCode::RUF003],
CheckCodePrefix::RUF00 => vec![CheckCode::RUF001, CheckCode::RUF002, CheckCode::RUF003],
CheckCodePrefix::RUF001 => vec![CheckCode::RUF001],
CheckCodePrefix::RUF002 => vec![CheckCode::RUF002],
CheckCodePrefix::RUF003 => vec![CheckCode::RUF003],
CheckCodePrefix::RUF004 => vec![CheckCode::RUF004],
CheckCodePrefix::RUF1 => vec![CheckCode::RUF100],
CheckCodePrefix::RUF10 => vec![CheckCode::RUF100],
CheckCodePrefix::RUF100 => vec![CheckCode::RUF100],
@@ -1917,6 +1931,10 @@ impl CheckCodePrefix {
CheckCodePrefix::PGH00 => SuffixLength::Two,
CheckCodePrefix::PGH001 => SuffixLength::Three,
CheckCodePrefix::PLC => SuffixLength::Zero,
CheckCodePrefix::PLC0 => SuffixLength::One,
CheckCodePrefix::PLC04 => SuffixLength::Two,
CheckCodePrefix::PLC041 => SuffixLength::Three,
CheckCodePrefix::PLC0414 => SuffixLength::Four,
CheckCodePrefix::PLC2 => SuffixLength::One,
CheckCodePrefix::PLC22 => SuffixLength::Two,
CheckCodePrefix::PLC220 => SuffixLength::Three,
@@ -1942,6 +1960,13 @@ impl CheckCodePrefix {
CheckCodePrefix::PLR17 => SuffixLength::Two,
CheckCodePrefix::PLR170 => SuffixLength::Three,
CheckCodePrefix::PLR1701 => SuffixLength::Four,
CheckCodePrefix::PLR172 => SuffixLength::Three,
CheckCodePrefix::PLR1722 => SuffixLength::Four,
CheckCodePrefix::PLW => SuffixLength::Zero,
CheckCodePrefix::PLW0 => SuffixLength::One,
CheckCodePrefix::PLW01 => SuffixLength::Two,
CheckCodePrefix::PLW012 => SuffixLength::Three,
CheckCodePrefix::PLW0120 => SuffixLength::Four,
CheckCodePrefix::Q => SuffixLength::Zero,
CheckCodePrefix::Q0 => SuffixLength::One,
CheckCodePrefix::Q00 => SuffixLength::Two,
@@ -1966,7 +1991,6 @@ impl CheckCodePrefix {
CheckCodePrefix::RUF001 => SuffixLength::Three,
CheckCodePrefix::RUF002 => SuffixLength::Three,
CheckCodePrefix::RUF003 => SuffixLength::Three,
CheckCodePrefix::RUF004 => SuffixLength::Three,
CheckCodePrefix::RUF1 => SuffixLength::One,
CheckCodePrefix::RUF10 => SuffixLength::Two,
CheckCodePrefix::RUF100 => SuffixLength::Three,
@@ -2068,6 +2092,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[
CheckCodePrefix::PLC,
CheckCodePrefix::PLE,
CheckCodePrefix::PLR,
CheckCodePrefix::PLW,
CheckCodePrefix::Q,
CheckCodePrefix::RET,
CheckCodePrefix::RUF,

View File

@@ -30,9 +30,15 @@ impl Flags {
}
}
#[derive(Default)]
pub struct IsortDirectives {
pub exclusions: IntSet<usize>,
pub splits: Vec<usize>,
}
pub struct Directives {
pub noqa_line_for: IntMap<usize, usize>,
pub isort_exclusions: IntSet<usize>,
pub isort: IsortDirectives,
}
pub fn extract_directives(
@@ -46,10 +52,10 @@ pub fn extract_directives(
} else {
IntMap::default()
},
isort_exclusions: if flags.contains(Flags::ISORT) {
extract_isort_exclusions(lxr, locator)
isort: if flags.contains(Flags::ISORT) {
extract_isort_directives(lxr, locator)
} else {
IntSet::default()
IsortDirectives::default()
},
}
}
@@ -73,17 +79,32 @@ pub fn extract_noqa_line_for(lxr: &[LexResult]) -> IntMap<usize, usize> {
}
/// Extract a set of lines over which to disable isort.
pub fn extract_isort_exclusions(lxr: &[LexResult], locator: &SourceCodeLocator) -> IntSet<usize> {
pub fn extract_isort_directives(lxr: &[LexResult], locator: &SourceCodeLocator) -> IsortDirectives {
let mut exclusions: IntSet<usize> = IntSet::default();
let mut splits: Vec<usize> = Vec::default();
let mut skip_file: bool = false;
let mut off: Option<Location> = None;
let mut last: Option<Location> = None;
for &(start, ref tok, end) in lxr.iter().flatten() {
// TODO(charlie): Modify RustPython to include the comment text in the token.
last = Some(end);
// No need to keep processing, but we do need to determine the last token.
if skip_file {
continue;
}
if matches!(tok, Tok::Comment) {
// TODO(charlie): Modify RustPython to include the comment text in the token.
let comment_text = locator.slice_source_code_range(&Range {
location: start,
end_location: end,
});
if off.is_some() {
if comment_text == "# isort: split" {
splits.push(start.row());
} else if comment_text == "# isort: skip_file" {
skip_file = true;
} else if off.is_some() {
if comment_text == "# isort: on" {
if let Some(start) = off {
for row in start.row() + 1..=end.row() {
@@ -93,43 +114,50 @@ pub fn extract_isort_exclusions(lxr: &[LexResult], locator: &SourceCodeLocator)
off = None;
}
} else {
if comment_text.contains("isort: skip") || comment_text.contains("isort:skip") {
if 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
if skip_file {
// Enforce `isort: skip_file`.
if let Some(end) = last {
for row in 1..=end.row() {
exclusions.insert(row);
}
}
} else if let Some(start) = off {
// Enforce unterminated `isort: off`.
if let Some(end) = last {
for row in start.row() + 1..=end.row() {
exclusions.insert(row);
}
}
}
IsortDirectives { exclusions, splits }
}
#[cfg(test)]
mod tests {
use nohash_hasher::IntMap;
use nohash_hasher::{IntMap, IntSet};
use rustpython_parser::lexer;
use rustpython_parser::lexer::LexResult;
use crate::directives::extract_noqa_line_for;
use crate::directives::{extract_isort_directives, extract_noqa_line_for};
use crate::SourceCodeLocator;
#[test]
fn extraction() {
let empty: IntMap<usize, usize> = IntMap::default();
fn noqa_extraction() {
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
y = 2
z = x + 1",
)
.collect();
assert_eq!(extract_noqa_line_for(&lxr), empty);
assert_eq!(extract_noqa_line_for(&lxr), IntMap::default());
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"
@@ -138,7 +166,7 @@ y = 2
z = x + 1",
)
.collect();
assert_eq!(extract_noqa_line_for(&lxr), empty);
assert_eq!(extract_noqa_line_for(&lxr), IntMap::default());
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
@@ -147,7 +175,7 @@ z = x + 1
",
)
.collect();
assert_eq!(extract_noqa_line_for(&lxr), empty);
assert_eq!(extract_noqa_line_for(&lxr), IntMap::default());
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
@@ -157,7 +185,7 @@ z = x + 1
",
)
.collect();
assert_eq!(extract_noqa_line_for(&lxr), empty);
assert_eq!(extract_noqa_line_for(&lxr), IntMap::default());
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = '''abc
@@ -200,4 +228,106 @@ z = x + 1",
IntMap::from_iter([(2, 5), (3, 5), (4, 5)])
);
}
#[test]
fn isort_exclusions() {
let contents = "x = 1
y = 2
z = x + 1";
let lxr: Vec<LexResult> = lexer::make_tokenizer(contents).collect();
let locator = SourceCodeLocator::new(contents);
assert_eq!(
extract_isort_directives(&lxr, &locator).exclusions,
IntSet::default()
);
let contents = "# isort: off
x = 1
y = 2
# isort: on
z = x + 1";
let lxr: Vec<LexResult> = lexer::make_tokenizer(contents).collect();
let locator = SourceCodeLocator::new(contents);
assert_eq!(
extract_isort_directives(&lxr, &locator).exclusions,
IntSet::from_iter([2, 3, 4])
);
let contents = "# isort: off
x = 1
# isort: off
y = 2
# isort: on
z = x + 1
# isort: on";
let lxr: Vec<LexResult> = lexer::make_tokenizer(contents).collect();
let locator = SourceCodeLocator::new(contents);
assert_eq!(
extract_isort_directives(&lxr, &locator).exclusions,
IntSet::from_iter([2, 3, 4, 5])
);
let contents = "# isort: off
x = 1
y = 2
z = x + 1";
let lxr: Vec<LexResult> = lexer::make_tokenizer(contents).collect();
let locator = SourceCodeLocator::new(contents);
assert_eq!(
extract_isort_directives(&lxr, &locator).exclusions,
IntSet::from_iter([2, 3, 4])
);
let contents = "# isort: skip_file
x = 1
y = 2
z = x + 1";
let lxr: Vec<LexResult> = lexer::make_tokenizer(contents).collect();
let locator = SourceCodeLocator::new(contents);
assert_eq!(
extract_isort_directives(&lxr, &locator).exclusions,
IntSet::from_iter([1, 2, 3, 4])
);
let contents = "# isort: off
x = 1
# isort: on
y = 2
# isort: skip_file
z = x + 1";
let lxr: Vec<LexResult> = lexer::make_tokenizer(contents).collect();
let locator = SourceCodeLocator::new(contents);
assert_eq!(
extract_isort_directives(&lxr, &locator).exclusions,
IntSet::from_iter([1, 2, 3, 4, 5, 6])
);
}
#[test]
fn isort_splits() {
let contents = "x = 1
y = 2
z = x + 1";
let lxr: Vec<LexResult> = lexer::make_tokenizer(contents).collect();
let locator = SourceCodeLocator::new(contents);
assert_eq!(
extract_isort_directives(&lxr, &locator).splits,
Vec::<usize>::new()
);
let contents = "x = 1
y = 2
# isort: split
z = x + 1";
let lxr: Vec<LexResult> = lexer::make_tokenizer(contents).collect();
let locator = SourceCodeLocator::new(contents);
assert_eq!(extract_isort_directives(&lxr, &locator).splits, vec![3]);
let contents = "x = 1
y = 2 # isort: split
z = x + 1";
let lxr: Vec<LexResult> = lexer::make_tokenizer(contents).collect();
let locator = SourceCodeLocator::new(contents);
assert_eq!(extract_isort_directives(&lxr, &locator).splits, vec![2]);
}
}

View File

@@ -7,20 +7,20 @@ use crate::visibility::{Modifier, VisibleScope};
/// Extract a docstring from a function or class body.
pub fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
if let Some(stmt) = suite.first() {
if let StmtKind::Expr { value } = &stmt.node {
if matches!(
&value.node,
ExprKind::Constant {
value: Constant::Str(_),
..
}
) {
return Some(value);
}
let stmt = suite.first()?;
let StmtKind::Expr { value } = &stmt.node else {
return None;
};
if !matches!(
&value.node,
ExprKind::Constant {
value: Constant::Str(_),
..
}
) {
return None;
}
None
Some(value)
}
/// Extract a `Definition` from the AST node defined by a `Stmt`.

View File

@@ -211,7 +211,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
.chain(args.kwonlyargs.iter())
.skip(
// If this is a non-static method, skip `cls` or `self`.
usize::from(!visibility::is_staticmethod(stmt)),
usize::from(!visibility::is_staticmethod(checker, stmt)),
)
{
// ANN401 for dynamically typed arguments
@@ -283,10 +283,10 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
}
// ANN101, ANN102
if !visibility::is_staticmethod(stmt) {
if !visibility::is_staticmethod(checker, stmt) {
if let Some(arg) = args.args.first() {
if arg.node.annotation.is_none() {
if visibility::is_classmethod(stmt) {
if visibility::is_classmethod(checker, stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN102) {
checker.add_check(Check::new(
CheckKind::MissingTypeCls(arg.node.arg.to_string()),
@@ -319,14 +319,14 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
return;
}
if visibility::is_classmethod(stmt) {
if visibility::is_classmethod(checker, stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN206) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeClassMethod(name.to_string()),
Range::from_located(stmt),
));
}
} else if visibility::is_staticmethod(stmt) {
} else if visibility::is_staticmethod(checker, stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN205) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeStaticMethod(name.to_string()),

View File

@@ -1,22 +1,51 @@
//! Settings for the `flake-annotations` plugin.
use ruff_macros::ConfigurationOptions;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
/// Allow omission of a return type hint for `__init__` if at least one
/// argument is annotated.
#[option(
doc = r#"
Whether to allow the omission of a return type hint for `__init__` if at least one
argument is annotated.
"#,
default = "false",
value_type = "bool",
example = "mypy-init-return = true"
)]
pub mypy_init_return: Option<bool>,
/// Suppress ANN000-level errors for dummy arguments, like `_`.
#[option(
doc = r#"
Whether to suppress `ANN000`-level errors for arguments matching the "dummy" variable
regex (like `_`).
"#,
default = "false",
value_type = "bool",
example = "suppress-dummy-args = true"
)]
pub suppress_dummy_args: Option<bool>,
/// Suppress ANN200-level errors for functions that meet one of the
/// following criteria:
/// - Contain no `return` statement
/// - Explicit `return` statement(s) all return `None` (explicitly or
/// implicitly).
#[option(
doc = r#"
Whether to suppress `ANN200`-level errors for functions that meet either of the
following criteria:
- Contain no `return` statement.
- Explicit `return` statement(s) all return `None` (explicitly or implicitly).
"#,
default = "false",
value_type = "bool",
example = "suppress-none-returning = true"
)]
pub suppress_none_returning: Option<bool>,
/// Suppress ANN401 for dynamically typed *args and **kwargs.
#[option(
doc = "Whether to suppress `ANN401` for dynamically typed `*args` and `**kwargs` \
arguments.",
default = "false",
value_type = "bool",
example = "allow-star-arg-any = true"
)]
pub allow_star_arg_any: Option<bool>,
}

View File

@@ -5,10 +5,11 @@ 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)));
}
let ExprKind::Name { id, .. } = &func.node else {
return None;
};
if id != "exec" {
return None;
}
None
Some(Check::new(CheckKind::ExecUsed, Range::from_located(expr)))
}

View File

@@ -5,16 +5,15 @@ 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),
));
}
let string = string_literal(default)?;
let kwarg_name = &arg.node.arg;
if !matches_password_name(kwarg_name) {
return None;
}
None
Some(Check::new(
CheckKind::HardcodedPasswordDefault(string.to_string()),
Range::from_located(default),
))
}
/// S107

View File

@@ -9,17 +9,15 @@ 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),
));
}
}
let string = string_literal(&keyword.node.value)?;
let arg = keyword.node.arg.as_ref()?;
if !matches_password_name(arg) {
return None;
}
None
Some(Check::new(
CheckKind::HardcodedPasswordFuncArg(string.to_string()),
Range::from_located(keyword),
))
})
.collect()
}

View File

@@ -29,15 +29,14 @@ pub fn compare_to_hardcoded_password_string(left: &Expr, comparators: &[Expr]) -
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),
));
}
let string = string_literal(comp)?;
if !is_password_target(left) {
return None;
}
None
Some(Check::new(
CheckKind::HardcodedPasswordString(string.to_string()),
Range::from_located(comp),
))
})
.collect()
}

View File

@@ -6,17 +6,18 @@ use crate::checks::{Check, CheckKind};
pub fn blind_except(checker: &mut Checker, handlers: &[Excepthandler]) {
for handler in handlers {
let ExcepthandlerKind::ExceptHandler { type_, .. } = &handler.node;
if let Some(type_) = type_ {
if let ExprKind::Name { id, .. } = &type_.node {
for exception in ["BaseException", "Exception"] {
if id == exception {
checker.add_check(Check::new(
CheckKind::BlindExcept,
Range::from_located(type_),
));
}
}
let ExcepthandlerKind::ExceptHandler { type_: Some(type_), .. } = &handler.node else {
continue;
};
let ExprKind::Name { id, .. } = &type_.node else {
continue;
};
for exception in ["BaseException", "Exception"] {
if id == exception {
checker.add_check(Check::new(
CheckKind::BlindExcept,
Range::from_located(type_),
));
}
}
}

View File

@@ -11,11 +11,10 @@ const FUNC_NAME_ALLOWLIST: &[&str] = &["get", "setdefault", "pop", "fromkeys"];
/// `true`, the function name must be explicitly allowed, and the argument must
/// be either the first or second argument in the call.
fn allow_boolean_trap(func: &Expr) -> bool {
if let ExprKind::Attribute { attr, .. } = &func.node {
FUNC_NAME_ALLOWLIST.contains(&attr.as_ref())
} else {
false
}
let ExprKind::Attribute { attr, .. } = &func.node else {
return false;
};
FUNC_NAME_ALLOWLIST.contains(&attr.as_ref())
}
fn is_boolean_arg(arg: &Expr) -> bool {
@@ -39,24 +38,26 @@ pub fn check_positional_boolean_in_def(checker: &mut Checker, arguments: &Argume
if arg.node.annotation.is_none() {
continue;
}
let Some(expr) = &arg.node.annotation else {
continue;
};
if let Some(expr) = &arg.node.annotation {
// check for both bool (python class) and 'bool' (string annotation)
let hint = match &expr.node {
ExprKind::Name { id, .. } => id == "bool",
ExprKind::Constant {
value: Constant::Str(value),
..
} => value == "bool",
_ => false,
};
if hint {
checker.add_check(Check::new(
CheckKind::BooleanPositionalArgInFunctionDefinition,
Range::from_located(arg),
));
}
// check for both bool (python class) and 'bool' (string annotation)
let hint = match &expr.node {
ExprKind::Name { id, .. } => id == "bool",
ExprKind::Constant {
value: Constant::Str(value),
..
} => value == "bool",
_ => false,
};
if !hint {
continue;
}
checker.add_check(Check::new(
CheckKind::BooleanPositionalArgInFunctionDefinition,
Range::from_located(arg),
));
}
}

View File

@@ -67,24 +67,28 @@ pub fn abstract_base_class(
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 bases.len() + keywords.len() != 1 {
return;
}
if !is_abc_class(
bases,
keywords,
&checker.from_imports,
&checker.import_aliases,
) {
return;
}
if let StmtKind::FunctionDef {
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;
}
let (StmtKind::FunctionDef {
decorator_list,
body,
..
@@ -93,36 +97,38 @@ pub fn abstract_base_class(
decorator_list,
body,
..
} = &stmt.node
{
let has_abstract_decorator = decorator_list
.iter()
.any(|d| is_abstractmethod(d, &checker.from_imports, &checker.import_aliases));
}) = &stmt.node else {
continue;
};
has_abstract_method |= has_abstract_decorator;
let has_abstract_decorator = decorator_list
.iter()
.any(|d| is_abstractmethod(d, &checker.from_imports, &checker.import_aliases));
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),
));
}
}
}
has_abstract_method |= has_abstract_decorator;
if !checker.settings.enabled.contains(&CheckCode::B027) {
continue;
}
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),
));
}
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),
));
}
}
}

View File

@@ -38,23 +38,24 @@ fn assertion_error(msg: Option<&Expr>) -> Stmt {
/// B011
pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: Option<&Expr>) {
if let ExprKind::Constant {
let ExprKind::Constant {
value: Constant::Bool(false),
..
} = &test.node
{
let mut check = Check::new(CheckKind::DoNotAssertFalse, Range::from_located(test));
if checker.patch(check.kind.code()) {
let mut generator = SourceGenerator::new();
generator.unparse_stmt(&assertion_error(msg));
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
stmt.location,
stmt.end_location.unwrap(),
));
}
} = &test.node else {
return;
};
let mut check = Check::new(CheckKind::DoNotAssertFalse, Range::from_located(test));
if checker.patch(check.kind.code()) {
let mut generator = SourceGenerator::new();
generator.unparse_stmt(&assertion_error(msg));
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
stmt.location,
stmt.end_location.unwrap(),
));
}
checker.add_check(check);
}
checker.add_check(check);
}

View File

@@ -1,25 +1,40 @@
use rustpython_ast::{ExprKind, Stmt, Withitem};
use crate::ast::helpers::match_name_or_attr;
use crate::ast::helpers::match_module_member;
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
/// B017
pub fn assert_raises_exception(checker: &mut Checker, stmt: &Stmt, items: &[Withitem]) {
if let Some(item) = items.first() {
let item_context = &item.context_expr;
if let ExprKind::Call { func, args, .. } = &item_context.node {
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,
Range::from_located(stmt),
));
}
}
let Some(item) = items.first() else {
return;
};
let item_context = &item.context_expr;
let ExprKind::Call { func, args, .. } = &item_context.node else {
return;
};
if args.len() != 1 {
return;
}
if item.optional_vars.is_some() {
return;
}
if !matches!(&func.node, ExprKind::Attribute { attr, .. } if attr == "assertRaises") {
return;
}
if !match_module_member(
args.first().unwrap(),
"",
"Exception",
&checker.from_imports,
&checker.import_aliases,
) {
return;
}
checker.add_check(Check::new(
CheckKind::NoAssertRaisesException,
Range::from_located(stmt),
));
}

View File

@@ -6,19 +6,24 @@ use crate::checks::{Check, CheckKind};
/// B003
pub fn assignment_to_os_environ(checker: &mut Checker, targets: &[Expr]) {
if targets.len() == 1 {
let target = &targets[0];
if let ExprKind::Attribute { value, attr, .. } = &target.node {
if attr == "environ" {
if let ExprKind::Name { id, .. } = &value.node {
if id == "os" {
checker.add_check(Check::new(
CheckKind::AssignmentToOsEnviron,
Range::from_located(target),
));
}
}
}
}
if targets.len() != 1 {
return;
}
let target = &targets[0];
let ExprKind::Attribute { value, attr, .. } = &target.node else {
return;
};
if attr != "environ" {
return;
}
let ExprKind::Name { id, .. } = &value.node else {
return;
};
if id != "os" {
return;
}
checker.add_check(Check::new(
CheckKind::AssignmentToOsEnviron,
Range::from_located(target),
));
}

View File

@@ -13,29 +13,30 @@ fn is_cache_func(checker: &Checker, expr: &Expr) -> bool {
/// 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),
));
if !matches!(checker.current_scope().kind, ScopeKind::Class(_)) {
return;
}
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),
));
}
}
}

View File

@@ -6,10 +6,11 @@ use crate::checks::{Check, CheckKind};
/// B016
pub fn cannot_raise_literal(checker: &mut Checker, expr: &Expr) {
if let ExprKind::Constant { .. } = &expr.node {
checker.add_check(Check::new(
CheckKind::CannotRaiseLiteral,
Range::from_located(expr),
));
}
let ExprKind::Constant { .. } = &expr.node else {
return;
};
checker.add_check(Check::new(
CheckKind::CannotRaiseLiteral,
Range::from_located(expr),
));
}

View File

@@ -79,33 +79,30 @@ pub fn duplicate_exceptions(checker: &mut Checker, stmt: &Stmt, handlers: &[Exce
let mut seen: BTreeSet<Vec<&str>> = BTreeSet::default();
let mut duplicates: BTreeSet<Vec<&str>> = BTreeSet::default();
for handler in handlers {
match &handler.node {
ExcepthandlerKind::ExceptHandler { type_, .. } => {
if let Some(type_) = type_ {
match &type_.node {
ExprKind::Attribute { .. } | ExprKind::Name { .. } => {
let call_path = helpers::collect_call_paths(type_);
if !call_path.is_empty() {
if seen.contains(&call_path) {
duplicates.insert(call_path);
} else {
seen.insert(call_path);
}
}
}
ExprKind::Tuple { elts, .. } => {
for name in duplicate_handler_exceptions(checker, type_, elts) {
if seen.contains(&name) {
duplicates.insert(name);
} else {
seen.insert(name);
}
}
}
_ => {}
let ExcepthandlerKind::ExceptHandler { type_: Some(type_), .. } = &handler.node else {
continue;
};
match &type_.node {
ExprKind::Attribute { .. } | ExprKind::Name { .. } => {
let call_path = helpers::collect_call_paths(type_);
if !call_path.is_empty() {
if seen.contains(&call_path) {
duplicates.insert(call_path);
} else {
seen.insert(call_path);
}
}
}
ExprKind::Tuple { elts, .. } => {
for name in duplicate_handler_exceptions(checker, type_, elts) {
if seen.contains(&name) {
duplicates.insert(name);
} else {
seen.insert(name);
}
}
}
_ => {}
}
}

View File

@@ -6,14 +6,17 @@ use crate::checks::{Check, CheckKind};
/// B021
pub fn f_string_docstring(checker: &mut Checker, body: &[Stmt]) {
if let Some(stmt) = body.first() {
if let StmtKind::Expr { value } = &stmt.node {
if let ExprKind::JoinedStr { .. } = value.node {
checker.add_check(Check::new(
CheckKind::FStringDocstring,
Range::from_located(stmt),
));
}
}
}
let Some(stmt) = body.first() else {
return;
};
let StmtKind::Expr { value } = &stmt.node else {
return;
};
let ExprKind::JoinedStr { .. } = value.node else {
return;
};
checker.add_check(Check::new(
CheckKind::FStringDocstring,
Range::from_located(stmt),
));
}

View File

@@ -71,29 +71,26 @@ where
}
fn is_nan_or_infinity(expr: &Expr, args: &[Expr]) -> bool {
if let ExprKind::Name { id, .. } = &expr.node {
if id == "float" {
if let Some(arg) = args.first() {
if let ExprKind::Constant {
value: Constant::Str(value),
..
} = &arg.node
{
let lowercased = value.to_lowercase();
return lowercased == "nan"
|| lowercased == "+nan"
|| lowercased == "-nan"
|| lowercased == "inf"
|| lowercased == "+inf"
|| lowercased == "-inf"
|| lowercased == "infinity"
|| lowercased == "+infinity"
|| lowercased == "-infinity";
}
}
}
let ExprKind::Name { id, .. } = &expr.node else {
return false;
};
if id != "float" {
return false;
}
false
let Some(arg) = args.first() else {
return false;
};
let ExprKind::Constant {
value: Constant::Str(value),
..
} = &arg.node else {
return false;
};
let lowercased = value.to_lowercase();
matches!(
lowercased.as_str(),
"nan" | "+nan" | "-nan" | "inf" | "+inf" | "-inf" | "infinity" | "+infinity" | "-infinity"
)
}
/// B008

View File

@@ -22,32 +22,39 @@ fn attribute(value: &Expr, attr: &str) -> Expr {
/// B009
pub fn getattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
if let ExprKind::Name { id, .. } = &func.node {
if id == "getattr" {
if let [obj, arg] = args {
if let ExprKind::Constant {
value: Constant::Str(value),
..
} = &arg.node
{
if IDENTIFIER_REGEX.is_match(value) && !KWLIST.contains(&value.as_str()) {
let mut check =
Check::new(CheckKind::GetAttrWithConstant, Range::from_located(expr));
if checker.patch(check.kind.code()) {
let mut generator = SourceGenerator::new();
generator.unparse_expr(&attribute(obj, value), 0);
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
}
checker.add_check(check);
}
}
}
let ExprKind::Name { id, .. } = &func.node else {
return;
};
if id != "getattr" {
return;
}
let [obj, arg] = args else {
return;
};
let ExprKind::Constant {
value: Constant::Str(value),
..
} = &arg.node else {
return;
};
if !IDENTIFIER_REGEX.is_match(value) {
return;
}
if KWLIST.contains(&value.as_str()) {
return;
}
let mut check = Check::new(CheckKind::GetAttrWithConstant, Range::from_located(expr));
if checker.patch(check.kind.code()) {
let mut generator = SourceGenerator::new();
generator.unparse_expr(&attribute(obj, value), 0);
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
}
checker.add_check(check);
}

View File

@@ -13,21 +13,18 @@ struct RaiseVisitor {
impl<'a> Visitor<'a> for RaiseVisitor {
fn visit_stmt(&mut self, stmt: &'a Stmt) {
match &stmt.node {
StmtKind::Raise { exc, cause } => {
if cause.is_none() {
if let Some(exc) = exc {
match &exc.node {
ExprKind::Name { id, .. } if is_lower(id) => {}
_ => {
self.checks.push(Check::new(
CheckKind::RaiseWithoutFromInsideExcept,
Range::from_located(stmt),
));
}
}
}
StmtKind::Raise {
exc: Some(exc),
cause: None,
} => match &exc.node {
ExprKind::Name { id, .. } if is_lower(id) => {}
_ => {
self.checks.push(Check::new(
CheckKind::RaiseWithoutFromInsideExcept,
Range::from_located(stmt),
));
}
}
},
StmtKind::ClassDef { .. }
| StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }

View File

@@ -39,46 +39,47 @@ fn match_tuple_range<T>(located: &Located<T>, locator: &SourceCodeLocator) -> Re
}
}
}
if let (Some(location), Some(end_location)) = (location, end_location) {
Ok(Range {
location,
end_location,
})
} else {
let (Some(location), Some(end_location)) = (location, end_location) else {
bail!("Unable to find left and right parentheses");
}
};
Ok(Range {
location,
end_location,
})
}
/// B013
pub fn redundant_tuple_in_exception_handler(checker: &mut Checker, handlers: &[Excepthandler]) {
for handler in handlers {
let ExcepthandlerKind::ExceptHandler { type_, .. } = &handler.node;
if let Some(type_) = type_ {
if let ExprKind::Tuple { elts, .. } = &type_.node {
if let [elt] = &elts[..] {
let mut check = Check::new(
CheckKind::RedundantTupleInExceptionHandler(elt.to_string()),
Range::from_located(type_),
);
if checker.patch(check.kind.code()) {
let mut generator = SourceGenerator::new();
generator.unparse_expr(elt, 0);
if let Ok(content) = generator.generate() {
match match_tuple_range(handler, checker.locator) {
Ok(range) => {
check.amend(Fix::replacement(
content,
range.location,
range.end_location,
));
}
Err(e) => error!("Failed to locate parentheses: {}", e),
}
}
let ExcepthandlerKind::ExceptHandler { type_: Some(type_), .. } = &handler.node else {
continue;
};
let ExprKind::Tuple { elts, .. } = &type_.node else {
continue;
};
let [elt] = &elts[..] else {
continue;
};
let mut check = Check::new(
CheckKind::RedundantTupleInExceptionHandler(elt.to_string()),
Range::from_located(type_),
);
if checker.patch(check.kind.code()) {
let mut generator = SourceGenerator::new();
generator.unparse_expr(elt, 0);
if let Ok(content) = generator.generate() {
match match_tuple_range(handler, checker.locator) {
Ok(range) => {
check.amend(Fix::replacement(
content,
range.location,
range.end_location,
));
}
checker.add_check(check);
Err(e) => error!("Failed to locate parentheses: {e}"),
}
}
}
checker.add_check(check);
}
}

View File

@@ -35,31 +35,37 @@ fn assignment(obj: &Expr, name: &str, value: &Expr) -> Result<String> {
/// B010
pub fn setattr_with_constant(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
if let ExprKind::Name { id, .. } = &func.node {
if id == "setattr" {
if let [obj, name, value] = args {
if let ExprKind::Constant {
value: Constant::Str(name),
..
} = &name.node
{
if IDENTIFIER_REGEX.is_match(name) && !KWLIST.contains(&name.as_str()) {
let mut check =
Check::new(CheckKind::SetAttrWithConstant, Range::from_located(expr));
if checker.patch(check.kind.code()) {
match assignment(obj, name, value) {
Ok(content) => check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
)),
Err(e) => error!("Failed to fix invalid comparison: {}", e),
};
}
checker.add_check(check);
}
}
}
}
let ExprKind::Name { id, .. } = &func.node else {
return;
};
if id != "setattr" {
return;
}
let [obj, name, value] = args else {
return;
};
let ExprKind::Constant {
value: Constant::Str(name),
..
} = &name.node else {
return;
};
if !IDENTIFIER_REGEX.is_match(name) {
return;
}
if KWLIST.contains(&name.as_str()) {
return;
}
let mut check = Check::new(CheckKind::SetAttrWithConstant, Range::from_located(expr));
if checker.patch(check.kind.code()) {
match assignment(obj, name, value) {
Ok(content) => check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
)),
Err(e) => error!("Failed to fix invalid comparison: {e}"),
};
}
checker.add_check(check);
}

View File

@@ -10,16 +10,19 @@ pub fn star_arg_unpacking_after_keyword_arg(
args: &[Expr],
keywords: &[Keyword],
) {
if let Some(keyword) = keywords.first() {
for arg in args {
if let ExprKind::Starred { .. } = arg.node {
if arg.location > keyword.location {
checker.add_check(Check::new(
CheckKind::StarArgUnpackingAfterKeywordArg,
Range::from_located(arg),
));
}
}
let Some(keyword) = keywords.first() else {
return;
};
for arg in args {
let ExprKind::Starred { .. } = arg.node else {
continue;
};
if arg.location <= keyword.location {
continue;
}
checker.add_check(Check::new(
CheckKind::StarArgUnpackingAfterKeywordArg,
Range::from_located(arg),
));
}
}

View File

@@ -7,22 +7,27 @@ use crate::checks::{Check, CheckKind};
/// B005
pub fn strip_with_multi_characters(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
if let ExprKind::Attribute { attr, .. } = &func.node {
if attr == "strip" || attr == "lstrip" || attr == "rstrip" {
if args.len() == 1 {
if let ExprKind::Constant {
value: Constant::Str(value),
..
} = &args[0].node
{
if value.len() > 1 && value.chars().unique().count() != value.len() {
checker.add_check(Check::new(
CheckKind::StripWithMultiCharacters,
Range::from_located(expr),
));
}
}
}
}
let ExprKind::Attribute { attr, .. } = &func.node else {
return;
};
if !matches!(attr.as_str(), "strip" | "lstrip" | "rstrip") {
return;
}
if args.len() != 1 {
return;
}
let ExprKind::Constant {
value: Constant::Str(value),
..
} = &args[0].node else {
return;
};
if value.len() > 1 && value.chars().unique().count() != value.len() {
checker.add_check(Check::new(
CheckKind::StripWithMultiCharacters,
Range::from_located(expr),
));
}
}

View File

@@ -6,14 +6,17 @@ use crate::checks::{Check, CheckKind};
/// B002
pub fn unary_prefix_increment(checker: &mut Checker, expr: &Expr, op: &Unaryop, operand: &Expr) {
if matches!(op, Unaryop::UAdd) {
if let ExprKind::UnaryOp { op, .. } = &operand.node {
if matches!(op, Unaryop::UAdd) {
checker.add_check(Check::new(
CheckKind::UnaryPrefixIncrement,
Range::from_located(expr),
));
}
}
if !matches!(op, Unaryop::UAdd) {
return;
}
let ExprKind::UnaryOp { op, .. } = &operand.node else {
return;
};
if !matches!(op, Unaryop::UAdd) {
return;
}
checker.add_check(Check::new(
CheckKind::UnaryPrefixIncrement,
Range::from_located(expr),
));
}

View File

@@ -6,22 +6,27 @@ use crate::checks::{Check, CheckKind};
/// B004
pub fn unreliable_callable_check(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
if let ExprKind::Name { id, .. } = &func.node {
if id == "getattr" || id == "hasattr" {
if args.len() >= 2 {
if let ExprKind::Constant {
value: Constant::Str(s),
..
} = &args[1].node
{
if s == "__call__" {
checker.add_check(Check::new(
CheckKind::UnreliableCallableCheck,
Range::from_located(expr),
));
}
}
}
}
let ExprKind::Name { id, .. } = &func.node else {
return;
};
if id != "getattr" && id != "hasattr" {
return;
}
if args.len() < 2 {
return;
};
let ExprKind::Constant {
value: Constant::Str(s),
..
} = &args[1].node else
{
return;
};
if s != "__call__" {
return;
}
checker.add_check(Check::new(
CheckKind::UnreliableCallableCheck,
Range::from_located(expr),
));
}

View File

@@ -1,10 +1,23 @@
//! Settings for the `flake8-bugbear` plugin.
use ruff_macros::ConfigurationOptions;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
#[option(
doc = r#"
Additional callable functions to consider "immutable" when evaluating, e.g.,
`no-mutable-default-argument` checks (`B006`).
"#,
default = r#"[]"#,
value_type = "Vec<String>",
example = r#"
# Allow default arguments like, e.g., `data: List[str] = fastapi.Query(None)`.
extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"]
"#
)]
pub extend_immutable_calls: Option<Vec<String>>,
}

View File

@@ -62,7 +62,7 @@ pub fn unnecessary_generator_list(
if fix {
match fixes::fix_unnecessary_generator_list(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
Err(e) => error!("Failed to generate fix: {e}"),
}
}
return Some(check);
@@ -86,7 +86,7 @@ pub fn unnecessary_generator_set(
if fix {
match fixes::fix_unnecessary_generator_set(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
Err(e) => error!("Failed to generate fix: {e}"),
}
}
return Some(check);
@@ -112,7 +112,7 @@ pub fn unnecessary_generator_dict(
if fix {
match fixes::fix_unnecessary_generator_dict(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
Err(e) => error!("Failed to generate fix: {e}"),
}
}
return Some(check);
@@ -139,7 +139,7 @@ pub fn unnecessary_list_comprehension_set(
if fix {
match fixes::fix_unnecessary_list_comprehension_set(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
Err(e) => error!("Failed to generate fix: {e}"),
}
}
return Some(check);
@@ -158,22 +158,23 @@ pub fn unnecessary_list_comprehension_dict(
location: Range,
) -> Option<Check> {
let argument = exactly_one_argument_with_matching_function("dict", func, args, keywords)?;
if let ExprKind::ListComp { elt, .. } = &argument {
match &elt.node {
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
let mut check = Check::new(CheckKind::UnnecessaryListComprehensionDict, location);
if fix {
match fixes::fix_unnecessary_list_comprehension_dict(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
return Some(check);
}
_ => {}
let ExprKind::ListComp { elt, .. } = &argument else {
return None;
};
let ExprKind::Tuple { elts, .. } = &elt.node else {
return None;
};
if elts.len() != 2 {
return None;
}
let mut check = Check::new(CheckKind::UnnecessaryListComprehensionDict, location);
if fix {
match fixes::fix_unnecessary_list_comprehension_dict(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {e}"),
}
}
None
Some(check)
}
/// C405 (`set([1, 2])`)
@@ -196,7 +197,7 @@ pub fn unnecessary_literal_set(
if fix {
match fixes::fix_unnecessary_literal_set(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
Err(e) => error!("Failed to generate fix: {e}"),
}
}
Some(check)
@@ -232,7 +233,7 @@ pub fn unnecessary_literal_dict(
if fix {
match fixes::fix_unnecessary_literal_dict(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
Err(e) => error!("Failed to generate fix: {e}"),
}
}
Some(check)
@@ -268,7 +269,7 @@ pub fn unnecessary_collection_call(
if fix {
match fixes::fix_unnecessary_collection_call(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
Err(e) => error!("Failed to generate fix: {e}"),
}
}
Some(check)
@@ -296,7 +297,7 @@ pub fn unnecessary_literal_within_tuple_call(
if fix {
match fixes::fix_unnecessary_literal_within_tuple_call(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
Err(e) => error!("Failed to generate fix: {e}"),
}
}
Some(check)
@@ -324,7 +325,7 @@ pub fn unnecessary_literal_within_list_call(
if fix {
match fixes::fix_unnecessary_literal_within_list_call(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
Err(e) => error!("Failed to generate fix: {e}"),
}
}
Some(check)
@@ -340,17 +341,17 @@ pub fn unnecessary_list_call(
location: Range,
) -> Option<Check> {
let argument = first_argument_with_matching_function("list", func, args)?;
if let ExprKind::ListComp { .. } = argument {
let mut check = Check::new(CheckKind::UnnecessaryListCall, location);
if fix {
match fixes::fix_unnecessary_list_call(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
return Some(check);
if !matches!(argument, ExprKind::ListComp { .. }) {
return None;
}
None
let mut check = Check::new(CheckKind::UnnecessaryListCall, location);
if fix {
match fixes::fix_unnecessary_list_call(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {e}"),
}
}
Some(check)
}
/// C413
@@ -366,22 +367,24 @@ pub fn unnecessary_call_around_sorted(
if !(outer == "list" || outer == "reversed") {
return None;
}
if let ExprKind::Call { func, .. } = &args.first()?.node {
if function_name(func)? == "sorted" {
let mut check = Check::new(
CheckKind::UnnecessaryCallAroundSorted(outer.to_string()),
location,
);
if fix {
match fixes::fix_unnecessary_call_around_sorted(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
}
}
return Some(check);
let ExprKind::Call { func, .. } = &args.first()?.node else {
return None;
};
if function_name(func)? != "sorted" {
return None;
}
let mut check = Check::new(
CheckKind::UnnecessaryCallAroundSorted(outer.to_string()),
location,
);
if fix {
match fixes::fix_unnecessary_call_around_sorted(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {e}"),
}
}
None
Some(check)
}
/// C414
@@ -402,25 +405,28 @@ pub fn unnecessary_double_cast_or_process(
return None;
}
if let ExprKind::Call { func, .. } = &args.first()?.node {
let inner = function_name(func)?;
// Ex) set(tuple(...))
if (outer == "set" || outer == "sorted")
&& (inner == "list" || inner == "tuple" || inner == "reversed" || inner == "sorted")
{
return Some(new_check(inner, outer, location));
}
let ExprKind::Call { func, .. } = &args.first()?.node else {
return None;
};
// Ex) list(tuple(...))
if (outer == "list" || outer == "tuple") && (inner == "list" || inner == "tuple") {
return Some(new_check(inner, outer, location));
}
// Ex) set(set(...))
if outer == "set" && inner == "set" {
return Some(new_check(inner, outer, location));
}
let inner = function_name(func)?;
// Ex) set(tuple(...))
if (outer == "set" || outer == "sorted")
&& (inner == "list" || inner == "tuple" || inner == "reversed" || inner == "sorted")
{
return Some(new_check(inner, outer, location));
}
// Ex) list(tuple(...))
if (outer == "list" || outer == "tuple") && (inner == "list" || inner == "tuple") {
return Some(new_check(inner, outer, location));
}
// Ex) set(set(...))
if outer == "set" && inner == "set" {
return Some(new_check(inner, outer, location));
}
None
}
@@ -435,33 +441,34 @@ pub fn unnecessary_subscript_reversal(
if !["set", "sorted", "reversed"].contains(&id) {
return None;
}
if let ExprKind::Subscript { slice, .. } = &first_arg.node {
if let ExprKind::Slice { lower, upper, step } = &slice.node {
if lower.is_none() && upper.is_none() {
if let Some(step) = step {
if let ExprKind::UnaryOp {
op: Unaryop::USub,
operand,
} = &step.node
{
if let ExprKind::Constant {
value: Constant::Int(val),
..
} = &operand.node
{
if *val == BigInt::from(1) {
return Some(Check::new(
CheckKind::UnnecessarySubscriptReversal(id.to_string()),
location,
));
}
}
}
}
}
}
let ExprKind::Subscript { slice, .. } = &first_arg.node else {
return None;
};
let ExprKind::Slice { lower, upper, step } = &slice.node else {
return None;
};
if lower.is_some() || upper.is_some() {
return None;
}
None
let ExprKind::UnaryOp {
op: Unaryop::USub,
operand,
} = &step.as_ref()?.node else {
return None;
};
let ExprKind::Constant {
value: Constant::Int(val),
..
} = &operand.node else {
return None;
};
if *val != BigInt::from(1) {
return None;
};
Some(Check::new(
CheckKind::UnnecessarySubscriptReversal(id.to_string()),
location,
))
}
/// C416
@@ -497,7 +504,7 @@ pub fn unnecessary_comprehension(
if fix {
match fixes::fix_unnecessary_comprehension(locator, expr) {
Ok(fix) => check.amend(fix),
Err(e) => error!("Failed to generate fix: {}", e),
Err(e) => error!("Failed to generate fix: {e}"),
}
}
Some(check)

View File

@@ -41,6 +41,12 @@ pub fn debugger_call(
/// Checks for the presence of a debugger import.
pub fn debugger_import(stmt: &Stmt, module: Option<&str>, name: &str) -> Option<Check> {
// Special-case: allow `import builtins`, which is far more general than (e.g.)
// `import celery.contrib.rdb`).
if module.is_none() && name == "builtins" {
return None;
}
if let Some(module) = module {
if let Some((module_name, member)) = DEBUGGERS
.iter()

View File

@@ -9,39 +9,41 @@ use crate::flake8_print::checks;
/// T201, T203
pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
if let Some(mut check) = checks::print_call(
let Some(mut check) = checks::print_call(
func,
checker.settings.enabled.contains(&CheckCode::T201),
checker.settings.enabled.contains(&CheckCode::T203),
Range::from_located(expr),
) {
if checker.patch(check.kind.code()) {
let context = checker.binding_context();
if matches!(
checker.parents[context.defined_by].node,
StmtKind::Expr { .. }
) else {
return;
};
if checker.patch(check.kind.code()) {
let context = checker.binding_context();
if matches!(
checker.parents[context.defined_by].node,
StmtKind::Expr { .. }
) {
let deleted: Vec<&Stmt> = checker
.deletions
.iter()
.map(|index| checker.parents[*index])
.collect();
match helpers::remove_stmt(
checker.parents[context.defined_by],
context.defined_in.map(|index| checker.parents[index]),
&deleted,
) {
let deleted: Vec<&Stmt> = checker
.deletions
.iter()
.map(|index| checker.parents[*index])
.collect();
match helpers::remove_stmt(
checker.parents[context.defined_by],
context.defined_in.map(|index| checker.parents[index]),
&deleted,
) {
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
checker.deletions.insert(context.defined_by);
}
check.amend(fix);
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
checker.deletions.insert(context.defined_by);
}
Err(e) => error!("Failed to remove print call: {}", e),
check.amend(fix);
}
Err(e) => error!("Failed to remove print call: {e}"),
}
}
checker.add_check(check);
}
checker.add_check(check);
}

View File

@@ -26,70 +26,70 @@ expression: checks
Debugger:
Import: builtins.breakpoint
location:
row: 5
row: 6
column: 0
end_location:
row: 5
row: 6
column: 31
fix: ~
- kind:
Debugger:
Import: pdb.set_trace
location:
row: 6
row: 7
column: 0
end_location:
row: 6
row: 7
column: 31
fix: ~
- kind:
Debugger:
Import: celery.contrib.rdb.set_trace
location:
row: 7
row: 8
column: 0
end_location:
row: 7
row: 8
column: 40
fix: ~
- kind:
Debugger:
Import: celery.contrib.rdb
location:
row: 9
row: 10
column: 0
end_location:
row: 9
row: 10
column: 25
fix: ~
- kind:
Debugger:
Call: breakpoint
location:
row: 12
row: 13
column: 0
end_location:
row: 12
row: 13
column: 12
fix: ~
- kind:
Debugger:
Call: set_trace
location:
row: 13
row: 14
column: 0
end_location:
row: 13
row: 14
column: 4
fix: ~
- kind:
Debugger:
Call: set_trace
location:
row: 14
row: 15
column: 0
end_location:
row: 14
row: 15
column: 11
fix: ~

View File

@@ -1,5 +1,6 @@
//! Settings for the `flake8-quotes` plugin.
use ruff_macros::ConfigurationOptions;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
@@ -9,12 +10,55 @@ pub enum Quote {
Double,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
#[option(
doc = r#"
Quote style to prefer for inline strings (either "single" (`'`) or "double" (`"`)).
"#,
default = r#""double""#,
value_type = "Quote",
example = r#"
inline-quotes = "single"
"#
)]
pub inline_quotes: Option<Quote>,
#[option(
doc = r#"
Quote style to prefer for multiline strings (either "single" (`'`) or "double" (`"`)).
"#,
default = r#""double""#,
value_type = "Quote",
example = r#"
multiline-quotes = "single"
"#
)]
pub multiline_quotes: Option<Quote>,
#[option(
doc = r#"
Quote style to prefer for docstrings (either "single" (`'`) or "double" (`"`)).
"#,
default = r#""double""#,
value_type = "Quote",
example = r#"
docstring-quotes = "single"
"#
)]
pub docstring_quotes: Option<Quote>,
#[option(
doc = r#"
Whether to avoid using single quotes if a string contains single quotes, or vice-versa
with double quotes, as per [PEP8](https://peps.python.org/pep-0008/#string-quotes).
This minimizes the need to escape quotation marks within strings.
"#,
default = r#"true"#,
value_type = "bool",
example = r#"
# Don't bother trying to avoid escapes.
avoid-escape = false
"#
)]
pub avoid_escape: Option<bool>,
}

View File

@@ -14,43 +14,45 @@ use crate::Check;
/// RET501
fn unnecessary_return_none(checker: &mut Checker, stack: &Stack) {
for (stmt, expr) in &stack.returns {
if let Some(expr) = expr {
if matches!(
expr.node,
ExprKind::Constant {
value: Constant::None,
..
}
) {
let mut check =
Check::new(CheckKind::UnnecessaryReturnNone, Range::from_located(stmt));
if checker.patch(&CheckCode::RET501) {
check.amend(Fix::replacement(
"return".to_string(),
stmt.location,
stmt.end_location.unwrap(),
));
}
checker.add_check(check);
let Some(expr) = expr else {
continue;
};
if !matches!(
expr.node,
ExprKind::Constant {
value: Constant::None,
..
}
) {
continue;
}
let mut check = Check::new(CheckKind::UnnecessaryReturnNone, Range::from_located(stmt));
if checker.patch(&CheckCode::RET501) {
check.amend(Fix::replacement(
"return".to_string(),
stmt.location,
stmt.end_location.unwrap(),
));
}
checker.add_check(check);
}
}
/// RET502
fn implicit_return_value(checker: &mut Checker, stack: &Stack) {
for (stmt, expr) in &stack.returns {
if expr.is_none() {
let mut check = Check::new(CheckKind::ImplicitReturnValue, Range::from_located(stmt));
if checker.patch(&CheckCode::RET502) {
check.amend(Fix::replacement(
"return None".to_string(),
stmt.location,
stmt.end_location.unwrap(),
));
}
checker.add_check(check);
if expr.is_some() {
continue;
}
let mut check = Check::new(CheckKind::ImplicitReturnValue, Range::from_located(stmt));
if checker.patch(&CheckCode::RET502) {
check.amend(Fix::replacement(
"return None".to_string(),
stmt.location,
stmt.end_location.unwrap(),
));
}
checker.add_check(check);
}
}
@@ -210,44 +212,45 @@ fn unnecessary_assign(checker: &mut Checker, stack: &Stack, expr: &Expr) {
/// RET505, RET506, RET507, RET508
fn superfluous_else_node(checker: &mut Checker, stmt: &Stmt, branch: Branch) -> bool {
if let StmtKind::If { body, .. } = &stmt.node {
for child in body {
if matches!(child.node, StmtKind::Return { .. }) {
if checker.settings.enabled.contains(&CheckCode::RET505) {
checker.add_check(Check::new(
CheckKind::SuperfluousElseReturn(branch),
Range::from_located(stmt),
));
}
return true;
let StmtKind::If { body, .. } = &stmt.node else {
return false;
};
for child in body {
if matches!(child.node, StmtKind::Return { .. }) {
if checker.settings.enabled.contains(&CheckCode::RET505) {
checker.add_check(Check::new(
CheckKind::SuperfluousElseReturn(branch),
Range::from_located(stmt),
));
}
if matches!(child.node, StmtKind::Break) {
if checker.settings.enabled.contains(&CheckCode::RET508) {
checker.add_check(Check::new(
CheckKind::SuperfluousElseBreak(branch),
Range::from_located(stmt),
));
}
return true;
return true;
}
if matches!(child.node, StmtKind::Break) {
if checker.settings.enabled.contains(&CheckCode::RET508) {
checker.add_check(Check::new(
CheckKind::SuperfluousElseBreak(branch),
Range::from_located(stmt),
));
}
if matches!(child.node, StmtKind::Raise { .. }) {
if checker.settings.enabled.contains(&CheckCode::RET506) {
checker.add_check(Check::new(
CheckKind::SuperfluousElseRaise(branch),
Range::from_located(stmt),
));
}
return true;
return true;
}
if matches!(child.node, StmtKind::Raise { .. }) {
if checker.settings.enabled.contains(&CheckCode::RET506) {
checker.add_check(Check::new(
CheckKind::SuperfluousElseRaise(branch),
Range::from_located(stmt),
));
}
if matches!(child.node, StmtKind::Continue) {
if checker.settings.enabled.contains(&CheckCode::RET507) {
checker.add_check(Check::new(
CheckKind::SuperfluousElseContinue(branch),
Range::from_located(stmt),
));
}
return true;
return true;
}
if matches!(child.node, StmtKind::Continue) {
if checker.settings.enabled.contains(&CheckCode::RET507) {
checker.add_check(Check::new(
CheckKind::SuperfluousElseContinue(branch),
Range::from_located(stmt),
));
}
return true;
}
}
false
@@ -266,12 +269,14 @@ fn superfluous_elif(checker: &mut Checker, stack: &Stack) -> bool {
/// RET505, RET506, RET507, RET508
fn superfluous_else(checker: &mut Checker, stack: &Stack) -> bool {
for stmt in &stack.ifs {
if let StmtKind::If { orelse, .. } = &stmt.node {
if !orelse.is_empty() {
if superfluous_else_node(checker, stmt, Branch::Else) {
return true;
}
}
let StmtKind::If { orelse, .. } = &stmt.node else {
continue;
};
if orelse.is_empty() {
continue;
}
if superfluous_else_node(checker, stmt, Branch::Else) {
return true;
}
}
false

View File

@@ -9,18 +9,16 @@ pub fn banned_relative_import(
level: Option<&usize>,
strictness: &Strictness,
) -> Option<Check> {
if let Some(level) = level {
if level
> &match strictness {
Strictness::All => 0,
Strictness::Parents => 1,
}
{
return Some(Check::new(
CheckKind::BannedRelativeImport(strictness.clone()),
Range::from_located(stmt),
));
}
let strictness_level = match strictness {
Strictness::All => 0,
Strictness::Parents => 1,
};
if level? > &strictness_level {
Some(Check::new(
CheckKind::BannedRelativeImport(strictness.clone()),
Range::from_located(stmt),
))
} else {
None
}
None
}

View File

@@ -1,5 +1,6 @@
//! Settings for the `flake8-tidy-imports` plugin.
use ruff_macros::ConfigurationOptions;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
@@ -9,9 +10,21 @@ pub enum Strictness {
All,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
#[option(
doc = r#"
Whether to ban all relative imports (`"all"`), or only those imports that extend into
the parent module and beyond (`"parents"`).
"#,
default = r#""parents""#,
value_type = "Strictness",
example = r#"
# Disallow all relative imports.
ban-relative-imports = "all"
"#
)]
pub ban_relative_imports: Option<Strictness>,
}

View File

@@ -97,7 +97,7 @@ pub(crate) fn ignores_from_path<'a>(
/// Convert any path to an absolute path (based on the current working
/// directory).
pub(crate) fn normalize_path(path: &Path) -> PathBuf {
pub fn normalize_path(path: &Path) -> PathBuf {
if let Ok(path) = path.absolutize() {
return path.to_path_buf();
}
@@ -105,7 +105,7 @@ pub(crate) fn normalize_path(path: &Path) -> PathBuf {
}
/// Convert any path to an absolute path (based on the specified project root).
pub(crate) fn normalize_path_to(path: &Path, project_root: &Path) -> PathBuf {
pub fn normalize_path_to(path: &Path, project_root: &Path) -> PathBuf {
if let Ok(path) = path.absolutize_from(project_root) {
return path.to_path_buf();
}
@@ -113,7 +113,7 @@ pub(crate) fn normalize_path_to(path: &Path, project_root: &Path) -> PathBuf {
}
/// Convert an absolute path to be relative to the current working directory.
pub(crate) fn relativize_path(path: &Path) -> Cow<str> {
pub fn relativize_path(path: &Path) -> Cow<str> {
if let Ok(path) = path.strip_prefix(&*path_dedot::CWD) {
return path.to_string_lossy();
}

View File

@@ -39,8 +39,18 @@ pub fn format_import_from(
comments: &CommentSet,
aliases: &[(AliasData, CommentSet)],
line_length: usize,
force_wrap_aliases: bool,
is_first: bool,
) -> String {
if aliases.len() == 1
&& aliases
.iter()
.all(|(alias, _)| alias.name == "*" && alias.asname.is_none())
{
let (single_line, ..) = format_single_line(import_from, comments, aliases, is_first);
return single_line;
}
// We can only inline if: (1) none of the aliases have atop comments, and (3)
// only the last alias (if any) has inline comments.
if aliases
@@ -51,10 +61,13 @@ pub fn format_import_from(
.rev()
.skip(1)
.all(|(_, CommentSet { inline, .. })| inline.is_empty())
&& (!force_wrap_aliases
|| aliases.len() == 1
|| aliases.iter().all(|(alias, _)| alias.asname.is_none()))
{
let (single_line, import_length) =
format_single_line(import_from, comments, aliases, is_first);
if import_length <= line_length {
if import_length <= line_length || aliases.iter().any(|(alias, _)| alias.name == "*") {
return single_line;
}
}

View File

@@ -10,6 +10,7 @@ use rustpython_ast::{Stmt, StmtKind};
use crate::isort::categorize::{categorize, ImportType};
use crate::isort::comments::Comment;
use crate::isort::sorting::{member_key, module_key};
use crate::isort::track::{Block, Trailer};
use crate::isort::types::{
AliasData, CommentSet, ImportBlock, ImportFromData, Importable, OrderedImportBlock,
};
@@ -191,7 +192,18 @@ fn normalize_imports(imports: Vec<AnnotatedImport>, combine_as_imports: bool) ->
} => {
// Associate the comments with the first alias (best effort).
if let Some(alias) = names.first() {
if alias.asname.is_none() || combine_as_imports {
if alias.name == "*" {
let entry = block
.import_from_star
.entry(ImportFromData { module, level })
.or_default();
for comment in atop {
entry.atop.push(comment.value);
}
for comment in inline {
entry.inline.push(comment.value);
}
} else if alias.asname.is_none() || combine_as_imports {
let entry = &mut block
.import_from
.entry(ImportFromData { module, level })
@@ -225,7 +237,18 @@ fn normalize_imports(imports: Vec<AnnotatedImport>, combine_as_imports: bool) ->
// Create an entry for every alias.
for alias in names {
if alias.asname.is_none() || combine_as_imports {
if alias.name == "*" {
let entry = block
.import_from_star
.entry(ImportFromData { module, level })
.or_default();
for comment in alias.atop {
entry.atop.push(comment.value);
}
for comment in alias.inline {
entry.inline.push(comment.value);
}
} else if alias.asname.is_none() || combine_as_imports {
let entry = block
.import_from
.entry(ImportFromData { module, level })
@@ -323,6 +346,22 @@ fn categorize_imports<'a>(
.import_from_as
.insert((import_from, alias), comments);
}
// Categorize `StmtKind::ImportFrom` (with star).
for (import_from, comments) in block.import_from_star {
let classification = categorize(
&import_from.module_base(),
import_from.level,
src,
known_first_party,
known_third_party,
extra_standard_library,
);
block_by_type
.entry(classification)
.or_default()
.import_from_star
.insert(import_from, comments);
}
block_by_type
}
@@ -367,6 +406,33 @@ fn sort_imports(block: ImportBlock) -> OrderedImportBlock {
)
}),
)
.chain(
// Include all star imports.
block
.import_from_star
.into_iter()
.map(|(import_from, comments)| {
(
import_from,
(
CommentSet {
atop: comments.atop,
inline: vec![],
},
FxHashMap::from_iter([(
AliasData {
name: "*",
asname: None,
},
CommentSet {
atop: vec![],
inline: comments.inline,
},
)]),
),
)
}),
)
.map(|(import_from, (comments, aliases))| {
// Within each `StmtKind::ImportFrom`, sort the members.
(
@@ -399,7 +465,7 @@ fn sort_imports(block: ImportBlock) -> OrderedImportBlock {
#[allow(clippy::too_many_arguments)]
pub fn format_imports(
block: &[&Stmt],
block: &Block,
comments: Vec<Comment>,
line_length: usize,
src: &[PathBuf],
@@ -407,8 +473,10 @@ pub fn format_imports(
known_third_party: &BTreeSet<String>,
extra_standard_library: &BTreeSet<String>,
combine_as_imports: bool,
force_wrap_aliases: bool,
) -> String {
let block = annotate_imports(block, comments);
let trailer = &block.trailer;
let block = annotate_imports(&block.imports, comments);
// Normalize imports (i.e., deduplicate, aggregate `from` imports).
let block = normalize_imports(block, combine_as_imports);
@@ -451,11 +519,22 @@ pub fn format_imports(
comments,
aliases,
line_length,
force_wrap_aliases,
is_first_statement,
));
is_first_statement = false;
}
}
match trailer {
None => {}
Some(Trailer::Sibling) => {
output.append("\n");
}
Some(Trailer::FunctionDef | Trailer::ClassDef) => {
output.append("\n");
output.append("\n");
}
}
output.finish().to_string()
}
@@ -477,12 +556,16 @@ mod tests {
#[test_case(Path::new("deduplicate_imports.py"))]
#[test_case(Path::new("fit_line_length.py"))]
#[test_case(Path::new("fit_line_length_comment.py"))]
#[test_case(Path::new("force_wrap_aliases.py"))]
#[test_case(Path::new("import_from_after_import.py"))]
#[test_case(Path::new("insert_empty_lines.py"))]
#[test_case(Path::new("leading_prefix.py"))]
#[test_case(Path::new("no_reorder_within_section.py"))]
#[test_case(Path::new("no_wrap_star.py"))]
#[test_case(Path::new("order_by_type.py"))]
#[test_case(Path::new("order_relative_imports_by_level.py"))]
#[test_case(Path::new("preserve_comment_order.py"))]
#[test_case(Path::new("preserve_import_star.py"))]
#[test_case(Path::new("preserve_indentation.py"))]
#[test_case(Path::new("reorder_within_section.py"))]
#[test_case(Path::new("separate_first_party_imports.py"))]
@@ -490,7 +573,9 @@ mod tests {
#[test_case(Path::new("separate_local_folder_imports.py"))]
#[test_case(Path::new("separate_third_party_imports.py"))]
#[test_case(Path::new("skip.py"))]
#[test_case(Path::new("skip_file.py"))]
#[test_case(Path::new("sort_similar_imports.py"))]
#[test_case(Path::new("split.py"))]
#[test_case(Path::new("trailing_suffix.py"))]
#[test_case(Path::new("type_comments.py"))]
fn default(path: &Path) -> Result<()> {
@@ -531,4 +616,27 @@ mod tests {
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
#[test_case(Path::new("force_wrap_aliases.py"))]
fn force_wrap_aliases(path: &Path) -> Result<()> {
let snapshot = format!("force_wrap_aliases_{}", path.to_string_lossy());
let mut checks = test_path(
Path::new("./resources/test/fixtures/isort")
.join(path)
.as_path(),
&Settings {
isort: isort::settings::Settings {
force_wrap_aliases: true,
combine_as_imports: true,
..isort::settings::Settings::default()
},
src: vec![Path::new("resources/test/fixtures/isort").to_path_buf()],
..Settings::for_rule(CheckCode::I001)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
}

View File

@@ -1,11 +1,12 @@
use rustpython_ast::{Location, Stmt};
use textwrap::{dedent, indent};
use crate::ast::helpers::{match_leading_content, match_trailing_content};
use crate::ast::helpers::{count_trailing_lines, match_leading_content, match_trailing_content};
use crate::ast::types::Range;
use crate::ast::whitespace::leading_space;
use crate::autofix::Fix;
use crate::checks::CheckKind;
use crate::isort::track::Block;
use crate::isort::{comments, format_imports};
use crate::{Check, Settings, SourceCodeLocator};
@@ -30,13 +31,13 @@ fn extract_indentation(body: &[&Stmt], locator: &SourceCodeLocator) -> String {
/// I001
pub fn check_imports(
body: &[&Stmt],
block: &Block,
locator: &SourceCodeLocator,
settings: &Settings,
autofix: bool,
) -> Option<Check> {
let range = extract_range(body);
let indentation = extract_indentation(body, locator);
let range = extract_range(&block.imports);
let indentation = extract_indentation(&block.imports, locator);
// Extract comments. Take care to grab any inline comments from the last line.
let comments = comments::collect_comments(
@@ -48,12 +49,17 @@ pub fn check_imports(
);
// Special-cases: there's leading or trailing content in the import block.
let has_leading_content = match_leading_content(body.first().unwrap(), locator);
let has_trailing_content = match_trailing_content(body.last().unwrap(), locator);
let has_leading_content = match_leading_content(block.imports.first().unwrap(), locator);
let has_trailing_content = match_trailing_content(block.imports.last().unwrap(), locator);
let num_trailing_lines = if block.trailer.is_none() {
0
} else {
count_trailing_lines(block.imports.last().unwrap(), locator)
};
// Generate the sorted import block.
let expected = format_imports(
body,
block,
comments,
settings.line_length - indentation.len(),
&settings.src,
@@ -61,6 +67,7 @@ pub fn check_imports(
&settings.isort.known_third_party,
&settings.isort.extra_standard_library,
settings.isort.combine_as_imports,
settings.isort.force_wrap_aliases,
);
if has_leading_content || has_trailing_content {
@@ -80,7 +87,7 @@ pub fn check_imports(
Location::new(range.location.row(), 0)
},
// TODO(charlie): Preserve trailing suffixes. Right now, we strip them.
Location::new(range.end_location.row() + 1, 0),
Location::new(range.end_location.row() + 1 + num_trailing_lines, 0),
));
}
Some(check)
@@ -88,7 +95,7 @@ pub fn check_imports(
// Expand the span the entire range, including leading and trailing space.
let range = Range {
location: Location::new(range.location.row(), 0),
end_location: Location::new(range.end_location.row() + 1, 0),
end_location: Location::new(range.end_location.row() + 1 + num_trailing_lines, 0),
};
let actual = dedent(&locator.slice_source_code_range(&range));
if actual == expected {

View File

@@ -2,20 +2,86 @@
use std::collections::BTreeSet;
use ruff_macros::ConfigurationOptions;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
#[option(
doc = r#"
Combines as imports on the same line. See isort's [`combine-as-imports`](https://pycqa.github.io/isort/docs/configuration/options.html#combine-as-imports)
option.
"#,
default = r#"false"#,
value_type = "bool",
example = r#"
combine-as-imports = true
"#
)]
pub combine_as_imports: Option<bool>,
#[option(
doc = r#"
Force `import from` statements with multiple members and at least one alias (e.g.,
`import A as B`) to wrap such that every line contains exactly one member. For example,
this formatting would be retained, rather than condensing to a single line:
```py
from .utils import (
test_directory as test_directory,
test_id as test_id
)
```
"#,
default = r#"false"#,
value_type = "bool",
example = r#"
force-wrap-aliases = true
"#
)]
pub force_wrap_aliases: Option<bool>,
#[option(
doc = r#"
A list of modules to consider first-party, regardless of whether they can be identified
as such via introspection of the local filesystem.
"#,
default = r#"[]"#,
value_type = "Vec<String>",
example = r#"
known-first-party = ["src"]
"#
)]
pub known_first_party: Option<Vec<String>>,
#[option(
doc = r#"
A list of modules to consider third-party, regardless of whether they can be identified
as such via introspection of the local filesystem.
"#,
default = r#"[]"#,
value_type = "Vec<String>",
example = r#"
known-third-party = ["src"]
"#
)]
pub known_third_party: Option<Vec<String>>,
#[option(
doc = r#"
A list of modules to consider standard-library, in addition to those known to Ruff in
advance.
"#,
default = r#"[]"#,
value_type = "Vec<String>",
example = r#"
extra-standard-library = ["path"]
"#
)]
pub extra_standard_library: Option<Vec<String>>,
}
#[derive(Debug, Hash, Default)]
pub struct Settings {
pub combine_as_imports: bool,
pub force_wrap_aliases: bool,
pub known_first_party: BTreeSet<String>,
pub known_third_party: BTreeSet<String>,
pub extra_standard_library: BTreeSet<String>,
@@ -25,6 +91,7 @@ impl Settings {
pub fn from_options(options: Options) -> Self {
Self {
combine_as_imports: options.combine_as_imports.unwrap_or_default(),
force_wrap_aliases: options.force_wrap_aliases.unwrap_or_default(),
known_first_party: BTreeSet::from_iter(options.known_first_party.unwrap_or_default()),
known_third_party: BTreeSet::from_iter(options.known_third_party.unwrap_or_default()),
extra_standard_library: BTreeSet::from_iter(

View File

@@ -0,0 +1,20 @@
---
source: src/isort/mod.rs
expression: checks
---
- kind: UnsortedImports
location:
row: 1
column: 0
end_location:
row: 4
column: 0
fix:
content: "from .a import a1 as a1\nfrom .a import a2 as a2\nfrom .b import b1 as b1\nfrom .c import c1\n"
location:
row: 1
column: 0
end_location:
row: 4
column: 0

View File

@@ -0,0 +1,20 @@
---
source: src/isort/mod.rs
expression: checks
---
- kind: UnsortedImports
location:
row: 1
column: 0
end_location:
row: 4
column: 0
fix:
content: "from .a import (\n a1 as a1,\n a2 as a2,\n)\nfrom .b import b1 as b1\nfrom .c import c1\n"
location:
row: 1
column: 0
end_location:
row: 4
column: 0

View File

@@ -0,0 +1,80 @@
---
source: src/isort/mod.rs
expression: checks
---
- kind: UnsortedImports
location:
row: 1
column: 0
end_location:
row: 3
column: 0
fix:
content: "import a\nimport b\n\n"
location:
row: 1
column: 0
end_location:
row: 3
column: 0
- kind: UnsortedImports
location:
row: 4
column: 0
end_location:
row: 6
column: 0
fix:
content: "import os\nimport sys\n\n\n"
location:
row: 4
column: 0
end_location:
row: 6
column: 0
- kind: UnsortedImports
location:
row: 14
column: 0
end_location:
row: 16
column: 0
fix:
content: "import os\nimport sys\n\n"
location:
row: 14
column: 0
end_location:
row: 16
column: 0
- kind: UnsortedImports
location:
row: 33
column: 0
end_location:
row: 35
column: 0
fix:
content: " import collections\n import typing\n\n"
location:
row: 33
column: 0
end_location:
row: 35
column: 0
- kind: UnsortedImports
location:
row: 39
column: 0
end_location:
row: 41
column: 0
fix:
content: " import collections\n import typing\n\n"
location:
row: 39
column: 0
end_location:
row: 41
column: 0

View File

@@ -10,12 +10,12 @@ expression: checks
row: 2
column: 9
fix:
content: "\nimport os\nimport sys\n"
content: "\nimport os\nimport sys\n\n"
location:
row: 1
column: 7
end_location:
row: 3
row: 4
column: 0
- kind: UnsortedImports
location:

View File

@@ -0,0 +1,20 @@
---
source: src/isort/mod.rs
expression: checks
---
- kind: UnsortedImports
location:
row: 1
column: 0
end_location:
row: 2
column: 0
fix:
content: "from .subscription import * # type: ignore # some very long comment explaining why this needs a type ignore\n"
location:
row: 1
column: 0
end_location:
row: 2
column: 0

View File

@@ -0,0 +1,20 @@
---
source: src/isort/mod.rs
expression: checks
---
- kind: UnsortedImports
location:
row: 1
column: 0
end_location:
row: 7
column: 0
fix:
content: "# Above\nfrom some_module import * # Aside\n\n# Above\nfrom some_module import some_class # Aside\nfrom some_other_module import *\nfrom some_other_module import some_class\n"
location:
row: 1
column: 0
end_location:
row: 7
column: 0

View File

@@ -2,6 +2,21 @@
source: src/isort/mod.rs
expression: checks
---
- kind: UnsortedImports
location:
row: 7
column: 0
end_location:
row: 8
column: 0
fix:
content: "import sys\n\n"
location:
row: 7
column: 0
end_location:
row: 8
column: 0
- kind: UnsortedImports
location:
row: 9

View File

@@ -1,5 +1,5 @@
---
source: src/rules/mod.rs
source: src/isort/mod.rs
expression: checks
---
[]

View File

@@ -1,5 +1,5 @@
---
source: src/rules/mod.rs
source: src/isort/mod.rs
expression: checks
---
[]

View File

@@ -10,12 +10,12 @@ expression: checks
row: 2
column: 9
fix:
content: "import os\nimport sys\n"
content: "import os\nimport sys\n\n"
location:
row: 1
column: 0
end_location:
row: 3
row: 4
column: 0
- kind: UnsortedImports
location:
@@ -25,7 +25,7 @@ expression: checks
row: 6
column: 13
fix:
content: " import os\n import sys\n"
content: " import os\n import sys\n\n"
location:
row: 5
column: 0

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