Compare commits

...

47 Commits

Author SHA1 Message Date
Charlie Marsh
560c00ff9d Bump version to 0.0.111 2022-11-11 12:38:23 -05:00
Charlie Marsh
befe64a10e Support isort: skip, isort: on, and isort: off (#678) 2022-11-11 12:38:01 -05:00
Charlie Marsh
4eccfdeb69 Fix lambda handling for B010 (#685) 2022-11-11 11:18:23 -05:00
Charlie Marsh
4123ba9851 Add backticks around setattr 2022-11-11 11:08:22 -05:00
Harutaka Kawamura
e727c24f79 Implement autofix for B009 (#684) 2022-11-11 11:06:47 -05:00
Harutaka Kawamura
bd3b40688f Implement B010 (#683) 2022-11-11 10:26:37 -05:00
Charlie Marsh
b5549382a7 Clarify a few settings for isort behavior (#676) 2022-11-10 23:19:51 -05:00
Charlie Marsh
8cf745045f Bump version to 0.0.110 2022-11-10 19:22:45 -05:00
Charlie Marsh
f6992cc98c Add a test utility for running lint checks (#672) 2022-11-10 19:22:00 -05:00
Charlie Marsh
3cc74c0564 Implement import sorting (#633) 2022-11-10 19:05:56 -05:00
Charlie Marsh
887b9aa840 Rename some fixture files (#671) 2022-11-10 17:28:10 -05:00
Charlie Marsh
faf8556a5c Limit Ropey to newlines and carriage returns (#670) 2022-11-10 17:25:30 -05:00
Harutaka Kawamura
1888f6d41b Implement B009 (#669) 2022-11-10 13:52:20 -05:00
Charlie Marsh
9d8cd2d2fe Bump version to 0.0.109 2022-11-10 10:54:27 -05:00
Harutaka Kawamura
05c19f0091 Implement B026 (#668) 2022-11-10 10:47:42 -05:00
Chammika Mannakkara
8213b64ad5 Fix unnecessary params in lru_cache (#667) 2022-11-10 10:45:12 -05:00
Chammika Mannakkara
ff0f5968fa Detect unnecessary params in lru_cache (#664) 2022-11-09 10:02:48 -05:00
Charlie Marsh
6c17670aa5 Upgrade LibCST and other crates (#663) 2022-11-08 17:42:12 -05:00
Charlie Marsh
1347b7ebb6 Add notes to README on editor integrations (#655) 2022-11-08 16:12:42 -05:00
Reiner Gerecke
f40609f524 Implement autofix for C413 (#661) 2022-11-08 16:12:29 -05:00
Charlie Marsh
f572acab30 Bump version to 0.0.108 2022-11-08 13:20:35 -05:00
Charlie Marsh
1eb5f3ea75 Support allow_star_arg_any in flake8-to-ruff 2022-11-08 09:51:11 -05:00
Edgar R. M
24aa177912 Implement ANN401 (#657) 2022-11-08 09:49:44 -05:00
Reiner Gerecke
2ddc768412 Implement fix for C404 (#656) 2022-11-08 09:37:17 -05:00
Chammika Mannakkara
0f9508f549 Remove unnecessary __future__ imports (#634) 2022-11-07 22:26:57 -05:00
Charlie Marsh
7c3d387abd Implement confusing unicode character detection for comments (#653) 2022-11-07 21:16:34 -05:00
Charlie Marsh
d600650214 Upgrade RustPython (#652) 2022-11-07 21:07:49 -05:00
Charlie Marsh
43383bb696 Bump version to 0.0.107 2022-11-07 16:39:03 -05:00
Charlie Marsh
ff83f356af Fix failing flake8-to-ruff tests 2022-11-07 16:38:46 -05:00
Anders Kaseorg
89622425d4 Run annotations plugin if ANN204, ANN205, ANN206 are selected (#649) 2022-11-07 16:37:49 -05:00
Charlie Marsh
1aafe31c00 Avoid U009 violations when disabled (#650) 2022-11-07 16:36:39 -05:00
Charlie Marsh
16c5ac1e91 Bump version to 0.0.106 2022-11-07 15:32:54 -05:00
Charlie Marsh
da39cddccb Include function and argument names in ANN checks (#648) 2022-11-07 15:27:03 -05:00
Charlie Marsh
de6435f41d Add a flake8-to-ruff mention (#644) 2022-11-07 10:59:47 -05:00
Charlie Marsh
589ae48f8c Update --help 2022-11-07 10:41:04 -05:00
Harutaka Kawamura
35cc3094b5 Implement B005 (#643) 2022-11-07 10:10:59 -05:00
Charlie Marsh
07c9fc55f6 Add fix option to pyproject.toml (#639) 2022-11-07 09:46:21 -05:00
Harutaka Kawamura
7773e9728b Implement B004 (#638) 2022-11-07 09:25:09 -05:00
Charlie Marsh
8fc435bad9 Fix --ignore for ANN101 and ANN102 (#637) 2022-11-07 09:03:09 -05:00
Charlie Marsh
dd20b23576 Infer plugins based on per-file-ignores, ignores, etc. (#632) 2022-11-06 22:39:06 -05:00
Charlie Marsh
da8ee1df3e Add TODO in flake8_annotations 2022-11-06 21:24:46 -05:00
Charlie Marsh
7f77ed0f86 Bump version to 0.0.105 2022-11-06 21:17:00 -05:00
Charlie Marsh
1b33cfb9cb Respect project root in per-file ignores (#631) 2022-11-06 21:15:49 -05:00
Charlie Marsh
dbc64f1faa Remove erroneous Literal entry from subscript list (#630) 2022-11-06 21:03:41 -05:00
Charlie Marsh
f4b5f0d259 Remove foo.py 2022-11-06 21:03:23 -05:00
Charlie Marsh
85b882fc54 Remove CheckLocator abstraction (#627) 2022-11-06 17:42:10 -05:00
Charlie Marsh
99d9aa61bf Implement flake8-annotations (#625) 2022-11-06 17:25:49 -05:00
193 changed files with 6636 additions and 1091 deletions

View File

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

310
Cargo.lock generated
View File

@@ -14,7 +14,7 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom 0.2.7",
"getrandom 0.2.8",
"once_cell",
"version_check",
]
@@ -51,9 +51,9 @@ checksum = "c7021ce4924a3f25f802b2cccd1af585e39ea1a363a1aa2e72afe54b67a3a7a7"
[[package]]
name = "anyhow"
version = "1.0.65"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
[[package]]
name = "ascii"
@@ -72,11 +72,11 @@ dependencies = [
[[package]]
name = "assert_cmd"
version = "2.0.4"
version = "2.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e"
checksum = "ba45b8163c49ab5f972e59a8a5a03b6d2972619d486e19ec9fe744f7c2753d3c"
dependencies = [
"bstr",
"bstr 1.0.1",
"doc-comment",
"predicates",
"predicates-core",
@@ -111,9 +111,9 @@ dependencies = [
[[package]]
name = "async-global-executor"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0da5b41ee986eed3f524c380e6d64965aea573882a8907682ad100f7859305ca"
checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776"
dependencies = [
"async-channel",
"async-executor",
@@ -126,16 +126,16 @@ dependencies = [
[[package]]
name = "async-io"
version = "1.9.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7"
checksum = "e8121296a9f05be7f34aa4196b1747243b3b62e048bb7906f644f3fbfc490cf7"
dependencies = [
"async-lock",
"autocfg",
"concurrent-queue",
"futures-lite",
"libc",
"log",
"once_cell",
"parking",
"polling",
"slab",
@@ -146,11 +146,12 @@ dependencies = [
[[package]]
name = "async-lock"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6"
checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685"
dependencies = [
"event-listener",
"futures-lite",
]
[[package]]
@@ -238,9 +239,9 @@ dependencies = [
[[package]]
name = "base64"
version = "0.13.0"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "bincode"
@@ -328,10 +329,22 @@ dependencies = [
]
[[package]]
name = "bumpalo"
version = "3.11.0"
name = "bstr"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
checksum = "fca0852af221f458706eb0725c03e4ed6c46af9ac98e6a689d5e634215d594dd"
dependencies = [
"memchr",
"once_cell",
"regex-automata",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
[[package]]
name = "byte-tools"
@@ -382,9 +395,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.0.73"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
checksum = "41ca34107f97baef6cfb231b32f36115781856b8f8208e8c580e0bcaea374842"
[[package]]
name = "cfg-if"
@@ -464,14 +477,14 @@ dependencies = [
"bitflags",
"clap_lex 0.2.4",
"indexmap",
"textwrap 0.16.0",
"textwrap",
]
[[package]]
name = "clap"
version = "4.0.15"
version = "4.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bf8832993da70a4c6d13c581f4463c2bdda27b9bf1c5498dc4365543abe6d6f"
checksum = "91b9970d7505127a162fdaa9b96428d28a479ba78c9ec7550a63a5d9863db682"
dependencies = [
"atty",
"bitflags",
@@ -484,9 +497,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.0.13"
version = "4.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f169caba89a7d512b5418b09864543eeb4d497416c917d7137863bd2076ad"
checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014"
dependencies = [
"heck",
"proc-macro-error",
@@ -701,9 +714,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "ctor"
version = "0.1.23"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb"
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
dependencies = [
"quote",
"syn",
@@ -711,9 +724,9 @@ dependencies = [
[[package]]
name = "cxx"
version = "1.0.78"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f39818dcfc97d45b03953c1292efc4e80954e1583c4aa770bac1383e2310a4"
checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -723,9 +736,9 @@ dependencies = [
[[package]]
name = "cxx-build"
version = "1.0.78"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e580d70777c116df50c390d1211993f62d40302881e54d4b79727acb83d0199"
checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3"
dependencies = [
"cc",
"codespan-reporting",
@@ -738,15 +751,15 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
version = "1.0.78"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56a46460b88d1cec95112c8c363f0e2c39afdb237f60583b0b36343bf627ea9c"
checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f"
[[package]]
name = "cxxbridge-macro"
version = "1.0.78"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "747b608fecf06b0d72d440f27acc99288207324b793be2c17991839f3d4995ea"
checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704"
dependencies = [
"proc-macro2",
"quote",
@@ -902,9 +915,9 @@ dependencies = [
[[package]]
name = "filetime"
version = "0.2.17"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c"
checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3"
dependencies = [
"cfg-if 1.0.0",
"libc",
@@ -920,10 +933,10 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.104-dev.0"
version = "0.0.111-dev.0"
dependencies = [
"anyhow",
"clap 4.0.15",
"clap 4.0.22",
"configparser",
"once_cell",
"regex",
@@ -995,9 +1008,9 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
[[package]]
name = "futures"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c"
checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0"
dependencies = [
"futures-channel",
"futures-core",
@@ -1010,9 +1023,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050"
checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed"
dependencies = [
"futures-core",
"futures-sink",
@@ -1020,15 +1033,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac"
[[package]]
name = "futures-executor"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab"
checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2"
dependencies = [
"futures-core",
"futures-task",
@@ -1037,9 +1050,9 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68"
checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
[[package]]
name = "futures-lite"
@@ -1058,9 +1071,9 @@ dependencies = [
[[package]]
name = "futures-macro"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17"
checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d"
dependencies = [
"proc-macro2",
"quote",
@@ -1069,21 +1082,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56"
checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9"
[[package]]
name = "futures-task"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1"
checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea"
[[package]]
name = "futures-util"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6"
dependencies = [
"futures-channel",
"futures-core",
@@ -1129,9 +1142,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.7"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
@@ -1208,9 +1221,9 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]]
name = "iana-time-zone"
version = "0.1.51"
version = "0.1.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed"
checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@@ -1222,9 +1235,9 @@ dependencies = [
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.0"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde6edd6cef363e9359ed3c98ba64590ba9eecba2293eb5a723ab32aee8926aa"
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
dependencies = [
"cxx",
"cxx-build",
@@ -1427,14 +1440,14 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.135"
version = "0.2.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
[[package]]
name = "libcst"
version = "0.1.0"
source = "git+https://github.com/charliermarsh/LibCST?rev=32a044c127668df44582f85699358e67803b0d73#32a044c127668df44582f85699358e67803b0d73"
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
dependencies = [
"chic",
"itertools",
@@ -1449,7 +1462,7 @@ dependencies = [
[[package]]
name = "libcst_derive"
version = "0.1.0"
source = "git+https://github.com/charliermarsh/LibCST?rev=32a044c127668df44582f85699358e67803b0d73#32a044c127668df44582f85699358e67803b0d73"
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
dependencies = [
"quote",
"syn",
@@ -1583,9 +1596,9 @@ dependencies = [
[[package]]
name = "net2"
version = "0.2.37"
version = "0.2.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae"
checksum = "74d0df99cfcd2530b2e694f6e17e7f37b8e26bb23983ac530c0c97408837c631"
dependencies = [
"cfg-if 0.1.10",
"libc",
@@ -1609,6 +1622,12 @@ dependencies = [
"libc",
]
[[package]]
name = "nohash-hasher"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
[[package]]
name = "nom"
version = "5.1.2"
@@ -1680,9 +1699,9 @@ dependencies = [
[[package]]
name = "num_cpus"
version = "1.13.1"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
dependencies = [
"hermit-abi",
"libc",
@@ -1690,9 +1709,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.15.0"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
[[package]]
name = "oorandom"
@@ -1714,9 +1733,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "os_str_bytes"
version = "6.3.0"
version = "6.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9"
[[package]]
name = "parking"
@@ -1736,9 +1755,9 @@ dependencies = [
[[package]]
name = "parking_lot_core"
version = "0.9.3"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0"
dependencies = [
"cfg-if 1.0.0",
"libc",
@@ -1938,9 +1957,9 @@ dependencies = [
[[package]]
name = "polling"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011"
checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
@@ -1952,9 +1971,9 @@ dependencies = [
[[package]]
name = "ppv-lite86"
version = "0.2.16"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "precomputed-hash"
@@ -1964,9 +1983,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "predicates"
version = "2.1.1"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c"
checksum = "ab68289ded120dcbf9d571afcf70163233229052aec9b08ab09532f698d0e1e6"
dependencies = [
"difflib",
"itertools",
@@ -1975,15 +1994,15 @@ dependencies = [
[[package]]
name = "predicates-core"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb"
checksum = "a6e7125585d872860e9955ca571650b27a4979c5823084168c5ed5bbfb016b56"
[[package]]
name = "predicates-tree"
version = "1.0.5"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032"
checksum = "ad3f7fa8d61e139cbc7c3edfebf3b6678883a53f5ffac65d1259329a93ee43a5"
dependencies = [
"predicates-core",
"termtree",
@@ -2015,9 +2034,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.46"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b"
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
dependencies = [
"unicode-ident",
]
@@ -2097,7 +2116,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.7",
"getrandom 0.2.8",
]
[[package]]
@@ -2157,16 +2176,16 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [
"getrandom 0.2.7",
"getrandom 0.2.8",
"redox_syscall",
"thiserror",
]
[[package]]
name = "regex"
version = "1.6.0"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
dependencies = [
"aho-corasick",
"memchr",
@@ -2181,9 +2200,9 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
[[package]]
name = "regex-syntax"
version = "0.6.27"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "remove_dir_all"
@@ -2221,14 +2240,15 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.104"
version = "0.0.111"
dependencies = [
"anyhow",
"assert_cmd",
"bincode",
"bitflags",
"cacache",
"chrono",
"clap 4.0.15",
"clap 4.0.22",
"clearscreen",
"colored",
"common-path",
@@ -2236,12 +2256,13 @@ dependencies = [
"dirs 4.0.0",
"fern",
"filetime",
"getrandom 0.2.7",
"getrandom 0.2.8",
"glob",
"insta",
"itertools",
"libcst",
"log",
"nohash-hasher",
"notify",
"num-bigint",
"once_cell",
@@ -2257,7 +2278,7 @@ dependencies = [
"strum",
"strum_macros",
"test-case",
"textwrap 0.15.1",
"textwrap",
"titlecase",
"toml",
"update-informer",
@@ -2266,12 +2287,13 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.104"
version = "0.0.111"
dependencies = [
"anyhow",
"clap 4.0.15",
"clap 4.0.22",
"codegen",
"itertools",
"libcst",
"ruff",
"rustpython-ast",
"rustpython-common",
@@ -2282,9 +2304,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.20.6"
version = "0.20.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033"
checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c"
dependencies = [
"log",
"ring",
@@ -2295,7 +2317,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.1.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
source = "git+https://github.com/RustPython/RustPython.git?rev=27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb#27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb"
dependencies = [
"num-bigint",
"rustpython-common",
@@ -2305,7 +2327,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.0.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
source = "git+https://github.com/RustPython/RustPython.git?rev=27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb#27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb"
dependencies = [
"ascii",
"cfg-if 1.0.0",
@@ -2328,11 +2350,11 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
source = "git+https://github.com/RustPython/RustPython.git?rev=27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb#27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb"
dependencies = [
"bincode",
"bitflags",
"bstr",
"bstr 0.2.17",
"itertools",
"lz4_flex",
"num-bigint",
@@ -2345,7 +2367,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
source = "git+https://github.com/RustPython/RustPython.git?rev=27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb#27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb"
dependencies = [
"ahash",
"anyhow",
@@ -2417,18 +2439,18 @@ checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
[[package]]
name = "serde"
version = "1.0.145"
version = "1.0.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.145"
version = "1.0.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
dependencies = [
"proc-macro2",
"quote",
@@ -2437,9 +2459,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.86"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074"
checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
dependencies = [
"itoa",
"ryu",
@@ -2634,9 +2656,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.102"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1"
checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
dependencies = [
"proc-macro2",
"quote",
@@ -2702,9 +2724,9 @@ dependencies = [
[[package]]
name = "termtree"
version = "0.2.4"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b"
checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8"
[[package]]
name = "test-case"
@@ -2730,21 +2752,15 @@ dependencies = [
[[package]]
name = "textwrap"
version = "0.15.1"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
dependencies = [
"smawk",
"unicode-linebreak",
"unicode-width",
]
[[package]]
name = "textwrap"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
[[package]]
name = "thiserror"
version = "1.0.37"
@@ -2984,7 +3000,7 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97acb4c28a254fd7a4aeec976c46a7fa404eac4d7c134b30c75144846d7cb8f"
dependencies = [
"base64 0.13.0",
"base64 0.13.1",
"chunked_transfer",
"flate2",
"log",
@@ -3240,46 +3256,60 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.36.1"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_msvc"
version = "0.36.1"
name = "windows_aarch64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
[[package]]
name = "windows_i686_gnu"
version = "0.36.1"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
[[package]]
name = "windows_i686_msvc"
version = "0.36.1"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
[[package]]
name = "windows_x86_64_gnu"
version = "0.36.1"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
[[package]]
name = "windows_x86_64_msvc"
version = "0.36.1"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
[[package]]
name = "ws2_32-sys"

View File

@@ -6,15 +6,16 @@ members = [
[package]
name = "ruff"
version = "0.0.104"
version = "0.0.111"
edition = "2021"
[lib]
name = "ruff"
[dependencies]
anyhow = { version = "1.0.60" }
anyhow = { version = "1.0.66" }
bincode = { version = "1.3.3" }
bitflags = { version = "1.3.2" }
chrono = { version = "0.4.21" }
clap = { version = "4.0.1", features = ["derive"] }
colored = { version = "2.0.0" }
@@ -24,26 +25,27 @@ fern = { version = "0.6.1" }
filetime = { version = "0.2.17" }
glob = { version = "0.3.0" }
itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "32a044c127668df44582f85699358e67803b0d73" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "a13ec97dd4eb925bde4d426c6e422582793b260c" }
log = { version = "0.4.17" }
nohash-hasher = { version = "0.2.0" }
notify = { version = "4.0.17" }
num-bigint = { version = "0.4.3" }
once_cell = { version = "1.13.1" }
once_cell = { version = "1.16.0" }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
rayon = { version = "1.5.3" }
regex = { version = "1.6.0" }
ropey = { version = "1.5.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
serde = { version = "1.0.143", features = ["derive"] }
serde_json = { version = "1.0.83" }
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
textwrap = { version = "0.15.1" }
textwrap = { version = "0.16.0" }
titlecase = { version = "2.2.1" }
toml = { version = "0.5.9" }
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
update-informer = { version = "0.5.0", default-features = false, features = ["pypi"], optional = true }
walkdir = { version = "2.3.2" }
[target.'cfg(not(target_family = "wasm"))'.dependencies]

145
README.md
View File

@@ -15,21 +15,25 @@ An extremely fast Python linter, written in Rust.
<i>Linting the CPython codebase from scratch.</i>
</p>
- ⚡️ 10-100x faster than existing linters
- 🐍 Installable via `pip`
- 🤝 Python 3.10 compatibility
- 🛠️ `pyproject.toml` support
- 📦 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired cache support
- 🔧 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix)-inspired autofix support (e.g., automatically remove unused imports)
- 👀 [TypeScript](https://www.typescriptlang.org/docs/handbook/configuring-watch.html)-inspired `--watch` support, for continuous file monitoring
- ⚖️ [Near-parity](#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
- 🔌 Native re-implementations of popular Flake8 plugins, like [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) ([`pydocstyle`](https://pypi.org/project/pydocstyle/))
- ⚡️ 10-100x faster than existing linters
- 🐍 Installable via `pip`
- 🤝 Python 3.10 compatibility
- 🛠️ `pyproject.toml` support
- 📦 Built-in caching, to avoid re-analyzing unchanged files
- 🔧 `--fix` support, for automatic error correction (e.g., automatically remove unused imports)
- 👀 `--watch` support, for continuous file monitoring
- ⚖️ [Near-parity](#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
- 🔌 Native re-implementations of popular Flake8 plugins, like [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) ([`pydocstyle`](https://pypi.org/project/pydocstyle/))
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
functionality behind a single, common interface. Ruff can be used to replace Flake8 (plus a variety
of plugins), [`pydocstyle`](https://pypi.org/project/pydocstyle/), [`yesqa`](https://github.com/asottile/yesqa),
and even a subset of [`pyupgrade`](https://pypi.org/project/pyupgrade/) and [`autoflake`](https://pypi.org/project/autoflake/)
all while executing tens or hundreds of times faster than any individual tool.
of plugins), [`isort`](https://pypi.org/project/isort/), [`pydocstyle`](https://pypi.org/project/pydocstyle/),
[`yesqa`](https://github.com/asottile/yesqa), and even a subset of [`pyupgrade`](https://pypi.org/project/pyupgrade/)
and [`autoflake`](https://pypi.org/project/autoflake/) all while executing tens or hundreds of times
faster than any individual tool.
(Coming from Flake8? Try [`flake8-to-ruff`](https://pypi.org/project/flake8-to-ruff/) to
automatically convert your existing configuration.)
Ruff is actively developed and used in major open-source projects
like [Zulip](https://github.com/zulip/zulip), [pydantic](https://github.com/pydantic/pydantic),
@@ -52,8 +56,9 @@ Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-mu
8. [flake8-builtins](#flake8-builtins)
9. [flake8-print](#flake8-print)
10. [flake8-quotes](#flake8-quotes)
11. [Ruff-specific rules](#ruff-specific-rules)
12. [Meta rules](#meta-rules)
11. [flake8-annotations](#flake8-annotations)
12. [Ruff-specific rules](#ruff-specific-rules)
13. [Meta rules](#meta-rules)
5. [Editor Integrations](#editor-integrations)
6. [FAQ](#faq)
7. [Development](#development)
@@ -93,7 +98,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.104
rev: v0.0.111
hooks:
- id: ruff
```
@@ -199,7 +204,7 @@ Options:
Exit with status code "0", even upon detecting errors
-w, --watch
Run in watch mode by re-running whenever files change
-f, --fix
--fix
Attempt to automatically fix lint errors
-n, --no-cache
Disable cache reads
@@ -281,16 +286,16 @@ Ruff supports several workflows to aid in `noqa` management.
First, Ruff provides a special error code, `M001`, to enforce that your `noqa` directives are
"valid", in that the errors they _say_ they ignore are actually being triggered on that line (and
thus suppressed). **You can run `ruff /path/to/file.py --extend-select M001` to flag unused `noqa`
directives.**
thus suppressed). You can run `ruff /path/to/file.py --extend-select M001` to flag unused `noqa`
directives.
Second, Ruff can _automatically remove_ unused `noqa` directives via its autofix functionality.
**You can run `ruff /path/to/file.py --extend-select M001 --fix` to automatically remove unused
`noqa` directives.**
You can run `ruff /path/to/file.py --extend-select M001 --fix` to automatically remove unused
`noqa` directives.
Third, Ruff can _automatically add_ `noqa` directives to all failing lines. This is useful when
migrating a new codebase to Ruff. **You can run `ruff /path/to/file.py --add-noqa` to automatically
add `noqa` directives to all failing lines, with the appropriate error codes.**
migrating a new codebase to Ruff. You can run `ruff /path/to/file.py --add-noqa` to automatically
add `noqa` directives to all failing lines, with the appropriate error codes.
## Supported Rules
@@ -361,6 +366,14 @@ For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI
| W292 | NoNewLineAtEndOfFile | No newline at end of file | |
| W605 | InvalidEscapeSequence | Invalid escape sequence: '\c' | |
### isort
For more, see [isort](https://pypi.org/project/isort/5.10.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| I001 | UnsortedImports | Import block is un-sorted or un-formatted | 🛠 |
### pydocstyle
For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI.
@@ -427,6 +440,8 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
| U007 | UsePEP604Annotation | Use `X \| Y` for type annotations | 🛠 |
| U008 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | 🛠 |
| U009 | PEP3120UnnecessaryCodingComment | utf-8 encoding declaration is unnecessary | 🛠 |
| U010 | UnnecessaryFutureImport | Unnessary __future__ import `...` for target Python version | |
| U011 | UnnecessaryLRUCacheParams | Unnessary parameters to functools.lru_cache | 🛠 |
### pep8-naming
@@ -460,7 +475,7 @@ For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehens
| C401 | UnnecessaryGeneratorSet | Unnecessary generator (rewrite as a `set` comprehension) | 🛠 |
| C402 | UnnecessaryGeneratorDict | Unnecessary generator (rewrite as a `dict` comprehension) | 🛠 |
| C403 | UnnecessaryListComprehensionSet | Unnecessary `list` comprehension (rewrite as a `set` comprehension) | 🛠 |
| C404 | UnnecessaryListComprehensionDict | Unnecessary `list` comprehension (rewrite as a `dict` comprehension) | |
| C404 | UnnecessaryListComprehensionDict | Unnecessary `list` comprehension (rewrite as a `dict` comprehension) | 🛠 |
| C405 | UnnecessaryLiteralSet | Unnecessary `(list\|tuple)` literal (rewrite as a `set` literal) | 🛠 |
| C406 | UnnecessaryLiteralDict | Unnecessary `(list\|tuple)` literal (rewrite as a `dict` literal) | 🛠 |
| C408 | UnnecessaryCollectionCall | Unnecessary `(dict\|list\|tuple)` call (rewrite as a literal) | 🛠 |
@@ -481,9 +496,13 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/
| ---- | ---- | ------- | --- |
| B002 | UnaryPrefixIncrement | Python does not support the unary prefix increment. | |
| B003 | AssignmentToOsEnviron | Assigning to `os.environ` doesn't clear the environment. | |
| B004 | UnreliableCallableCheck | Using `hasattr(x, '__call__')` to test if x is callable is unreliable. Use `callable(x)` for consistent results. | |
| B005 | StripWithMultiCharacters | Using `.strip()` with multi-character strings is misleading the reader. | |
| B006 | MutableArgumentDefault | Do not use mutable data structures for argument defaults. | |
| B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body. | 🛠 |
| B008 | FunctionCallArgumentDefault | Do not perform function calls in argument defaults. | |
| B009 | GetAttrWithConstant | Do not call `getattr` with a constant attribute value, it is not any safer than normal property access. | 🛠 |
| B010 | SetAttrWithConstant | Do not call `setattr` with a constant attribute value, it is not any safer than normal property access. | |
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
| B013 | RedundantTupleInExceptionHandler | A length-one tuple literal is redundant. Write `except ValueError:` instead of `except (ValueError,):`. | |
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | 🛠 |
@@ -492,6 +511,7 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/
| B017 | NoAssertRaisesException | `assertRaises(Exception):` should be considered evil. | |
| B018 | UselessExpression | Found useless expression. Either assign it to a variable or remove it. | |
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` | |
| B026 | StarArgUnpackingAfterKeywordArg | Star-arg unpacking after a keyword argument is strongly discouraged. | |
### flake8-builtins
@@ -523,12 +543,31 @@ For more, see [flake8-quotes](https://pypi.org/project/flake8-quotes/3.3.1/) on
| Q002 | BadQuotesDocstring | Single quote docstring found but double quotes preferred | |
| Q003 | AvoidQuoteEscape | Change outer quotes to avoid escaping inner quotes | |
### flake8-annotations
For more, see [flake8-annotations](https://pypi.org/project/flake8-annotations/2.9.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ANN001 | MissingTypeFunctionArgument | Missing type annotation for function argument `...` | |
| ANN002 | MissingTypeArgs | Missing type annotation for `*...` | |
| ANN003 | MissingTypeKwargs | Missing type annotation for `**...` | |
| ANN101 | MissingTypeSelf | Missing type annotation for `...` in method | |
| ANN102 | MissingTypeCls | Missing type annotation for `...` in classmethod | |
| ANN201 | MissingReturnTypePublicFunction | Missing return type annotation for public function `...` | |
| ANN202 | MissingReturnTypePrivateFunction | Missing return type annotation for private function `...` | |
| ANN204 | MissingReturnTypeMagicMethod | Missing return type annotation for magic method `...` | |
| ANN205 | MissingReturnTypeStaticMethod | Missing return type annotation for staticmethod `...` | |
| ANN206 | MissingReturnTypeClassMethod | Missing return type annotation for classmethod `...` | |
| ANN401 | DynamicallyTypedExpression | Dynamically typed expressions (typing.Any) are disallowed in `...` | |
### Ruff-specific rules
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| 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'?) | |
### Meta rules
@@ -540,6 +579,10 @@ For more, see [flake8-quotes](https://pypi.org/project/flake8-quotes/3.3.1/) on
## Editor Integrations
### VS Code (Official)
Download the [Ruff VS Code extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff).
### PyCharm
Ruff can be installed as an [External Tool](https://www.jetbrains.com/help/pycharm/configuring-third-party-tools.html)
@@ -552,6 +595,19 @@ Ruff should then appear as a runnable action:
![Ruff as a runnable action](https://user-images.githubusercontent.com/1309177/193156026-732b0aaf-3dd9-4549-9b4d-2de6d2168a33.png)
### Vim & Neovim (Unofficial)
Ruff is available as part of the [coc-pyright](https://github.com/fannheyward/coc-pyright) extension
for coc.nvim.
Ruff can also be integrated via [efm](https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#efm)
in just a [few lines](https://github.com/JafarAbdi/myconfigs/blob/6f0b6b2450e92ec8fc50422928cd22005b919110/efm-langserver/config.yaml#L14-L20).
### Language Server Protocol (Unofficial)
[`ruffd`](https://github.com/Seamooo/ruffd) is a Rust-based language server for Ruff that implements
the Language Server Protocol (LSP).
### GitHub Actions
GitHub Actions has everything you need to run Ruff out-of-the-box:
@@ -588,6 +644,9 @@ stylistic lint rules that are obviated by autoformatting.
### How does Ruff compare to Flake8?
(Coming from Flake8? Try [`flake8-to-ruff`](https://pypi.org/project/flake8-to-ruff/) to
automatically convert your existing configuration.)
Ruff can be used as a (near) drop-in replacement for Flake8 when used (1) without or with a small
number of plugins, (2) alongside Black, and (3) on Python 3 code.
@@ -605,9 +664,10 @@ including:
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (15/32)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (11/34)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (20/32)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/34)
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
Beyond rule-set parity, Ruff suffers from the following limitations vis-à-vis Flake8:
@@ -627,11 +687,12 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (15/32)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (20/32)
Ruff also implements the functionality that you get from [`yesqa`](https://github.com/asottile/yesqa),
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (11/34).
Ruff can also replace [`isort`](https://pypi.org/project/isort/), [`yesqa`](https://github.com/asottile/yesqa),
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/34).
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.
@@ -651,6 +712,34 @@ on Rust at all.
Ruff does not yet support third-party plugins, though a plugin system is within-scope for the
project. See [#283](https://github.com/charliermarsh/ruff/issues/283) for more.
### How does Ruff's import sorting compare to [`isort`](https://pypi.org/project/isort/)?
Ruff's import sorting is intended to be equivalent to `isort` when used `profile = "black"`, and a
few other settings (`combine_as_imports = true`, `order_by_type = false`, and
`case_sensitive` = true`).
Like `isort`, Ruff's import sorting is compatible with Black.
Ruff is less configurable than `isort`, but supports the `known-first-party`, `known-third-party`,
`extra-standard-library`, and `src` settings, like so:
```toml
[tool.ruff]
select = [
# Pyflakes
"F",
# Pycodestyle
"E",
"W",
# isort
"I"
]
src = ["src", "tests"]
[tool.ruff.isort]
known-first-party = ["my_module1", "my_module2"]
```
### Does Ruff support NumPy- or Google-style docstrings?
Yes! To enable a specific docstring convention, start by enabling all `pydocstyle` error codes, and

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.104"
version = "0.0.111"
dependencies = [
"anyhow",
"clap",
@@ -1265,7 +1265,7 @@ checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
[[package]]
name = "libcst"
version = "0.1.0"
source = "git+https://github.com/charliermarsh/LibCST?rev=32a044c127668df44582f85699358e67803b0d73#32a044c127668df44582f85699358e67803b0d73"
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
dependencies = [
"chic",
"itertools",
@@ -1280,7 +1280,7 @@ dependencies = [
[[package]]
name = "libcst_derive"
version = "0.1.0"
source = "git+https://github.com/charliermarsh/LibCST?rev=32a044c127668df44582f85699358e67803b0d73#32a044c127668df44582f85699358e67803b0d73"
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
dependencies = [
"quote",
"syn",
@@ -1505,7 +1505,7 @@ dependencies = [
[[package]]
name = "num_cpus"
version = "1.13.1"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.104"
version = "0.0.111"
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=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
source = "git+https://github.com/RustPython/RustPython.git?rev=27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb#27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb"
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=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
source = "git+https://github.com/RustPython/RustPython.git?rev=27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb#27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb"
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=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
source = "git+https://github.com/RustPython/RustPython.git?rev=27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb#27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb"
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=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
source = "git+https://github.com/RustPython/RustPython.git?rev=27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb#27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb"
dependencies = [
"ahash",
"anyhow",

View File

@@ -1,20 +1,20 @@
[package]
name = "flake8-to-ruff"
version = "0.0.104-dev.0"
version = "0.0.111-dev.0"
edition = "2021"
[lib]
name = "flake8_to_ruff"
[dependencies]
anyhow = { version = "1.0.60" }
anyhow = { version = "1.0.66" }
clap = { version = "4.0.1", features = ["derive"] }
configparser = { version = "3.0.2" }
once_cell = { version = "1.13.1" }
once_cell = { version = "1.16.0" }
regex = { version = "1.6.0" }
ruff = { path = "..", default-features = false }
serde = { version = "1.0.143", features = ["derive"] }
serde_json = { version = "1.0.83" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }
toml = { version = "0.5.9" }
[dev-dependencies]

View File

@@ -1,10 +1,11 @@
use std::collections::{BTreeSet, HashMap};
use anyhow::Result;
use ruff::checks_gen::CheckCodePrefix;
use ruff::flake8_quotes::settings::Quote;
use ruff::settings::options::Options;
use ruff::settings::pyproject::Pyproject;
use ruff::{flake8_quotes, pep8_naming};
use ruff::{flake8_annotations, flake8_quotes, pep8_naming};
use crate::plugin::Plugin;
use crate::{parser, plugin};
@@ -13,6 +14,29 @@ pub fn convert(
flake8: &HashMap<String, Option<String>>,
plugins: Option<Vec<Plugin>>,
) -> Result<Pyproject> {
// Extract all referenced check code prefixes, to power plugin inference.
let mut referenced_codes: BTreeSet<CheckCodePrefix> = Default::default();
for (key, value) in flake8 {
if let Some(value) = value {
match key.as_str() {
"select" | "ignore" | "extend-select" | "extend_select" | "extend-ignore"
| "extend_ignore" => {
referenced_codes.extend(parser::parse_prefix_codes(value.as_ref()));
}
"per-file-ignores" | "per_file_ignores" => {
if let Ok(per_file_ignores) =
parser::parse_files_to_codes_mapping(value.as_ref())
{
for (_, codes) in parser::collect_per_file_ignores(per_file_ignores) {
referenced_codes.extend(codes);
}
}
}
_ => {}
}
}
}
// Check if the user has specified a `select`. If not, we'll add our own
// default `select`, and populate it based on user plugins.
let mut select = flake8
@@ -25,7 +49,12 @@ pub fn convert(
.unwrap_or_else(|| {
plugin::resolve_select(
flake8,
&plugins.unwrap_or_else(|| plugin::infer_plugins(flake8)),
&plugins.unwrap_or_else(|| {
plugin::infer_plugins_from_options(flake8)
.into_iter()
.chain(plugin::infer_plugins_from_codes(&referenced_codes))
.collect()
}),
)
});
let mut ignore = flake8
@@ -39,6 +68,7 @@ pub fn convert(
// Parse each supported option.
let mut options: Options = Default::default();
let mut flake8_annotations: flake8_annotations::settings::Options = Default::default();
let mut flake8_quotes: flake8_quotes::settings::Options = Default::default();
let mut pep8_naming: pep8_naming::settings::Options = Default::default();
for (key, value) in flake8 {
@@ -51,6 +81,7 @@ pub fn convert(
},
"select" => {
// No-op (handled above).
select.extend(parser::parse_prefix_codes(value.as_ref()));
}
"ignore" => {
// No-op (handled above).
@@ -78,6 +109,31 @@ pub fn convert(
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}
// flake8-annotations
"suppress-none-returning" | "suppress_none_returning" => {
match parser::parse_bool(value.as_ref()) {
Ok(bool) => flake8_annotations.suppress_none_returning = Some(bool),
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}
"suppress-dummy-args" | "suppress_dummy_args" => {
match parser::parse_bool(value.as_ref()) {
Ok(bool) => flake8_annotations.suppress_dummy_args = Some(bool),
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}
"mypy-init-return" | "mypy_init_return" => {
match parser::parse_bool(value.as_ref()) {
Ok(bool) => flake8_annotations.mypy_init_return = Some(bool),
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}
"allow-star-arg-any" | "allow_star_arg_any" => {
match parser::parse_bool(value.as_ref()) {
Ok(bool) => flake8_annotations.allow_star_arg_any = Some(bool),
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}
// flake8-quotes
"quotes" | "inline-quotes" | "inline_quotes" => match value.trim() {
"'" | "single" => flake8_quotes.inline_quotes = Some(Quote::Single),
@@ -94,10 +150,9 @@ pub fn convert(
"\"" | "double" => flake8_quotes.docstring_quotes = Some(Quote::Single),
_ => eprintln!("Unexpected '{key}' value: {value}"),
},
"avoid-escape" | "avoid_escape" => match value.trim() {
"true" => flake8_quotes.avoid_escape = Some(true),
"false" => flake8_quotes.avoid_escape = Some(false),
_ => eprintln!("Unexpected '{key}' value: {value}"),
"avoid-escape" | "avoid_escape" => match parser::parse_bool(value.as_ref()) {
Ok(bool) => flake8_quotes.avoid_escape = Some(bool),
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
},
// pep8-naming
"ignore-names" | "ignore_names" => {
@@ -153,6 +208,8 @@ mod tests {
let actual = convert(&HashMap::from([]), None)?;
let expected = Pyproject::new(Options {
line_length: None,
src: None,
fix: None,
exclude: None,
extend_exclude: None,
select: Some(vec![
@@ -166,7 +223,9 @@ mod tests {
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_annotations: None,
flake8_quotes: None,
isort: None,
pep8_naming: None,
});
assert_eq!(actual, expected);
@@ -182,6 +241,8 @@ mod tests {
)?;
let expected = Pyproject::new(Options {
line_length: Some(100),
src: None,
fix: None,
exclude: None,
extend_exclude: None,
select: Some(vec![
@@ -195,7 +256,9 @@ mod tests {
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_annotations: None,
flake8_quotes: None,
isort: None,
pep8_naming: None,
});
assert_eq!(actual, expected);
@@ -211,6 +274,8 @@ mod tests {
)?;
let expected = Pyproject::new(Options {
line_length: Some(100),
src: None,
fix: None,
exclude: None,
extend_exclude: None,
select: Some(vec![
@@ -224,7 +289,9 @@ mod tests {
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_annotations: None,
flake8_quotes: None,
isort: None,
pep8_naming: None,
});
assert_eq!(actual, expected);
@@ -240,6 +307,8 @@ mod tests {
)?;
let expected = Pyproject::new(Options {
line_length: None,
src: None,
fix: None,
exclude: None,
extend_exclude: None,
select: Some(vec![
@@ -253,7 +322,9 @@ mod tests {
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_annotations: None,
flake8_quotes: None,
isort: None,
pep8_naming: None,
});
assert_eq!(actual, expected);
@@ -269,6 +340,8 @@ mod tests {
)?;
let expected = Pyproject::new(Options {
line_length: None,
src: None,
fix: None,
exclude: None,
extend_exclude: None,
select: Some(vec![
@@ -282,12 +355,14 @@ mod tests {
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_annotations: None,
flake8_quotes: Some(flake8_quotes::settings::Options {
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
multiline_quotes: None,
docstring_quotes: None,
avoid_escape: None,
}),
isort: None,
pep8_naming: None,
});
assert_eq!(actual, expected);
@@ -306,6 +381,8 @@ mod tests {
)?;
let expected = Pyproject::new(Options {
line_length: None,
src: None,
fix: None,
exclude: None,
extend_exclude: None,
select: Some(vec![
@@ -354,7 +431,9 @@ mod tests {
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_annotations: None,
flake8_quotes: None,
isort: None,
pep8_naming: None,
});
assert_eq!(actual, expected);
@@ -370,6 +449,8 @@ mod tests {
)?;
let expected = Pyproject::new(Options {
line_length: None,
src: None,
fix: None,
exclude: None,
extend_exclude: None,
select: Some(vec![
@@ -384,12 +465,14 @@ mod tests {
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_annotations: None,
flake8_quotes: Some(flake8_quotes::settings::Options {
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
multiline_quotes: None,
docstring_quotes: None,
avoid_escape: None,
}),
isort: None,
pep8_naming: None,
});
assert_eq!(actual, expected);

View File

@@ -37,6 +37,15 @@ pub fn parse_strings(value: &str) -> Vec<String> {
.collect()
}
/// Parse a boolean.
pub fn parse_bool(value: &str) -> Result<bool> {
match value.trim() {
"true" => Ok(true),
"false" => Ok(false),
_ => Err(anyhow::anyhow!("Unexpected boolean value: {value}")),
}
}
#[derive(Debug)]
struct Token {
token_name: TokenType,

View File

@@ -12,6 +12,7 @@ pub enum Plugin {
Flake8Docstrings,
Flake8Print,
Flake8Quotes,
Flake8Annotations,
PEP8Naming,
Pyupgrade,
}
@@ -27,6 +28,7 @@ impl FromStr for Plugin {
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
"flake8-print" => Ok(Plugin::Flake8Print),
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
"flake8-annotations" => Ok(Plugin::Flake8Annotations),
"pep8-naming" => Ok(Plugin::PEP8Naming),
"pyupgrade" => Ok(Plugin::Pyupgrade),
_ => Err(anyhow!("Unknown plugin: {}", string)),
@@ -35,6 +37,20 @@ impl FromStr for Plugin {
}
impl Plugin {
pub fn default(&self) -> CheckCodePrefix {
match self {
Plugin::Flake8Bugbear => CheckCodePrefix::B,
Plugin::Flake8Builtins => CheckCodePrefix::A,
Plugin::Flake8Comprehensions => CheckCodePrefix::C,
Plugin::Flake8Docstrings => CheckCodePrefix::D,
Plugin::Flake8Print => CheckCodePrefix::T,
Plugin::Flake8Quotes => CheckCodePrefix::Q,
Plugin::Flake8Annotations => CheckCodePrefix::ANN,
Plugin::PEP8Naming => CheckCodePrefix::N,
Plugin::Pyupgrade => CheckCodePrefix::U,
}
}
pub fn select(&self, flake8: &HashMap<String, Option<String>>) -> Vec<CheckCodePrefix> {
match self {
Plugin::Flake8Bugbear => vec![CheckCodePrefix::B],
@@ -58,6 +74,7 @@ impl Plugin {
}
Plugin::Flake8Print => vec![CheckCodePrefix::T],
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
Plugin::Flake8Annotations => vec![CheckCodePrefix::ANN],
Plugin::PEP8Naming => vec![CheckCodePrefix::N],
Plugin::Pyupgrade => vec![CheckCodePrefix::U],
}
@@ -236,8 +253,11 @@ impl DocstringConvention {
}
}
/// Infer the enabled plugins based on user-provided settings.
pub fn infer_plugins(flake8: &HashMap<String, Option<String>>) -> Vec<Plugin> {
/// Infer the enabled plugins based on user-provided options.
///
/// For example, if the user specified a `mypy-init-return` setting, we should
/// infer that `flake8-annotations` is active.
pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> Vec<Plugin> {
let mut plugins = BTreeSet::new();
for key in flake8.keys() {
match key.as_str() {
@@ -262,6 +282,31 @@ pub fn infer_plugins(flake8: &HashMap<String, Option<String>>) -> Vec<Plugin> {
"avoid-escape" | "avoid_escape" => {
plugins.insert(Plugin::Flake8Quotes);
}
// flake8-annotations
"suppress-none-returning" | "suppress_none_returning" => {
plugins.insert(Plugin::Flake8Annotations);
}
"suppress-dummy-args" | "suppress_dummy_args" => {
plugins.insert(Plugin::Flake8Annotations);
}
"allow-untyped-defs" | "allow_untyped_defs" => {
plugins.insert(Plugin::Flake8Annotations);
}
"allow-untyped-nested" | "allow_untyped_nested" => {
plugins.insert(Plugin::Flake8Annotations);
}
"mypy-init-return" | "mypy_init_return" => {
plugins.insert(Plugin::Flake8Annotations);
}
"dispatch-decorators" | "dispatch_decorators" => {
plugins.insert(Plugin::Flake8Annotations);
}
"overload-decorators" | "overload_decorators" => {
plugins.insert(Plugin::Flake8Annotations);
}
"allow-star-arg-any" | "allow_star_arg_any" => {
plugins.insert(Plugin::Flake8Annotations);
}
// pep8-naming
"ignore-names" | "ignore_names" => {
plugins.insert(Plugin::PEP8Naming);
@@ -278,6 +323,38 @@ pub fn infer_plugins(flake8: &HashMap<String, Option<String>>) -> Vec<Plugin> {
Vec::from_iter(plugins)
}
/// Infer the enabled plugins based on the referenced prefixes.
///
/// For example, if the user ignores `ANN101`, we should infer that
/// `flake8-annotations` is active.
pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin> {
[
Plugin::Flake8Bugbear,
Plugin::Flake8Builtins,
Plugin::Flake8Comprehensions,
Plugin::Flake8Docstrings,
Plugin::Flake8Print,
Plugin::Flake8Quotes,
Plugin::Flake8Annotations,
Plugin::PEP8Naming,
Plugin::Pyupgrade,
]
.into_iter()
.filter(|plugin| {
for prefix in codes {
if prefix
.codes()
.iter()
.any(|code| plugin.default().codes().contains(code))
{
return true;
}
}
false
})
.collect()
}
/// Resolve the set of enabled `CheckCodePrefix` values for the given plugins.
pub fn resolve_select(
flake8: &HashMap<String, Option<String>>,
@@ -288,9 +365,7 @@ pub fn resolve_select(
// Add prefix codes for every plugin.
for plugin in plugins {
for prefix in plugin.select(flake8) {
select.insert(prefix);
}
select.extend(plugin.select(flake8));
}
select
@@ -300,18 +375,18 @@ pub fn resolve_select(
mod tests {
use std::collections::HashMap;
use crate::plugin::{infer_plugins, Plugin};
use crate::plugin::{infer_plugins_from_options, Plugin};
#[test]
fn it_infers_plugins() {
let actual = infer_plugins(&HashMap::from([(
let actual = infer_plugins_from_options(&HashMap::from([(
"inline-quotes".to_string(),
Some("single".to_string()),
)]));
let expected = vec![Plugin::Flake8Quotes];
assert_eq!(actual, expected);
let actual = infer_plugins(&HashMap::from([(
let actual = infer_plugins_from_options(&HashMap::from([(
"staticmethod-decorators".to_string(),
Some("[]".to_string()),
)]));

View File

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

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

@@ -0,0 +1,12 @@
def this_is_a_bug():
o = object()
if hasattr(o, "__call__"):
print("Ooh, callable! Or is it?")
if getattr(o, "__call__", False):
print("Ooh, callable! Or is it?")
def this_is_fine():
o = object()
if callable(o):
print("Ooh, this is actually callable.")

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

@@ -0,0 +1,26 @@
s = "qwe"
s.strip(s) # no warning
s.strip("we") # no warning
s.strip(".facebook.com") # warning
s.strip("e") # no warning
s.strip("\n\t ") # no warning
s.strip(r"\n\t ") # warning
s.lstrip(s) # no warning
s.lstrip("we") # no warning
s.lstrip(".facebook.com") # warning
s.lstrip("e") # no warning
s.lstrip("\n\t ") # no warning
s.lstrip(r"\n\t ") # warning
s.rstrip(s) # no warning
s.rstrip("we") # warning
s.rstrip(".facebook.com") # warning
s.rstrip("e") # no warning
s.rstrip("\n\t ") # no warning
s.rstrip(r"\n\t ") # warning
from somewhere import other_type, strip
strip("we") # no warning
other_type().lstrip() # no warning
other_type().rstrip(["a", "b", "c"]) # no warning
other_type().strip("a", "b") # no warning

36
resources/test/fixtures/B009_B010.py vendored Normal file
View File

@@ -0,0 +1,36 @@
"""
Should emit:
B009 - Line 18, 19, 20, 21, 22
B010 - Line 33, 34, 35, 36
"""
# Valid getattr usage
getattr(foo, bar)
getattr(foo, "bar", None)
getattr(foo, "bar{foo}".format(foo="a"), None)
getattr(foo, "bar{foo}".format(foo="a"))
getattr(foo, bar, None)
getattr(foo, "123abc")
getattr(foo, r"123\abc")
getattr(foo, "except")
# Invalid usage
getattr(foo, "bar")
getattr(foo, "_123abc")
getattr(foo, "abc123")
getattr(foo, r"abc123")
_ = lambda x: getattr(x, "bar")
# Valid setattr usage
setattr(foo, bar, None)
setattr(foo, "bar{foo}".format(foo="a"), None)
setattr(foo, "123abc", None)
setattr(foo, r"123\abc", None)
setattr(foo, "except", None)
_ = lambda x: setattr(x, "bar", 1)
# Invalid usage
setattr(foo, "bar", None)
setattr(foo, "_123abc", None)
setattr(foo, "abc123", None)
setattr(foo, r"abc123", None)

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

@@ -0,0 +1,21 @@
"""
Should emit:
B026 - on lines 16, 17, 18, 19, 20, 21
"""
def foo(bar, baz, bam):
print(bar, baz, bam)
bar_baz = ["bar", "baz"]
foo("bar", "baz", bam="bam")
foo("bar", baz="baz", bam="bam")
foo(bar="bar", baz="baz", bam="bam")
foo(bam="bam", *["bar", "baz"])
foo(bam="bam", *bar_baz)
foo(baz="baz", bam="bam", *["bar"])
foo(bar="bar", baz="baz", bam="bam", *[])
foo(bam="bam", *["bar"], *["baz"])
foo(*["bar"], bam="bam", *["baz"])

View File

@@ -1,4 +1,6 @@
x = [2, 3, 1]
list(x)
list(sorted(x))
reversed(sorted(x))
reversed(sorted(x, key=lambda e: e))
reversed(sorted(x, reverse=True))

View File

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

View File

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

7
resources/test/fixtures/RUF003.py vendored Normal file
View File

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

5
resources/test/fixtures/U010.py vendored Normal file
View File

@@ -0,0 +1,5 @@
from __future__ import annotations, nested_scopes, generators
from __future__ import absolute_import, division
from __future__ import generator_stop

103
resources/test/fixtures/U011_0.py vendored Normal file
View File

@@ -0,0 +1,103 @@
import functools
from functools import lru_cache
@lru_cache()
def fixme1():
pass
@other_deco_after
@functools.lru_cache()
def fixme2():
pass
@lru_cache(maxsize=None)
def fixme3():
pass
@functools.lru_cache(maxsize=None)
@other_deco_before
def fixme4():
pass
@lru_cache( # A
) # B
def fixme5():
pass
@lru_cache(
# A
) # B
def fixme6():
pass
@functools.lru_cache(
# A
maxsize = None) # B
def fixme7():
pass
@functools.lru_cache(
# A1
maxsize = None
# A2
) # B
def fixme8():
pass
@functools.lru_cache(
# A1
maxsize =
None
# A2
)
def fixme9():
pass
@functools.lru_cache(
# A1
maxsize =
None
# A2
)
def fixme10():
pass
@lru_cache
def correct1():
pass
@functools.lru_cache
def correct2():
pass
@functoools.lru_cache(maxsize=64)
def correct3():
pass
def user_func():
pass
@lru_cache(user_func)
def correct4():
pass
@lru_cache(user_func, maxsize=None)
def correct5():
pass

15
resources/test/fixtures/U011_1.py vendored Normal file
View File

@@ -0,0 +1,15 @@
import functools
def lru_cache(maxsize=None):
pass
@lru_cache()
def dont_fixme():
pass
@lru_cache(maxsize=None)
def dont_fixme():
pass

View File

@@ -1,9 +0,0 @@
a = "abc"
b = f"ghi{'jkl'}"
c = f"def"
d = f"def" + "ghi"
e = (
f"def" +
"ghi"
)

View File

@@ -0,0 +1,57 @@
from typing import Any
# OK
def foo(a: int, *args: str, **kwargs: str) -> int:
pass
# ANN401
def foo(a: Any, *args: str, **kwargs: str) -> int:
pass
# ANN401
def foo(a: int, *args: str, **kwargs: str) -> Any:
pass
# OK
def foo(a: int, *args: Any, **kwargs: Any) -> int:
pass
# OK
def foo(a: int, *args: Any, **kwargs: str) -> int:
pass
# ANN401
def foo(a: int, *args: str, **kwargs: Any) -> int:
pass
class Bar:
# OK
def foo_method(self, a: int, *params: str, **options: str) -> int:
pass
# ANN401
def foo_method(self, a: Any, *params: str, **options: str) -> int:
pass
# ANN401
def foo_method(self, a: int, *params: str, **options: str) -> Any:
pass
# OK
def foo_method(self, a: int, *params: Any, **options: Any) -> int:
pass
# OK
def foo_method(self, a: int, *params: Any, **options: str) -> int:
pass
# OK
def foo_method(self, a: int, *params: str, **options: Any) -> int:
pass

View File

@@ -0,0 +1,105 @@
from typing import Any, Type
# Error
def foo(a, b):
pass
# Error
def foo(a: int, b):
pass
# Error
def foo(a: int, b) -> int:
pass
# Error
def foo(a: int, b: int):
pass
# Error
def foo():
pass
# OK
def foo(a: int, b: int) -> int:
pass
# OK
def foo() -> int:
pass
# OK
def foo(a: int, *args: str, **kwargs: str) -> int:
pass
# ANN401
def foo(a: Any, *args: str, **kwargs: str) -> int:
pass
# ANN401
def foo(a: int, *args: str, **kwargs: str) -> Any:
pass
# ANN401
def foo(a: int, *args: Any, **kwargs: Any) -> int:
pass
# ANN401
def foo(a: int, *args: Any, **kwargs: str) -> int:
pass
# ANN401
def foo(a: int, *args: str, **kwargs: Any) -> int:
pass
class Foo:
# OK
def foo(self: "Foo", a: int, b: int) -> int:
pass
# ANN101
def foo(self, a: int, b: int) -> int:
pass
# ANN401
def foo(self: "Foo", a: Any, *params: str, **options: str) -> int:
pass
# ANN401
def foo(self: "Foo", a: int, *params: str, **options: str) -> Any:
pass
# ANN401
def foo(self: "Foo", a: int, *params: Any, **options: Any) -> int:
pass
# ANN401
def foo(self: "Foo", a: int, *params: Any, **options: str) -> int:
pass
# ANN401
def foo(self: "Foo", a: int, *params: str, **options: Any) -> int:
pass
# OK
@classmethod
def foo(cls: Type["Foo"], a: int, b: int) -> int:
pass
# ANN102
@classmethod
def foo(cls, a: int, b: int) -> int:
pass

View File

@@ -0,0 +1,41 @@
"""Test case expected to be run with `mypy_init_return = True`."""
# Error
class Foo:
def __init__(self):
...
# Error
class Foo:
def __init__(self, foo):
...
# OK
class Foo:
def __init__(self, foo) -> None:
...
# OK
class Foo:
def __init__(self) -> None:
...
# OK
class Foo:
def __init__(self, foo: int):
...
# OK
class Foo:
def __init__(self, foo: int) -> None:
...
# Error
def __init__(self, foo: int):
...

View File

@@ -0,0 +1,26 @@
"""Test case expected to be run with `suppress_dummy_args = True`."""
# OK
def foo(_) -> None:
...
# OK
def foo(*_) -> None:
...
# OK
def foo(**_) -> None:
...
# OK
def foo(a: int, _) -> None:
...
# OK
def foo() -> None:
def bar(_) -> None:
...

View File

@@ -0,0 +1,55 @@
"""Test case expected to be run with `suppress_none_returning = True`."""
# OK
def foo():
a = 2 + 2
# OK
def foo():
return
# OK
def foo():
return None
# OK
def foo():
a = 2 + 2
if a == 4:
return
else:
return
# OK
def foo():
a = 2 + 2
if a == 4:
return None
else:
return
# OK
def foo():
def bar() -> bool:
return True
bar()
# Error
def foo():
return True
# Error
def foo():
a = 2 + 2
if a == 4:
return True
else:
return

View File

@@ -0,0 +1,5 @@
from collections import Awaitable
from collections import AsyncIterable
from collections import Collection
from collections import ChainMap
from collections import MutableSequence, MutableMapping

View File

@@ -0,0 +1,4 @@
import os
import os
import os as os1
import os as os2

View File

@@ -0,0 +1 @@
from collections import Collection

View File

@@ -0,0 +1,2 @@
from collections import Collection
import os

View File

@@ -0,0 +1,6 @@
x = 1; import sys
import os
if True:
x = 1; import sys
import os

View File

@@ -0,0 +1,3 @@
# OK
import os
import sys

View File

@@ -0,0 +1,6 @@
if True:
import sys
import os
else:
import sys
import os

View File

@@ -0,0 +1,2 @@
import sys
import os

View File

@@ -0,0 +1,5 @@
import sys
import leading_prefix
import numpy as np
import os
from leading_prefix import Class

View File

@@ -0,0 +1,3 @@
import sys
import os
from __future__ import annotations

View File

@@ -0,0 +1,4 @@
import pandas as pd
import sys
import numpy as np
import os

10
resources/test/fixtures/isort/skip.py vendored Normal file
View File

@@ -0,0 +1,10 @@
# isort: off
import sys
import os
import collections
# isort: on
import sys
import os # isort: skip
import collections
import abc

View File

@@ -0,0 +1,6 @@
import sys
import os; x = 1
if True:
import sys
import os; x = 1

View File

@@ -1,9 +1,9 @@
[tool.ruff]
line-length = 88
extend-exclude = [
"excluded.py",
"excluded_file.py",
"migrations",
"directory/also_excluded.py",
"with_excluded_file/other_excluded_file.py",
]
per-file-ignores = { "__init__.py" = ["F401"] }

View File

@@ -1,16 +1,17 @@
[package]
name = "ruff_dev"
version = "0.0.104"
version = "0.0.111"
edition = "2021"
[dependencies]
anyhow = { version = "1.0.60" }
anyhow = { version = "1.0.66" }
clap = { version = "4.0.1", features = ["derive"] }
codegen = { version = "0.2.0" }
itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "a13ec97dd4eb925bde4d426c6e422582793b260c" }
ruff = { path = ".." }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }

View File

@@ -2,4 +2,5 @@ pub mod generate_check_code_prefix;
pub mod generate_rules_table;
pub mod generate_source_code;
pub mod print_ast;
pub mod print_cst;
pub mod print_tokens;

View File

@@ -1,7 +1,8 @@
use anyhow::Result;
use clap::{Parser, Subcommand};
use ruff_dev::{
generate_check_code_prefix, generate_rules_table, generate_source_code, print_ast, print_tokens,
generate_check_code_prefix, generate_rules_table, generate_source_code, print_ast, print_cst,
print_tokens,
};
#[derive(Parser)]
@@ -22,6 +23,8 @@ enum Commands {
GenerateSourceCode(generate_source_code::Cli),
/// Print the AST for a given Python file.
PrintAST(print_ast::Cli),
/// Print the LibCST CST for a given Python file.
PrintCST(print_cst::Cli),
/// Print the token stream for a given Python file.
PrintTokens(print_tokens::Cli),
}
@@ -33,6 +36,7 @@ fn main() -> Result<()> {
Commands::GenerateRulesTable(args) => generate_rules_table::main(args)?,
Commands::GenerateSourceCode(args) => generate_source_code::main(args)?,
Commands::PrintAST(args) => print_ast::main(args)?,
Commands::PrintCST(args) => print_cst::main(args)?,
Commands::PrintTokens(args) => print_tokens::main(args)?,
}
Ok(())

25
ruff_dev/src/print_cst.rs Normal file
View File

@@ -0,0 +1,25 @@
//! Print the LibCST CST for a given Python file.
use std::fs;
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
#[derive(Args)]
pub struct Cli {
/// Python file for which to generate the CST.
#[arg(required = true)]
file: PathBuf,
}
pub fn main(cli: &Cli) -> Result<()> {
let contents = fs::read_to_string(&cli.file)?;
match libcst_native::parse_module(&contents, None) {
Ok(python_cst) => {
println!("{:#?}", python_cst);
Ok(())
}
Err(_) => Err(anyhow::anyhow!("Failed to parse CST")),
}
}

View File

@@ -46,6 +46,7 @@ pub enum ScopeKind<'a> {
Generator,
Module,
Arg,
Lambda,
}
#[derive(Clone, Debug)]
@@ -100,10 +101,6 @@ pub struct Binding {
pub used: Option<(usize, Range)>,
}
pub trait CheckLocator {
fn locate_check(&self, default: Range) -> Range;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ImportKind {
Import,

File diff suppressed because it is too large Load Diff

43
src/check_imports.rs Normal file
View File

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

View File

@@ -1,7 +1,6 @@
//! Lint rules based on checking raw physical lines.
use std::collections::BTreeMap;
use nohash_hasher::IntMap;
use once_cell::sync::Lazy;
use regex::Regex;
use rustpython_parser::ast::Location;
@@ -36,15 +35,15 @@ fn should_enforce_line_length(line: &str, length: usize, limit: usize) -> bool {
pub fn check_lines(
checks: &mut Vec<Check>,
contents: &str,
noqa_line_for: &[usize],
noqa_line_for: &IntMap<usize, usize>,
settings: &Settings,
autofix: &fixer::Mode,
) {
let enforce_unnecessary_coding_comment = settings.enabled.contains(&CheckCode::U009);
let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501);
let enforce_noqa = settings.enabled.contains(&CheckCode::M001);
let mut noqa_directives: BTreeMap<usize, (Directive, Vec<&str>)> = BTreeMap::new();
let mut noqa_directives: IntMap<usize, (Directive, Vec<&str>)> = IntMap::default();
let mut line_checks = vec![];
let mut ignored = vec![];
@@ -54,28 +53,31 @@ pub fn check_lines(
// If there are newlines at the end of the file, they won't be represented in
// `noqa_line_for`, so fallback to the current line.
let noqa_lineno = noqa_line_for
.get(lineno)
.get(&lineno)
.map(|lineno| lineno - 1)
.unwrap_or(lineno);
if lineno < 2 {
// PEP3120 makes utf-8 the default encoding.
if CODING_COMMENT_REGEX.is_match(line) {
let line_length = line.len();
let mut check = Check::new(
CheckKind::PEP3120UnnecessaryCodingComment,
Range {
location: Location::new(lineno + 1, 0),
end_location: Location::new(lineno + 1, line_length + 1),
},
);
if autofix.patch() {
check.amend(Fix::deletion(
Location::new(lineno + 1, 0),
Location::new(lineno + 1, line_length + 1),
));
// Enforce unnecessary coding comments (U009).
if enforce_unnecessary_coding_comment {
if lineno < 2 {
// PEP3120 makes utf-8 the default encoding.
if CODING_COMMENT_REGEX.is_match(line) {
let line_length = line.len();
let mut check = Check::new(
CheckKind::PEP3120UnnecessaryCodingComment,
Range {
location: Location::new(lineno + 1, 0),
end_location: Location::new(lineno + 1, line_length + 1),
},
);
if autofix.patch() {
check.amend(Fix::deletion(
Location::new(lineno + 1, 0),
Location::new(lineno + 1, line_length + 1),
));
}
line_checks.push(check);
}
line_checks.push(check);
}
}
@@ -109,7 +111,7 @@ pub fn check_lines(
}
}
// Enforce line length.
// Enforce line length violations (E501).
if enforce_line_too_long {
let line_length = line.chars().count();
if should_enforce_line_length(line, line_length, settings.line_length) {
@@ -149,7 +151,7 @@ pub fn check_lines(
if let Some(line) = lines.last() {
let lineno = lines.len() - 1;
let noqa_lineno = noqa_line_for
.get(lineno)
.get(&lineno)
.map(|lineno| lineno - 1)
.unwrap_or(lineno);
@@ -253,6 +255,8 @@ pub fn check_lines(
#[cfg(test)]
mod tests {
use nohash_hasher::IntMap;
use super::check_lines;
use crate::autofix::fixer;
use crate::checks::{Check, CheckCode};
@@ -261,7 +265,7 @@ mod tests {
#[test]
fn e501_non_ascii_char() {
let line = "'\u{4e9c}' * 2"; // 7 in UTF-32, 9 in UTF-8.
let noqa_line_for: Vec<usize> = vec![1];
let noqa_line_for: IntMap<usize, usize> = Default::default();
let check_with_max_line_length = |line_length: usize| {
let mut checks: Vec<Check> = vec![];
check_lines(

View File

@@ -5,6 +5,7 @@ use rustpython_parser::lexer::{LexResult, Tok};
use crate::autofix::fixer;
use crate::checks::{Check, CheckCode};
use crate::lex::docstring_detection::StateMachine;
use crate::rules::checks::Context;
use crate::source_code_locator::SourceCodeLocator;
use crate::{flake8_quotes, pycodestyle, rules, Settings};
@@ -16,7 +17,8 @@ pub fn check_tokens(
autofix: &fixer::Mode,
) {
let enforce_ambiguous_unicode_character = settings.enabled.contains(&CheckCode::RUF001)
|| settings.enabled.contains(&CheckCode::RUF002);
|| settings.enabled.contains(&CheckCode::RUF002)
|| settings.enabled.contains(&CheckCode::RUF003);
let enforce_quotes = settings.enabled.contains(&CheckCode::Q000)
|| settings.enabled.contains(&CheckCode::Q001)
|| settings.enabled.contains(&CheckCode::Q002)
@@ -31,14 +33,22 @@ pub fn check_tokens(
false
};
// RUF001, RUF002
// RUF001, RUF002, RUF003
if enforce_ambiguous_unicode_character {
if matches!(tok, Tok::String { .. }) {
if matches!(tok, Tok::String { .. } | Tok::Comment) {
for check in rules::checks::ambiguous_unicode_character(
locator,
start,
end,
is_docstring,
if matches!(tok, Tok::String { .. }) {
if is_docstring {
Context::Docstring
} else {
Context::String
}
} else {
Context::Comment
},
autofix.patch(),
) {
if settings.enabled.contains(check.kind.code()) {

View File

@@ -79,9 +79,13 @@ pub enum CheckCode {
// flake8-bugbear
B002,
B003,
B004,
B005,
B006,
B007,
B008,
B009,
B010,
B011,
B013,
B014,
@@ -90,6 +94,7 @@ pub enum CheckCode {
B017,
B018,
B025,
B026,
// flake8-comprehensions
C400,
C401,
@@ -115,6 +120,18 @@ pub enum CheckCode {
Q001,
Q002,
Q003,
// flake8-annotations
ANN001,
ANN002,
ANN003,
ANN101,
ANN102,
ANN201,
ANN202,
ANN204,
ANN205,
ANN206,
ANN401,
// pyupgrade
U001,
U002,
@@ -125,6 +142,8 @@ pub enum CheckCode {
U007,
U008,
U009,
U010,
U011,
// pydocstyle
D100,
D101,
@@ -186,9 +205,12 @@ pub enum CheckCode {
N816,
N817,
N818,
// isort
I001,
// Ruff
RUF001,
RUF002,
RUF003,
// Meta
M001,
}
@@ -197,6 +219,7 @@ pub enum CheckCode {
pub enum CheckCategory {
Pyflakes,
Pycodestyle,
Isort,
Pydocstyle,
Pyupgrade,
PEP8Naming,
@@ -205,6 +228,7 @@ pub enum CheckCategory {
Flake8Builtins,
Flake8Print,
Flake8Quotes,
Flake8Annotations,
Ruff,
Meta,
}
@@ -214,11 +238,13 @@ impl CheckCategory {
match self {
CheckCategory::Pycodestyle => "pycodestyle",
CheckCategory::Pyflakes => "Pyflakes",
CheckCategory::Isort => "isort",
CheckCategory::Flake8Builtins => "flake8-builtins",
CheckCategory::Flake8Bugbear => "flake8-bugbear",
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
CheckCategory::Flake8Print => "flake8-print",
CheckCategory::Flake8Quotes => "flake8-quotes",
CheckCategory::Flake8Annotations => "flake8-annotations",
CheckCategory::Pyupgrade => "pyupgrade",
CheckCategory::Pydocstyle => "pydocstyle",
CheckCategory::PEP8Naming => "pep8-naming",
@@ -231,6 +257,7 @@ impl CheckCategory {
match self {
CheckCategory::Pycodestyle => Some("https://pypi.org/project/pycodestyle/2.9.1/"),
CheckCategory::Pyflakes => Some("https://pypi.org/project/pyflakes/2.5.0/"),
CheckCategory::Isort => Some("https://pypi.org/project/isort/5.10.1/"),
CheckCategory::Flake8Builtins => {
Some("https://pypi.org/project/flake8-builtins/2.0.1/")
}
@@ -242,6 +269,9 @@ impl CheckCategory {
}
CheckCategory::Flake8Print => Some("https://pypi.org/project/flake8-print/5.0.0/"),
CheckCategory::Flake8Quotes => Some("https://pypi.org/project/flake8-quotes/3.3.1/"),
CheckCategory::Flake8Annotations => {
Some("https://pypi.org/project/flake8-annotations/2.9.1/")
}
CheckCategory::Pyupgrade => Some("https://pypi.org/project/pyupgrade/3.2.0/"),
CheckCategory::Pydocstyle => Some("https://pypi.org/project/pydocstyle/6.1.1/"),
CheckCategory::PEP8Naming => Some("https://pypi.org/project/pep8-naming/0.13.2/"),
@@ -257,6 +287,7 @@ pub enum LintSource {
FileSystem,
Lines,
Tokens,
Imports,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -321,9 +352,13 @@ pub enum CheckKind {
// flake8-bugbear
UnaryPrefixIncrement,
AssignmentToOsEnviron,
UnreliableCallableCheck,
StripWithMultiCharacters,
MutableArgumentDefault,
UnusedLoopControlVariable(String),
FunctionCallArgumentDefault,
GetAttrWithConstant,
SetAttrWithConstant,
DoNotAssertFalse,
RedundantTupleInExceptionHandler(String),
DuplicateHandlerException(Vec<String>),
@@ -332,6 +367,7 @@ pub enum CheckKind {
NoAssertRaisesException,
UselessExpression,
DuplicateTryBlockException(String),
StarArgUnpackingAfterKeywordArg,
// flake8-comprehensions
UnnecessaryGeneratorList,
UnnecessaryGeneratorSet,
@@ -357,6 +393,18 @@ pub enum CheckKind {
BadQuotesMultilineString(Quote),
BadQuotesDocstring(Quote),
AvoidQuoteEscape,
// flake8-annotations
MissingTypeFunctionArgument(String),
MissingTypeArgs(String),
MissingTypeKwargs(String),
MissingTypeSelf(String),
MissingTypeCls(String),
MissingReturnTypePublicFunction(String),
MissingReturnTypePrivateFunction(String),
MissingReturnTypeMagicMethod(String),
MissingReturnTypeStaticMethod(String),
MissingReturnTypeClassMethod(String),
DynamicallyTypedExpression(String),
// pyupgrade
TypeOfPrimitive(Primitive),
UnnecessaryAbspath,
@@ -367,6 +415,8 @@ pub enum CheckKind {
UsePEP604Annotation,
SuperCallWithParameters,
PEP3120UnnecessaryCodingComment,
UnnecessaryFutureImport(String),
UnnecessaryLRUCacheParams,
// pydocstyle
BlankLineAfterLastSection(String),
BlankLineAfterSection(String),
@@ -428,9 +478,12 @@ pub enum CheckKind {
MixedCaseVariableInGlobalScope(String),
CamelcaseImportedAsAcronym(String, String),
ErrorSuffixOnExceptionName(String),
// isort
UnsortedImports,
// Ruff
AmbiguousUnicodeCharacterString(char, char),
AmbiguousUnicodeCharacterDocstring(char, char),
AmbiguousUnicodeCharacterComment(char, char),
// Meta
UnusedNOQA(Option<Vec<String>>),
}
@@ -449,8 +502,10 @@ impl CheckCode {
| CheckCode::Q003
| CheckCode::W605
| CheckCode::RUF001
| CheckCode::RUF002 => &LintSource::Tokens,
| CheckCode::RUF002
| CheckCode::RUF003 => &LintSource::Tokens,
CheckCode::E902 => &LintSource::FileSystem,
CheckCode::I001 => &LintSource::Imports,
_ => &LintSource::AST,
}
}
@@ -514,9 +569,13 @@ impl CheckCode {
// flake8-bugbear
CheckCode::B002 => CheckKind::UnaryPrefixIncrement,
CheckCode::B003 => CheckKind::AssignmentToOsEnviron,
CheckCode::B004 => CheckKind::UnreliableCallableCheck,
CheckCode::B005 => CheckKind::StripWithMultiCharacters,
CheckCode::B006 => CheckKind::MutableArgumentDefault,
CheckCode::B007 => CheckKind::UnusedLoopControlVariable("i".to_string()),
CheckCode::B008 => CheckKind::FunctionCallArgumentDefault,
CheckCode::B009 => CheckKind::GetAttrWithConstant,
CheckCode::B010 => CheckKind::SetAttrWithConstant,
CheckCode::B011 => CheckKind::DoNotAssertFalse,
CheckCode::B013 => {
CheckKind::RedundantTupleInExceptionHandler("ValueError".to_string())
@@ -527,6 +586,7 @@ impl CheckCode {
CheckCode::B017 => CheckKind::NoAssertRaisesException,
CheckCode::B018 => CheckKind::UselessExpression,
CheckCode::B025 => CheckKind::DuplicateTryBlockException("Exception".to_string()),
CheckCode::B026 => CheckKind::StarArgUnpackingAfterKeywordArg,
// flake8-comprehensions
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
CheckCode::C401 => CheckKind::UnnecessaryGeneratorSet,
@@ -565,6 +625,18 @@ impl CheckCode {
CheckCode::Q001 => CheckKind::BadQuotesMultilineString(Quote::Double),
CheckCode::Q002 => CheckKind::BadQuotesDocstring(Quote::Double),
CheckCode::Q003 => CheckKind::AvoidQuoteEscape,
// flake8-annotations
CheckCode::ANN001 => CheckKind::MissingTypeFunctionArgument("...".to_string()),
CheckCode::ANN002 => CheckKind::MissingTypeArgs("...".to_string()),
CheckCode::ANN003 => CheckKind::MissingTypeKwargs("...".to_string()),
CheckCode::ANN101 => CheckKind::MissingTypeSelf("...".to_string()),
CheckCode::ANN102 => CheckKind::MissingTypeCls("...".to_string()),
CheckCode::ANN201 => CheckKind::MissingReturnTypePublicFunction("...".to_string()),
CheckCode::ANN202 => CheckKind::MissingReturnTypePrivateFunction("...".to_string()),
CheckCode::ANN204 => CheckKind::MissingReturnTypeMagicMethod("...".to_string()),
CheckCode::ANN205 => CheckKind::MissingReturnTypeStaticMethod("...".to_string()),
CheckCode::ANN206 => CheckKind::MissingReturnTypeClassMethod("...".to_string()),
CheckCode::ANN401 => CheckKind::DynamicallyTypedExpression("...".to_string()),
// pyupgrade
CheckCode::U001 => CheckKind::UselessMetaclassType,
CheckCode::U002 => CheckKind::UnnecessaryAbspath,
@@ -578,6 +650,8 @@ impl CheckCode {
CheckCode::U007 => CheckKind::UsePEP604Annotation,
CheckCode::U008 => CheckKind::SuperCallWithParameters,
CheckCode::U009 => CheckKind::PEP3120UnnecessaryCodingComment,
CheckCode::U010 => CheckKind::UnnecessaryFutureImport("...".to_string()),
CheckCode::U011 => CheckKind::UnnecessaryLRUCacheParams,
// pydocstyle
CheckCode::D100 => CheckKind::PublicModule,
CheckCode::D101 => CheckKind::PublicClass,
@@ -655,9 +729,12 @@ impl CheckCode {
CheckKind::CamelcaseImportedAsAcronym("...".to_string(), "...".to_string())
}
CheckCode::N818 => CheckKind::ErrorSuffixOnExceptionName("...".to_string()),
// isort
CheckCode::I001 => CheckKind::UnsortedImports,
// Ruff
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
CheckCode::RUF003 => CheckKind::AmbiguousUnicodeCharacterComment('𝐁', 'B'),
// Meta
CheckCode::M001 => CheckKind::UnusedNOQA(None),
}
@@ -714,9 +791,13 @@ impl CheckCode {
CheckCode::A003 => CheckCategory::Flake8Builtins,
CheckCode::B002 => CheckCategory::Flake8Bugbear,
CheckCode::B003 => CheckCategory::Flake8Bugbear,
CheckCode::B004 => CheckCategory::Flake8Bugbear,
CheckCode::B005 => CheckCategory::Flake8Bugbear,
CheckCode::B006 => CheckCategory::Flake8Bugbear,
CheckCode::B007 => CheckCategory::Flake8Bugbear,
CheckCode::B008 => CheckCategory::Flake8Bugbear,
CheckCode::B009 => CheckCategory::Flake8Bugbear,
CheckCode::B010 => CheckCategory::Flake8Bugbear,
CheckCode::B011 => CheckCategory::Flake8Bugbear,
CheckCode::B013 => CheckCategory::Flake8Bugbear,
CheckCode::B014 => CheckCategory::Flake8Bugbear,
@@ -725,6 +806,7 @@ impl CheckCode {
CheckCode::B017 => CheckCategory::Flake8Bugbear,
CheckCode::B018 => CheckCategory::Flake8Bugbear,
CheckCode::B025 => CheckCategory::Flake8Bugbear,
CheckCode::B026 => CheckCategory::Flake8Bugbear,
CheckCode::C400 => CheckCategory::Flake8Comprehensions,
CheckCode::C401 => CheckCategory::Flake8Comprehensions,
CheckCode::C402 => CheckCategory::Flake8Comprehensions,
@@ -747,6 +829,17 @@ impl CheckCode {
CheckCode::Q001 => CheckCategory::Flake8Quotes,
CheckCode::Q002 => CheckCategory::Flake8Quotes,
CheckCode::Q003 => CheckCategory::Flake8Quotes,
CheckCode::ANN001 => CheckCategory::Flake8Annotations,
CheckCode::ANN002 => CheckCategory::Flake8Annotations,
CheckCode::ANN003 => CheckCategory::Flake8Annotations,
CheckCode::ANN101 => CheckCategory::Flake8Annotations,
CheckCode::ANN102 => CheckCategory::Flake8Annotations,
CheckCode::ANN201 => CheckCategory::Flake8Annotations,
CheckCode::ANN202 => CheckCategory::Flake8Annotations,
CheckCode::ANN204 => CheckCategory::Flake8Annotations,
CheckCode::ANN205 => CheckCategory::Flake8Annotations,
CheckCode::ANN206 => CheckCategory::Flake8Annotations,
CheckCode::ANN401 => CheckCategory::Flake8Annotations,
CheckCode::U001 => CheckCategory::Pyupgrade,
CheckCode::U002 => CheckCategory::Pyupgrade,
CheckCode::U003 => CheckCategory::Pyupgrade,
@@ -756,6 +849,8 @@ impl CheckCode {
CheckCode::U007 => CheckCategory::Pyupgrade,
CheckCode::U008 => CheckCategory::Pyupgrade,
CheckCode::U009 => CheckCategory::Pyupgrade,
CheckCode::U010 => CheckCategory::Pyupgrade,
CheckCode::U011 => CheckCategory::Pyupgrade,
CheckCode::D100 => CheckCategory::Pydocstyle,
CheckCode::D101 => CheckCategory::Pydocstyle,
CheckCode::D102 => CheckCategory::Pydocstyle,
@@ -815,8 +910,10 @@ impl CheckCode {
CheckCode::N816 => CheckCategory::PEP8Naming,
CheckCode::N817 => CheckCategory::PEP8Naming,
CheckCode::N818 => CheckCategory::PEP8Naming,
CheckCode::I001 => CheckCategory::Isort,
CheckCode::RUF001 => CheckCategory::Ruff,
CheckCode::RUF002 => CheckCategory::Ruff,
CheckCode::RUF003 => CheckCategory::Ruff,
CheckCode::M001 => CheckCategory::Meta,
}
}
@@ -879,9 +976,13 @@ impl CheckKind {
// flake8-bugbear
CheckKind::UnaryPrefixIncrement => &CheckCode::B002,
CheckKind::AssignmentToOsEnviron => &CheckCode::B003,
CheckKind::UnreliableCallableCheck => &CheckCode::B004,
CheckKind::StripWithMultiCharacters => &CheckCode::B005,
CheckKind::MutableArgumentDefault => &CheckCode::B006,
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
CheckKind::FunctionCallArgumentDefault => &CheckCode::B008,
CheckKind::GetAttrWithConstant => &CheckCode::B009,
CheckKind::SetAttrWithConstant => &CheckCode::B010,
CheckKind::DoNotAssertFalse => &CheckCode::B011,
CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013,
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
@@ -890,6 +991,7 @@ impl CheckKind {
CheckKind::NoAssertRaisesException => &CheckCode::B017,
CheckKind::UselessExpression => &CheckCode::B018,
CheckKind::DuplicateTryBlockException(_) => &CheckCode::B025,
CheckKind::StarArgUnpackingAfterKeywordArg => &CheckCode::B026,
// flake8-comprehensions
CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
CheckKind::UnnecessaryGeneratorSet => &CheckCode::C401,
@@ -915,6 +1017,18 @@ impl CheckKind {
CheckKind::BadQuotesMultilineString(_) => &CheckCode::Q001,
CheckKind::BadQuotesDocstring(_) => &CheckCode::Q002,
CheckKind::AvoidQuoteEscape => &CheckCode::Q003,
// flake8-annotations
CheckKind::MissingTypeFunctionArgument(_) => &CheckCode::ANN001,
CheckKind::MissingTypeArgs(_) => &CheckCode::ANN002,
CheckKind::MissingTypeKwargs(_) => &CheckCode::ANN003,
CheckKind::MissingTypeSelf(_) => &CheckCode::ANN101,
CheckKind::MissingTypeCls(_) => &CheckCode::ANN102,
CheckKind::MissingReturnTypePublicFunction(_) => &CheckCode::ANN201,
CheckKind::MissingReturnTypePrivateFunction(_) => &CheckCode::ANN202,
CheckKind::MissingReturnTypeMagicMethod(_) => &CheckCode::ANN204,
CheckKind::MissingReturnTypeStaticMethod(_) => &CheckCode::ANN205,
CheckKind::MissingReturnTypeClassMethod(_) => &CheckCode::ANN206,
CheckKind::DynamicallyTypedExpression(_) => &CheckCode::ANN401,
// pyupgrade
CheckKind::TypeOfPrimitive(_) => &CheckCode::U003,
CheckKind::UnnecessaryAbspath => &CheckCode::U002,
@@ -925,6 +1039,8 @@ impl CheckKind {
CheckKind::UselessObjectInheritance(_) => &CheckCode::U004,
CheckKind::SuperCallWithParameters => &CheckCode::U008,
CheckKind::PEP3120UnnecessaryCodingComment => &CheckCode::U009,
CheckKind::UnnecessaryFutureImport(_) => &CheckCode::U010,
CheckKind::UnnecessaryLRUCacheParams => &CheckCode::U011,
// pydocstyle
CheckKind::BlankLineAfterLastSection(_) => &CheckCode::D413,
CheckKind::BlankLineAfterSection(_) => &CheckCode::D410,
@@ -986,9 +1102,12 @@ impl CheckKind {
CheckKind::MixedCaseVariableInGlobalScope(..) => &CheckCode::N816,
CheckKind::CamelcaseImportedAsAcronym(..) => &CheckCode::N817,
CheckKind::ErrorSuffixOnExceptionName(..) => &CheckCode::N818,
// isort
CheckKind::UnsortedImports => &CheckCode::I001,
// Ruff
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
CheckKind::AmbiguousUnicodeCharacterComment(..) => &CheckCode::RUF003,
// Meta
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
}
@@ -1152,6 +1271,13 @@ impl CheckKind {
CheckKind::AssignmentToOsEnviron => {
"Assigning to `os.environ` doesn't clear the environment.".to_string()
}
CheckKind::UnreliableCallableCheck => " Using `hasattr(x, '__call__')` to test if x \
is callable is unreliable. Use `callable(x)` \
for consistent results."
.to_string(),
CheckKind::StripWithMultiCharacters => "Using `.strip()` with multi-character strings \
is misleading the reader."
.to_string(),
CheckKind::MutableArgumentDefault => {
"Do not use mutable data structures for argument defaults.".to_string()
}
@@ -1162,6 +1288,14 @@ impl CheckKind {
CheckKind::FunctionCallArgumentDefault => {
"Do not perform function calls in argument defaults.".to_string()
}
CheckKind::GetAttrWithConstant => "Do not call `getattr` with a constant attribute \
value, it is not any safer than normal property \
access."
.to_string(),
CheckKind::SetAttrWithConstant => "Do not call `setattr` with a constant attribute \
value, it is not any safer than normal property \
access."
.to_string(),
CheckKind::DoNotAssertFalse => "Do not `assert False` (`python -O` removes these \
calls), raise `AssertionError()`"
.to_string(),
@@ -1200,6 +1334,13 @@ impl CheckKind {
CheckKind::DuplicateTryBlockException(name) => {
format!("try-except block with duplicate exception `{name}`")
}
CheckKind::StarArgUnpackingAfterKeywordArg => {
"Star-arg unpacking after a keyword argument is strongly discouraged, because it \
only works when the keyword parameter is declared after all parameters supplied \
by the unpacked sequence, and this change of ordering can surprise and mislead \
readers."
.to_string()
}
// flake8-comprehensions
CheckKind::UnnecessaryGeneratorList => {
"Unnecessary generator (rewrite as a `list` comprehension)".to_string()
@@ -1300,6 +1441,38 @@ impl CheckKind {
CheckKind::AvoidQuoteEscape => {
"Change outer quotes to avoid escaping inner quotes".to_string()
}
// flake8-annotations
CheckKind::MissingTypeFunctionArgument(name) => {
format!("Missing type annotation for function argument `{name}`")
}
CheckKind::MissingTypeArgs(name) => format!("Missing type annotation for `*{name}`"),
CheckKind::MissingTypeKwargs(name) => {
format!("Missing type annotation for `**{name}`")
}
CheckKind::MissingTypeSelf(name) => {
format!("Missing type annotation for `{name}` in method")
}
CheckKind::MissingTypeCls(name) => {
format!("Missing type annotation for `{name}` in classmethod")
}
CheckKind::MissingReturnTypePublicFunction(name) => {
format!("Missing return type annotation for public function `{name}`")
}
CheckKind::MissingReturnTypePrivateFunction(name) => {
format!("Missing return type annotation for private function `{name}`")
}
CheckKind::MissingReturnTypeMagicMethod(name) => {
format!("Missing return type annotation for magic method `{name}`")
}
CheckKind::MissingReturnTypeStaticMethod(name) => {
format!("Missing return type annotation for staticmethod `{name}`")
}
CheckKind::MissingReturnTypeClassMethod(name) => {
format!("Missing return type annotation for classmethod `{name}`")
}
CheckKind::DynamicallyTypedExpression(name) => {
format!("Dynamically typed expressions (typing.Any) are disallowed in `{name}`")
}
// pyupgrade
CheckKind::TypeOfPrimitive(primitive) => {
format!("Use `{}` instead of `type(...)`", primitive.builtin())
@@ -1325,6 +1498,12 @@ impl CheckKind {
CheckKind::SuperCallWithParameters => {
"Use `super()` instead of `super(__class__, self)`".to_string()
}
CheckKind::UnnecessaryFutureImport(name) => {
format!("Unnessary __future__ import `{name}` for target Python version")
}
CheckKind::UnnecessaryLRUCacheParams => {
"Unnessary parameters to functools.lru_cache".to_string()
}
// pydocstyle
CheckKind::FitsOnOneLine => "One-line docstring should fit on one line".to_string(),
CheckKind::BlankLineAfterSummary => {
@@ -1488,6 +1667,8 @@ impl CheckKind {
CheckKind::PEP3120UnnecessaryCodingComment => {
"utf-8 encoding declaration is unnecessary".to_string()
}
// isort
CheckKind::UnsortedImports => "Import block is un-sorted or un-formatted".to_string(),
// Ruff
CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) => {
format!(
@@ -1501,6 +1682,12 @@ impl CheckKind {
'{representant}'?)"
)
}
CheckKind::AmbiguousUnicodeCharacterComment(confusable, representant) => {
format!(
"Comment contains ambiguous unicode character '{confusable}' (did you mean \
'{representant}'?)"
)
}
// Meta
CheckKind::UnusedNOQA(codes) => match codes {
None => "Unused `noqa` directive".to_string(),
@@ -1534,6 +1721,9 @@ impl CheckKind {
CheckKind::NoAssertRaisesException => {
"`assertRaises(Exception):` should be considered evil.".to_string()
}
CheckKind::StarArgUnpackingAfterKeywordArg => {
"Star-arg unpacking after a keyword argument is strongly discouraged.".to_string()
}
_ => self.body(),
}
}
@@ -1553,6 +1743,8 @@ impl CheckKind {
| CheckKind::DeprecatedUnittestAlias(_, _)
| CheckKind::DoNotAssertFalse
| CheckKind::DuplicateHandlerException(_)
| CheckKind::GetAttrWithConstant
| CheckKind::IsLiteral
| CheckKind::NewLineAfterLastParagraph
| CheckKind::NewLineAfterSectionName(_)
| CheckKind::NoBlankLineAfterFunction(_)
@@ -1564,6 +1756,7 @@ impl CheckKind {
| CheckKind::NoUnderIndentation
| CheckKind::OneBlankLineAfterClass(_)
| CheckKind::OneBlankLineBeforeClass(_)
| CheckKind::PEP3120UnnecessaryCodingComment
| CheckKind::PPrintFound
| CheckKind::PrintFound
| CheckKind::RaiseNotImplemented
@@ -1580,12 +1773,15 @@ impl CheckKind {
| CheckKind::UnnecessaryGeneratorDict
| CheckKind::UnnecessaryGeneratorList
| CheckKind::UnnecessaryGeneratorSet
| CheckKind::UnnecessaryLRUCacheParams
| CheckKind::UnnecessaryListCall
| CheckKind::UnnecessaryListComprehensionDict
| CheckKind::UnnecessaryListComprehensionSet
| CheckKind::UnnecessaryLiteralDict(_)
| CheckKind::UnnecessaryLiteralSet(_)
| CheckKind::UnnecessaryLiteralWithinListCall(_)
| CheckKind::UnnecessaryLiteralWithinTupleCall(_)
| CheckKind::UnsortedImports
| CheckKind::UnusedImport(_, false)
| CheckKind::UnusedLoopControlVariable(_)
| CheckKind::UnusedNOQA(_)
@@ -1593,8 +1789,6 @@ impl CheckKind {
| CheckKind::UsePEP604Annotation
| CheckKind::UselessMetaclassType
| CheckKind::UselessObjectInheritance(_)
| CheckKind::PEP3120UnnecessaryCodingComment
| CheckKind::IsLiteral
)
}
}

View File

@@ -13,15 +13,39 @@ pub enum CheckCodePrefix {
A001,
A002,
A003,
ANN,
ANN0,
ANN00,
ANN001,
ANN002,
ANN003,
ANN1,
ANN10,
ANN101,
ANN102,
ANN2,
ANN20,
ANN201,
ANN202,
ANN204,
ANN205,
ANN206,
ANN4,
ANN40,
ANN401,
B,
B0,
B00,
B002,
B003,
B004,
B005,
B006,
B007,
B008,
B009,
B01,
B010,
B011,
B013,
B014,
@@ -31,6 +55,7 @@ pub enum CheckCodePrefix {
B018,
B02,
B025,
B026,
C,
C4,
C40,
@@ -179,6 +204,10 @@ pub enum CheckCodePrefix {
F9,
F90,
F901,
I,
I0,
I00,
I001,
M,
M0,
M00,
@@ -214,6 +243,7 @@ pub enum CheckCodePrefix {
RUF00,
RUF001,
RUF002,
RUF003,
T,
T2,
T20,
@@ -231,6 +261,9 @@ pub enum CheckCodePrefix {
U007,
U008,
U009,
U01,
U010,
U011,
W,
W2,
W29,
@@ -257,12 +290,60 @@ impl CheckCodePrefix {
CheckCodePrefix::A001 => vec![CheckCode::A001],
CheckCodePrefix::A002 => vec![CheckCode::A002],
CheckCodePrefix::A003 => vec![CheckCode::A003],
CheckCodePrefix::ANN => vec![
CheckCode::ANN001,
CheckCode::ANN002,
CheckCode::ANN003,
CheckCode::ANN101,
CheckCode::ANN102,
CheckCode::ANN201,
CheckCode::ANN202,
CheckCode::ANN204,
CheckCode::ANN205,
CheckCode::ANN206,
CheckCode::ANN401,
],
CheckCodePrefix::ANN0 => vec![CheckCode::ANN001, CheckCode::ANN002, CheckCode::ANN003],
CheckCodePrefix::ANN00 => vec![CheckCode::ANN001, CheckCode::ANN002, CheckCode::ANN003],
CheckCodePrefix::ANN001 => vec![CheckCode::ANN001],
CheckCodePrefix::ANN002 => vec![CheckCode::ANN002],
CheckCodePrefix::ANN003 => vec![CheckCode::ANN003],
CheckCodePrefix::ANN1 => vec![CheckCode::ANN101, CheckCode::ANN102],
CheckCodePrefix::ANN10 => vec![CheckCode::ANN101, CheckCode::ANN102],
CheckCodePrefix::ANN101 => vec![CheckCode::ANN101],
CheckCodePrefix::ANN102 => vec![CheckCode::ANN102],
CheckCodePrefix::ANN2 => vec![
CheckCode::ANN201,
CheckCode::ANN202,
CheckCode::ANN204,
CheckCode::ANN205,
CheckCode::ANN206,
],
CheckCodePrefix::ANN20 => vec![
CheckCode::ANN201,
CheckCode::ANN202,
CheckCode::ANN204,
CheckCode::ANN205,
CheckCode::ANN206,
],
CheckCodePrefix::ANN201 => vec![CheckCode::ANN201],
CheckCodePrefix::ANN202 => vec![CheckCode::ANN202],
CheckCodePrefix::ANN204 => vec![CheckCode::ANN204],
CheckCodePrefix::ANN205 => vec![CheckCode::ANN205],
CheckCodePrefix::ANN206 => vec![CheckCode::ANN206],
CheckCodePrefix::ANN4 => vec![CheckCode::ANN401],
CheckCodePrefix::ANN40 => vec![CheckCode::ANN401],
CheckCodePrefix::ANN401 => vec![CheckCode::ANN401],
CheckCodePrefix::B => vec![
CheckCode::B002,
CheckCode::B003,
CheckCode::B004,
CheckCode::B005,
CheckCode::B006,
CheckCode::B007,
CheckCode::B008,
CheckCode::B009,
CheckCode::B010,
CheckCode::B011,
CheckCode::B013,
CheckCode::B014,
@@ -271,13 +352,18 @@ impl CheckCodePrefix {
CheckCode::B017,
CheckCode::B018,
CheckCode::B025,
CheckCode::B026,
],
CheckCodePrefix::B0 => vec![
CheckCode::B002,
CheckCode::B003,
CheckCode::B004,
CheckCode::B005,
CheckCode::B006,
CheckCode::B007,
CheckCode::B008,
CheckCode::B009,
CheckCode::B010,
CheckCode::B011,
CheckCode::B013,
CheckCode::B014,
@@ -286,20 +372,28 @@ impl CheckCodePrefix {
CheckCode::B017,
CheckCode::B018,
CheckCode::B025,
CheckCode::B026,
],
CheckCodePrefix::B00 => vec![
CheckCode::B002,
CheckCode::B003,
CheckCode::B004,
CheckCode::B005,
CheckCode::B006,
CheckCode::B007,
CheckCode::B008,
CheckCode::B009,
],
CheckCodePrefix::B002 => vec![CheckCode::B002],
CheckCodePrefix::B003 => vec![CheckCode::B003],
CheckCodePrefix::B004 => vec![CheckCode::B004],
CheckCodePrefix::B005 => vec![CheckCode::B005],
CheckCodePrefix::B006 => vec![CheckCode::B006],
CheckCodePrefix::B007 => vec![CheckCode::B007],
CheckCodePrefix::B008 => vec![CheckCode::B008],
CheckCodePrefix::B009 => vec![CheckCode::B009],
CheckCodePrefix::B01 => vec![
CheckCode::B010,
CheckCode::B011,
CheckCode::B013,
CheckCode::B014,
@@ -308,6 +402,7 @@ impl CheckCodePrefix {
CheckCode::B017,
CheckCode::B018,
],
CheckCodePrefix::B010 => vec![CheckCode::B010],
CheckCodePrefix::B011 => vec![CheckCode::B011],
CheckCodePrefix::B013 => vec![CheckCode::B013],
CheckCodePrefix::B014 => vec![CheckCode::B014],
@@ -315,8 +410,9 @@ impl CheckCodePrefix {
CheckCodePrefix::B016 => vec![CheckCode::B016],
CheckCodePrefix::B017 => vec![CheckCode::B017],
CheckCodePrefix::B018 => vec![CheckCode::B018],
CheckCodePrefix::B02 => vec![CheckCode::B025],
CheckCodePrefix::B02 => vec![CheckCode::B025, CheckCode::B026],
CheckCodePrefix::B025 => vec![CheckCode::B025],
CheckCodePrefix::B026 => vec![CheckCode::B026],
CheckCodePrefix::C => vec![
CheckCode::C400,
CheckCode::C401,
@@ -765,6 +861,10 @@ impl CheckCodePrefix {
CheckCodePrefix::F9 => vec![CheckCode::F901],
CheckCodePrefix::F90 => vec![CheckCode::F901],
CheckCodePrefix::F901 => vec![CheckCode::F901],
CheckCodePrefix::I => vec![CheckCode::I001],
CheckCodePrefix::I0 => vec![CheckCode::I001],
CheckCodePrefix::I00 => vec![CheckCode::I001],
CheckCodePrefix::I001 => vec![CheckCode::I001],
CheckCodePrefix::M => vec![CheckCode::M001],
CheckCodePrefix::M0 => vec![CheckCode::M001],
CheckCodePrefix::M00 => vec![CheckCode::M001],
@@ -859,11 +959,12 @@ impl CheckCodePrefix {
CheckCodePrefix::Q001 => vec![CheckCode::Q001],
CheckCodePrefix::Q002 => vec![CheckCode::Q002],
CheckCodePrefix::Q003 => vec![CheckCode::Q003],
CheckCodePrefix::RUF => vec![CheckCode::RUF001, CheckCode::RUF002],
CheckCodePrefix::RUF0 => vec![CheckCode::RUF001, CheckCode::RUF002],
CheckCodePrefix::RUF00 => vec![CheckCode::RUF001, CheckCode::RUF002],
CheckCodePrefix::RUF => vec![CheckCode::RUF001, CheckCode::RUF002, CheckCode::RUF003],
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::T => vec![CheckCode::T201, CheckCode::T203],
CheckCodePrefix::T2 => vec![CheckCode::T201, CheckCode::T203],
CheckCodePrefix::T20 => vec![CheckCode::T201, CheckCode::T203],
@@ -879,6 +980,8 @@ impl CheckCodePrefix {
CheckCode::U007,
CheckCode::U008,
CheckCode::U009,
CheckCode::U010,
CheckCode::U011,
],
CheckCodePrefix::U0 => vec![
CheckCode::U001,
@@ -890,6 +993,8 @@ impl CheckCodePrefix {
CheckCode::U007,
CheckCode::U008,
CheckCode::U009,
CheckCode::U010,
CheckCode::U011,
],
CheckCodePrefix::U00 => vec![
CheckCode::U001,
@@ -911,6 +1016,9 @@ impl CheckCodePrefix {
CheckCodePrefix::U007 => vec![CheckCode::U007],
CheckCodePrefix::U008 => vec![CheckCode::U008],
CheckCodePrefix::U009 => vec![CheckCode::U009],
CheckCodePrefix::U01 => vec![CheckCode::U010, CheckCode::U011],
CheckCodePrefix::U010 => vec![CheckCode::U010],
CheckCodePrefix::U011 => vec![CheckCode::U011],
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
CheckCodePrefix::W2 => vec![CheckCode::W292],
CheckCodePrefix::W29 => vec![CheckCode::W292],
@@ -931,15 +1039,39 @@ impl CheckCodePrefix {
CheckCodePrefix::A001 => PrefixSpecificity::Explicit,
CheckCodePrefix::A002 => PrefixSpecificity::Explicit,
CheckCodePrefix::A003 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN => PrefixSpecificity::Category,
CheckCodePrefix::ANN0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::ANN00 => PrefixSpecificity::Tens,
CheckCodePrefix::ANN001 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN002 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN003 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN1 => PrefixSpecificity::Hundreds,
CheckCodePrefix::ANN10 => PrefixSpecificity::Tens,
CheckCodePrefix::ANN101 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN102 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN2 => PrefixSpecificity::Hundreds,
CheckCodePrefix::ANN20 => PrefixSpecificity::Tens,
CheckCodePrefix::ANN201 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN202 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN204 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN205 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN206 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN4 => PrefixSpecificity::Hundreds,
CheckCodePrefix::ANN40 => PrefixSpecificity::Tens,
CheckCodePrefix::ANN401 => PrefixSpecificity::Explicit,
CheckCodePrefix::B => PrefixSpecificity::Category,
CheckCodePrefix::B0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::B00 => PrefixSpecificity::Tens,
CheckCodePrefix::B002 => PrefixSpecificity::Explicit,
CheckCodePrefix::B003 => PrefixSpecificity::Explicit,
CheckCodePrefix::B004 => PrefixSpecificity::Explicit,
CheckCodePrefix::B005 => PrefixSpecificity::Explicit,
CheckCodePrefix::B006 => PrefixSpecificity::Explicit,
CheckCodePrefix::B007 => PrefixSpecificity::Explicit,
CheckCodePrefix::B008 => PrefixSpecificity::Explicit,
CheckCodePrefix::B009 => PrefixSpecificity::Explicit,
CheckCodePrefix::B01 => PrefixSpecificity::Tens,
CheckCodePrefix::B010 => PrefixSpecificity::Explicit,
CheckCodePrefix::B011 => PrefixSpecificity::Explicit,
CheckCodePrefix::B013 => PrefixSpecificity::Explicit,
CheckCodePrefix::B014 => PrefixSpecificity::Explicit,
@@ -949,6 +1081,7 @@ impl CheckCodePrefix {
CheckCodePrefix::B018 => PrefixSpecificity::Explicit,
CheckCodePrefix::B02 => PrefixSpecificity::Tens,
CheckCodePrefix::B025 => PrefixSpecificity::Explicit,
CheckCodePrefix::B026 => PrefixSpecificity::Explicit,
CheckCodePrefix::C => PrefixSpecificity::Category,
CheckCodePrefix::C4 => PrefixSpecificity::Hundreds,
CheckCodePrefix::C40 => PrefixSpecificity::Tens,
@@ -1097,6 +1230,10 @@ impl CheckCodePrefix {
CheckCodePrefix::F9 => PrefixSpecificity::Hundreds,
CheckCodePrefix::F90 => PrefixSpecificity::Tens,
CheckCodePrefix::F901 => PrefixSpecificity::Explicit,
CheckCodePrefix::I => PrefixSpecificity::Category,
CheckCodePrefix::I0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::I00 => PrefixSpecificity::Tens,
CheckCodePrefix::I001 => PrefixSpecificity::Explicit,
CheckCodePrefix::M => PrefixSpecificity::Category,
CheckCodePrefix::M0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::M00 => PrefixSpecificity::Tens,
@@ -1132,6 +1269,7 @@ impl CheckCodePrefix {
CheckCodePrefix::RUF00 => PrefixSpecificity::Tens,
CheckCodePrefix::RUF001 => PrefixSpecificity::Explicit,
CheckCodePrefix::RUF002 => PrefixSpecificity::Explicit,
CheckCodePrefix::RUF003 => PrefixSpecificity::Explicit,
CheckCodePrefix::T => PrefixSpecificity::Category,
CheckCodePrefix::T2 => PrefixSpecificity::Hundreds,
CheckCodePrefix::T20 => PrefixSpecificity::Tens,
@@ -1149,6 +1287,9 @@ impl CheckCodePrefix {
CheckCodePrefix::U007 => PrefixSpecificity::Explicit,
CheckCodePrefix::U008 => PrefixSpecificity::Explicit,
CheckCodePrefix::U009 => PrefixSpecificity::Explicit,
CheckCodePrefix::U01 => PrefixSpecificity::Tens,
CheckCodePrefix::U010 => PrefixSpecificity::Explicit,
CheckCodePrefix::U011 => PrefixSpecificity::Explicit,
CheckCodePrefix::W => PrefixSpecificity::Category,
CheckCodePrefix::W2 => PrefixSpecificity::Hundreds,
CheckCodePrefix::W29 => PrefixSpecificity::Tens,

View File

@@ -10,7 +10,7 @@ use crate::checks_gen::CheckCodePrefix;
use crate::logging::LogLevel;
use crate::printer::SerializationFormat;
use crate::settings::configuration::Configuration;
use crate::settings::types::{PatternPrefixPair, PythonVersion};
use crate::settings::types::{PatternPrefixPair, PerFileIgnore, PythonVersion};
#[derive(Debug, Parser)]
#[command(author, about = "ruff: An extremely fast Python linter.")]
@@ -38,8 +38,10 @@ pub struct Cli {
#[arg(short, long)]
pub watch: bool,
/// Attempt to automatically fix lint errors.
#[arg(short, long)]
pub fix: bool,
#[arg(long, overrides_with("no_fix"))]
fix: bool,
#[clap(long, overrides_with("fix"), hide = true)]
no_fix: bool,
/// Disable cache reads.
#[arg(short, long)]
pub no_cache: bool,
@@ -94,6 +96,22 @@ pub struct Cli {
pub stdin_filename: Option<String>,
}
impl Cli {
// See: https://github.com/clap-rs/clap/issues/3146
pub fn fix(&self) -> Option<bool> {
resolve_bool_arg(self.fix, self.no_fix)
}
}
fn resolve_bool_arg(yes: bool, no: bool) -> Option<bool> {
match (yes, no) {
(true, false) => Some(true),
(false, true) => Some(false),
(false, false) => None,
(..) => unreachable!("Clap should make this impossible"),
}
}
/// Map the CLI settings to a `LogLevel`.
pub fn extract_log_level(cli: &Cli) -> LogLevel {
if cli.silent {
@@ -165,10 +183,11 @@ pub fn warn_on(
}
}
/// Collect a list of `PatternPrefixPair` structs as a `BTreeMap`.
/// Convert a list of `PatternPrefixPair` structs to `PerFileIgnore`.
pub fn collect_per_file_ignores(
pairs: Vec<PatternPrefixPair>,
) -> BTreeMap<String, Vec<CheckCodePrefix>> {
project_root: &Option<PathBuf>,
) -> Vec<PerFileIgnore> {
let mut per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>> = BTreeMap::new();
for pair in pairs {
per_file_ignores
@@ -177,4 +196,7 @@ pub fn collect_per_file_ignores(
.push(pair.prefix);
}
per_file_ignores
.iter()
.map(|(pattern, prefixes)| PerFileIgnore::new(pattern, prefixes, project_root))
.collect()
}

206
src/directives.rs Normal file
View File

@@ -0,0 +1,206 @@
//! Extract `# noqa` and `# isort: skip` directives from tokenized source.
use bitflags::bitflags;
use nohash_hasher::{IntMap, IntSet};
use rustpython_ast::Location;
use rustpython_parser::lexer::{LexResult, Tok};
use crate::ast::types::Range;
use crate::checks::LintSource;
use crate::{Settings, SourceCodeLocator};
bitflags! {
pub struct Flags: u32 {
const NOQA = 0b00000001;
const ISORT = 0b00000010;
}
}
impl Flags {
pub fn from_settings(settings: &Settings) -> Self {
if settings
.enabled
.iter()
.any(|check_code| matches!(check_code.lint_source(), LintSource::Imports))
{
Flags::NOQA | Flags::ISORT
} else {
Flags::NOQA
}
}
}
pub struct Directives {
pub noqa_line_for: IntMap<usize, usize>,
pub isort_exclusions: IntSet<usize>,
}
pub fn extract_directives(
lxr: &[LexResult],
locator: &SourceCodeLocator,
flags: &Flags,
) -> Directives {
Directives {
noqa_line_for: if flags.contains(Flags::NOQA) {
extract_noqa_line_for(lxr)
} else {
Default::default()
},
isort_exclusions: if flags.contains(Flags::ISORT) {
extract_isort_exclusions(lxr, locator)
} else {
Default::default()
},
}
}
/// Extract a mapping from logical line to noqa line.
pub fn extract_noqa_line_for(lxr: &[LexResult]) -> IntMap<usize, usize> {
let mut noqa_line_for: IntMap<usize, usize> = IntMap::default();
for (start, tok, end) in lxr.iter().flatten() {
if matches!(tok, Tok::EndOfFile) {
break;
}
// For multi-line strings, we expect `noqa` directives on the last line of the
// string.
if matches!(tok, Tok::String { .. }) && end.row() > start.row() {
for i in start.row()..end.row() {
noqa_line_for.insert(i, end.row());
}
}
}
noqa_line_for
}
/// Extract a set of lines over which to disable isort.
pub fn extract_isort_exclusions(lxr: &[LexResult], locator: &SourceCodeLocator) -> IntSet<usize> {
let mut exclusions: IntSet<usize> = IntSet::default();
let mut off: Option<&Location> = None;
for (start, tok, end) in lxr.iter().flatten() {
// TODO(charlie): Modify RustPython to include the comment text in the token.
if matches!(tok, Tok::Comment) {
let comment_text = locator.slice_source_code_range(&Range {
location: *start,
end_location: *end,
});
if off.is_some() {
if comment_text == "# isort: on" {
if let Some(start) = off {
for row in start.row() + 1..=end.row() {
exclusions.insert(row);
}
}
off = None;
}
} else {
if comment_text.contains("isort: skip") || comment_text.contains("isort:skip") {
exclusions.insert(start.row());
} else if comment_text == "# isort: off" {
off = Some(start);
}
}
} else if matches!(tok, Tok::EndOfFile) {
if let Some(start) = off {
for row in start.row() + 1..=end.row() {
exclusions.insert(row);
}
}
break;
}
}
exclusions
}
#[cfg(test)]
mod tests {
use anyhow::Result;
use nohash_hasher::IntMap;
use rustpython_parser::lexer;
use rustpython_parser::lexer::LexResult;
use crate::directives::extract_noqa_line_for;
#[test]
fn extraction() -> Result<()> {
let empty: IntMap<usize, usize> = Default::default();
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
y = 2
z = x + 1",
)
.collect();
assert_eq!(extract_noqa_line_for(&lxr), empty);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"
x = 1
y = 2
z = x + 1",
)
.collect();
assert_eq!(extract_noqa_line_for(&lxr), empty);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
y = 2
z = x + 1
",
)
.collect();
assert_eq!(extract_noqa_line_for(&lxr), empty);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
y = 2
z = x + 1
",
)
.collect();
assert_eq!(extract_noqa_line_for(&lxr), empty);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = '''abc
def
ghi
'''
y = 2
z = x + 1",
)
.collect();
assert_eq!(
extract_noqa_line_for(&lxr),
IntMap::from_iter([(1, 4), (2, 4), (3, 4)])
);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
y = '''abc
def
ghi
'''
z = 2",
)
.collect();
assert_eq!(
extract_noqa_line_for(&lxr),
IntMap::from_iter([(2, 5), (3, 5), (4, 5)])
);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
y = '''abc
def
ghi
'''",
)
.collect();
assert_eq!(
extract_noqa_line_for(&lxr),
IntMap::from_iter([(2, 5), (3, 5), (4, 5)])
);
Ok(())
}
}

View File

@@ -1,4 +1,4 @@
use rustpython_ast::{Expr, Location};
use rustpython_ast::{Located, Location};
use crate::ast::types::Range;
use crate::check_ast::Checker;
@@ -24,9 +24,9 @@ pub fn leading_space(line: &str) -> String {
.collect()
}
/// Extract the leading indentation from a docstring.
pub fn indentation<'a>(checker: &'a Checker, docstring: &Expr) -> String {
let range = Range::from_located(docstring);
/// Extract the leading indentation from a line.
pub fn indentation<'a, T>(checker: &'a Checker, located: &Located<T>) -> String {
let range = Range::from_located(located);
checker
.locator
.slice_source_code_range(&Range {

View File

@@ -0,0 +1,138 @@
pub mod plugins;
pub mod settings;
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use crate::autofix::fixer;
use crate::checks::CheckCode;
use crate::linter::test_path;
use crate::{flake8_annotations, Settings};
#[test]
fn defaults() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_annotations/annotation_presence.py"),
&Settings {
..Settings::for_rules(vec![
CheckCode::ANN001,
CheckCode::ANN002,
CheckCode::ANN003,
CheckCode::ANN101,
CheckCode::ANN102,
CheckCode::ANN201,
CheckCode::ANN202,
CheckCode::ANN204,
CheckCode::ANN205,
CheckCode::ANN206,
CheckCode::ANN401,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn suppress_dummy_args() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_annotations/suppress_dummy_args.py"),
&Settings {
flake8_annotations: flake8_annotations::settings::Settings {
mypy_init_return: false,
suppress_dummy_args: true,
suppress_none_returning: false,
allow_star_arg_any: false,
},
..Settings::for_rules(vec![
CheckCode::ANN001,
CheckCode::ANN002,
CheckCode::ANN003,
CheckCode::ANN101,
CheckCode::ANN102,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn mypy_init_return() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_annotations/mypy_init_return.py"),
&Settings {
flake8_annotations: flake8_annotations::settings::Settings {
mypy_init_return: true,
suppress_dummy_args: false,
suppress_none_returning: false,
allow_star_arg_any: false,
},
..Settings::for_rules(vec![
CheckCode::ANN201,
CheckCode::ANN202,
CheckCode::ANN204,
CheckCode::ANN205,
CheckCode::ANN206,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn suppress_none_returning() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_annotations/suppress_none_returning.py"),
&Settings {
flake8_annotations: flake8_annotations::settings::Settings {
mypy_init_return: false,
suppress_dummy_args: false,
suppress_none_returning: true,
allow_star_arg_any: false,
},
..Settings::for_rules(vec![
CheckCode::ANN201,
CheckCode::ANN202,
CheckCode::ANN204,
CheckCode::ANN205,
CheckCode::ANN206,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn allow_star_arg_any() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_annotations/allow_star_arg_any.py"),
&Settings {
flake8_annotations: flake8_annotations::settings::Settings {
mypy_init_return: false,
suppress_dummy_args: false,
suppress_none_returning: false,
allow_star_arg_any: true,
},
..Settings::for_rules(vec![CheckCode::ANN401])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
}

View File

@@ -0,0 +1,376 @@
use rustpython_ast::{Arguments, Constant, Expr, ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::check_ast::Checker;
use crate::checks::{CheckCode, CheckKind};
use crate::docstrings::definition::{Definition, DefinitionKind};
use crate::visibility::Visibility;
use crate::{visibility, Check};
#[derive(Default)]
struct ReturnStatementVisitor<'a> {
returns: Vec<&'a Option<Box<Expr>>>,
}
impl<'a, 'b> Visitor<'b> for ReturnStatementVisitor<'a>
where
'b: 'a,
{
fn visit_stmt(&mut self, stmt: &'b Stmt) {
match &stmt.node {
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
// No recurse.
}
StmtKind::Return { value } => self.returns.push(value),
_ => visitor::walk_stmt(self, stmt),
}
}
}
fn is_none_returning(body: &[Stmt]) -> bool {
let mut visitor: ReturnStatementVisitor = Default::default();
for stmt in body {
visitor.visit_stmt(stmt);
}
for expr in visitor.returns.into_iter().flatten() {
if !matches!(
expr.node,
ExprKind::Constant {
value: Constant::None,
..
}
) {
return false;
}
}
true
}
/// ANN401
fn check_dynamically_typed(checker: &mut Checker, annotation: &Expr, name: &str) {
if checker.match_typing_module(annotation, "Any") {
checker.add_check(Check::new(
CheckKind::DynamicallyTypedExpression(name.to_string()),
Range::from_located(annotation),
));
};
}
fn match_function_def(stmt: &Stmt) -> (&str, &Arguments, &Option<Box<Expr>>, &Vec<Stmt>) {
match &stmt.node {
StmtKind::FunctionDef {
name,
args,
returns,
body,
..
}
| StmtKind::AsyncFunctionDef {
name,
args,
returns,
body,
..
} => (name, args, returns, body),
_ => panic!("Found non-FunctionDef in match_name"),
}
}
/// Generate flake8-annotation checks for a given `Definition`.
pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &Visibility) {
// TODO(charlie): Consider using the AST directly here rather than `Definition`.
// We could adhere more closely to `flake8-annotations` by defining public
// vs. secret vs. protected.
match &definition.kind {
DefinitionKind::Module => {}
DefinitionKind::Package => {}
DefinitionKind::Class(_) => {}
DefinitionKind::NestedClass(_) => {}
DefinitionKind::Function(stmt) | DefinitionKind::NestedFunction(stmt) => {
let (name, args, returns, body) = match_function_def(stmt);
// ANN001, ANN401
for arg in args
.args
.iter()
.chain(args.posonlyargs.iter())
.chain(args.kwonlyargs.iter())
{
if let Some(expr) = &arg.node.annotation {
if checker.settings.enabled.contains(&CheckCode::ANN401) {
check_dynamically_typed(checker, expr, &arg.node.arg);
};
} else {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker.settings.enabled.contains(&CheckCode::ANN001) {
checker.add_check(Check::new(
CheckKind::MissingTypeFunctionArgument(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
}
}
}
// ANN002, ANN401
if let Some(arg) = &args.vararg {
if let Some(expr) = &arg.node.annotation {
if !checker.settings.flake8_annotations.allow_star_arg_any {
if checker.settings.enabled.contains(&CheckCode::ANN401) {
let name = arg.node.arg.to_string();
check_dynamically_typed(checker, expr, &format!("*{name}"));
}
}
} else {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker.settings.enabled.contains(&CheckCode::ANN002) {
checker.add_check(Check::new(
CheckKind::MissingTypeArgs(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
}
}
}
// ANN003, ANN401
if let Some(arg) = &args.kwarg {
if let Some(expr) = &arg.node.annotation {
if !checker.settings.flake8_annotations.allow_star_arg_any {
if checker.settings.enabled.contains(&CheckCode::ANN401) {
let name = arg.node.arg.to_string();
check_dynamically_typed(checker, expr, &format!("**{name}"));
}
}
} else {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker.settings.enabled.contains(&CheckCode::ANN003) {
checker.add_check(Check::new(
CheckKind::MissingTypeKwargs(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
}
}
}
// ANN201, ANN202, ANN401
if let Some(expr) = &returns {
if checker.settings.enabled.contains(&CheckCode::ANN401) {
check_dynamically_typed(checker, expr, name);
};
} else {
// Allow omission of return annotation in `__init__` functions, if the function
// only returns `None` (explicitly or implicitly).
if checker.settings.flake8_annotations.suppress_none_returning
&& is_none_returning(body)
{
return;
}
match visibility {
Visibility::Public => {
if checker.settings.enabled.contains(&CheckCode::ANN201) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePublicFunction(name.to_string()),
Range::from_located(stmt),
));
}
}
Visibility::Private => {
if checker.settings.enabled.contains(&CheckCode::ANN202) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePrivateFunction(name.to_string()),
Range::from_located(stmt),
));
}
}
}
}
}
DefinitionKind::Method(stmt) => {
let (name, args, returns, body) = match_function_def(stmt);
let mut has_any_typed_arg = false;
// ANN001
for arg in args
.args
.iter()
.chain(args.posonlyargs.iter())
.chain(args.kwonlyargs.iter())
.skip(
// If this is a non-static method, skip `cls` or `self`.
usize::from(!visibility::is_staticmethod(stmt)),
)
{
// ANN401 for dynamically typed arguments
if let Some(annotation) = &arg.node.annotation {
has_any_typed_arg = true;
if checker.settings.enabled.contains(&CheckCode::ANN401) {
check_dynamically_typed(checker, annotation, &arg.node.arg);
}
} else {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker.settings.enabled.contains(&CheckCode::ANN001) {
checker.add_check(Check::new(
CheckKind::MissingTypeFunctionArgument(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
}
}
}
// ANN002, ANN401
if let Some(arg) = &args.vararg {
has_any_typed_arg = true;
if let Some(expr) = &arg.node.annotation {
if !checker.settings.flake8_annotations.allow_star_arg_any {
if checker.settings.enabled.contains(&CheckCode::ANN401) {
let name = arg.node.arg.to_string();
check_dynamically_typed(checker, expr, &format!("*{name}"));
}
}
} else {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker.settings.enabled.contains(&CheckCode::ANN002) {
checker.add_check(Check::new(
CheckKind::MissingTypeArgs(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
}
}
}
// ANN003, ANN401
if let Some(arg) = &args.kwarg {
has_any_typed_arg = true;
if let Some(expr) = &arg.node.annotation {
if !checker.settings.flake8_annotations.allow_star_arg_any {
if checker.settings.enabled.contains(&CheckCode::ANN401) {
let name = arg.node.arg.to_string();
check_dynamically_typed(checker, expr, &format!("**{name}"));
}
}
} else {
if !(checker.settings.flake8_annotations.suppress_dummy_args
&& checker.settings.dummy_variable_rgx.is_match(&arg.node.arg))
{
if checker.settings.enabled.contains(&CheckCode::ANN003) {
checker.add_check(Check::new(
CheckKind::MissingTypeKwargs(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
}
}
}
// ANN101, ANN102
if !visibility::is_staticmethod(stmt) {
if let Some(arg) = args.args.first() {
if arg.node.annotation.is_none() {
if visibility::is_classmethod(stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN102) {
checker.add_check(Check::new(
CheckKind::MissingTypeCls(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
} else {
if checker.settings.enabled.contains(&CheckCode::ANN101) {
checker.add_check(Check::new(
CheckKind::MissingTypeSelf(arg.node.arg.to_string()),
Range::from_located(arg),
));
}
}
}
}
}
// ANN201, ANN202
if let Some(expr) = &returns {
if checker.settings.enabled.contains(&CheckCode::ANN401) {
check_dynamically_typed(checker, expr, name);
}
} else {
// Allow omission of return annotation in `__init__` functions, if the function
// only returns `None` (explicitly or implicitly).
if checker.settings.flake8_annotations.suppress_none_returning
&& is_none_returning(body)
{
return;
}
if visibility::is_classmethod(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) {
if checker.settings.enabled.contains(&CheckCode::ANN205) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeStaticMethod(name.to_string()),
Range::from_located(stmt),
));
}
} else if visibility::is_magic(stmt) {
if checker.settings.enabled.contains(&CheckCode::ANN204) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeMagicMethod(name.to_string()),
Range::from_located(stmt),
));
}
} else if visibility::is_init(stmt) {
// Allow omission of return annotation in `__init__` functions, as long as at
// least one argument is typed.
if checker.settings.enabled.contains(&CheckCode::ANN204) {
if !(checker.settings.flake8_annotations.mypy_init_return
&& has_any_typed_arg)
{
checker.add_check(Check::new(
CheckKind::MissingReturnTypeMagicMethod(name.to_string()),
Range::from_located(stmt),
));
}
}
} else {
match visibility {
Visibility::Public => {
if checker.settings.enabled.contains(&CheckCode::ANN201) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePublicFunction(name.to_string()),
Range::from_located(stmt),
));
}
}
Visibility::Private => {
if checker.settings.enabled.contains(&CheckCode::ANN202) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypePrivateFunction(name.to_string()),
Range::from_located(stmt),
));
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,40 @@
//! Settings for the `flake-annotations` plugin.
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[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.
pub mypy_init_return: Option<bool>,
/// Suppress ANN000-level errors for dummy arguments, like `_`.
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).
pub suppress_none_returning: Option<bool>,
/// Suppress ANN401 for dynamically typed *args and **kwargs.
pub allow_star_arg_any: Option<bool>,
}
#[derive(Debug, Hash, Default)]
pub struct Settings {
pub mypy_init_return: bool,
pub suppress_dummy_args: bool,
pub suppress_none_returning: bool,
pub allow_star_arg_any: bool,
}
impl Settings {
pub fn from_options(options: Options) -> Self {
Self {
mypy_init_return: options.mypy_init_return.unwrap_or_default(),
suppress_dummy_args: options.suppress_dummy_args.unwrap_or_default(),
suppress_none_returning: options.suppress_none_returning.unwrap_or_default(),
allow_star_arg_any: options.allow_star_arg_any.unwrap_or_default(),
}
}
}

View File

@@ -0,0 +1,41 @@
---
source: src/flake8_annotations/mod.rs
expression: checks
---
- kind:
DynamicallyTypedExpression: a
location:
row: 10
column: 11
end_location:
row: 10
column: 14
fix: ~
- kind:
DynamicallyTypedExpression: foo
location:
row: 15
column: 46
end_location:
row: 15
column: 49
fix: ~
- kind:
DynamicallyTypedExpression: a
location:
row: 40
column: 28
end_location:
row: 40
column: 31
fix: ~
- kind:
DynamicallyTypedExpression: foo_method
location:
row: 44
column: 66
end_location:
row: 44
column: 69
fix: ~

View File

@@ -0,0 +1,203 @@
---
source: src/flake8_annotations/mod.rs
expression: checks
---
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 4
column: 0
end_location:
row: 9
column: 0
fix: ~
- kind:
MissingTypeFunctionArgument: a
location:
row: 4
column: 8
end_location:
row: 4
column: 9
fix: ~
- kind:
MissingTypeFunctionArgument: b
location:
row: 4
column: 11
end_location:
row: 4
column: 12
fix: ~
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 9
column: 0
end_location:
row: 14
column: 0
fix: ~
- kind:
MissingTypeFunctionArgument: b
location:
row: 9
column: 16
end_location:
row: 9
column: 17
fix: ~
- kind:
MissingTypeFunctionArgument: b
location:
row: 14
column: 16
end_location:
row: 14
column: 17
fix: ~
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 19
column: 0
end_location:
row: 24
column: 0
fix: ~
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 24
column: 0
end_location:
row: 29
column: 0
fix: ~
- kind:
DynamicallyTypedExpression: a
location:
row: 44
column: 11
end_location:
row: 44
column: 14
fix: ~
- kind:
DynamicallyTypedExpression: foo
location:
row: 49
column: 46
end_location:
row: 49
column: 49
fix: ~
- kind:
DynamicallyTypedExpression: "*args"
location:
row: 54
column: 23
end_location:
row: 54
column: 26
fix: ~
- kind:
DynamicallyTypedExpression: "**kwargs"
location:
row: 54
column: 38
end_location:
row: 54
column: 41
fix: ~
- kind:
DynamicallyTypedExpression: "*args"
location:
row: 59
column: 23
end_location:
row: 59
column: 26
fix: ~
- kind:
DynamicallyTypedExpression: "**kwargs"
location:
row: 64
column: 38
end_location:
row: 64
column: 41
fix: ~
- kind:
MissingTypeSelf: self
location:
row: 74
column: 12
end_location:
row: 74
column: 16
fix: ~
- kind:
DynamicallyTypedExpression: a
location:
row: 78
column: 28
end_location:
row: 78
column: 31
fix: ~
- kind:
DynamicallyTypedExpression: foo
location:
row: 82
column: 66
end_location:
row: 82
column: 69
fix: ~
- kind:
DynamicallyTypedExpression: "*params"
location:
row: 86
column: 42
end_location:
row: 86
column: 45
fix: ~
- kind:
DynamicallyTypedExpression: "**options"
location:
row: 86
column: 58
end_location:
row: 86
column: 61
fix: ~
- kind:
DynamicallyTypedExpression: "*params"
location:
row: 90
column: 42
end_location:
row: 90
column: 45
fix: ~
- kind:
DynamicallyTypedExpression: "**options"
location:
row: 94
column: 58
end_location:
row: 94
column: 61
fix: ~
- kind:
MissingTypeCls: cls
location:
row: 104
column: 12
end_location:
row: 104
column: 15
fix: ~

View File

@@ -0,0 +1,32 @@
---
source: src/flake8_annotations/mod.rs
expression: checks
---
- kind:
MissingReturnTypeMagicMethod: __init__
location:
row: 5
column: 4
end_location:
row: 10
column: 0
fix: ~
- kind:
MissingReturnTypeMagicMethod: __init__
location:
row: 11
column: 4
end_location:
row: 16
column: 0
fix: ~
- kind:
MissingReturnTypePrivateFunction: __init__
location:
row: 40
column: 0
end_location:
row: 42
column: 0
fix: ~

View File

@@ -0,0 +1,6 @@
---
source: src/flake8_annotations/mod.rs
expression: checks
---
[]

View File

@@ -0,0 +1,23 @@
---
source: src/flake8_annotations/mod.rs
expression: checks
---
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 45
column: 0
end_location:
row: 50
column: 0
fix: ~
- kind:
MissingReturnTypePublicFunction: foo
location:
row: 50
column: 0
end_location:
row: 56
column: 0
fix: ~

View File

@@ -0,0 +1,5 @@
use once_cell::sync::Lazy;
use regex::Regex;
pub static IDENTIFIER_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^[A-Za-z_][A-Za-z0-9_]*$").unwrap());

View File

@@ -1 +1,2 @@
mod constants;
pub mod plugins;

View File

@@ -1,6 +1,6 @@
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Stmt, StmtKind};
use crate::ast::types::{CheckLocator, Range};
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
@@ -42,10 +42,7 @@ pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: &Optio
..
} = &test.node
{
let mut check = Check::new(
CheckKind::DoNotAssertFalse,
checker.locate_check(Range::from_located(test)),
);
let mut check = Check::new(CheckKind::DoNotAssertFalse, Range::from_located(test));
if checker.patch() {
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_stmt(&assertion_error(msg)) {

View File

@@ -1,7 +1,7 @@
use rustpython_ast::{ExprKind, Stmt, Withitem};
use crate::ast::helpers::match_name_or_attr;
use crate::ast::types::{CheckLocator, Range};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
@@ -17,7 +17,7 @@ pub fn assert_raises_exception(checker: &mut Checker, stmt: &Stmt, items: &[With
{
checker.add_check(Check::new(
CheckKind::NoAssertRaisesException,
checker.locate_check(Range::from_located(stmt)),
Range::from_located(stmt),
));
}
}

View File

@@ -1,6 +1,6 @@
use rustpython_ast::{Expr, ExprKind};
use crate::ast::types::{CheckLocator, Range};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
@@ -14,7 +14,7 @@ pub fn assignment_to_os_environ(checker: &mut Checker, targets: &[Expr]) {
if id == "os" {
checker.add_check(Check::new(
CheckKind::AssignmentToOsEnviron,
checker.locate_check(Range::from_located(target)),
Range::from_located(target),
));
}
}

View File

@@ -1,6 +1,6 @@
use rustpython_ast::{Expr, ExprKind};
use crate::ast::types::{CheckLocator, Range};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
@@ -9,7 +9,7 @@ pub fn cannot_raise_literal(checker: &mut Checker, expr: &Expr) {
if let ExprKind::Constant { .. } = &expr.node {
checker.add_check(Check::new(
CheckKind::CannotRaiseLiteral,
checker.locate_check(Range::from_located(expr)),
Range::from_located(expr),
));
}
}

View File

@@ -4,7 +4,7 @@ use itertools::Itertools;
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Stmt};
use crate::ast::helpers;
use crate::ast::types::{CheckLocator, Range};
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
@@ -47,7 +47,7 @@ pub fn duplicate_handler_exceptions(
CheckKind::DuplicateHandlerException(
duplicates.into_iter().sorted().collect::<Vec<String>>(),
),
checker.locate_check(Range::from_located(expr)),
Range::from_located(expr),
);
if checker.patch() {
// TODO(charlie): If we have a single element, remove the tuple.
@@ -106,7 +106,7 @@ pub fn duplicate_exceptions(checker: &mut Checker, stmt: &Stmt, handlers: &[Exce
for duplicate in duplicates.into_iter().sorted() {
checker.add_check(Check::new(
CheckKind::DuplicateTryBlockException(duplicate),
checker.locate_check(Range::from_located(stmt)),
Range::from_located(stmt),
));
}
}

View File

@@ -1,7 +1,7 @@
use rustpython_ast::{Arguments, Constant, Expr, ExprKind};
use crate::ast::helpers::compose_call_path;
use crate::ast::types::{CheckLocator, Range};
use crate::ast::types::Range;
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::check_ast::Checker;
@@ -91,6 +91,6 @@ pub fn function_call_argument_default(checker: &mut Checker, arguments: &Argumen
visitor.visit_expr(expr);
}
for (check, range) in visitor.checks {
checker.add_check(Check::new(check, checker.locate_check(range)));
checker.add_check(Check::new(check, range));
}
}

View File

@@ -0,0 +1,53 @@
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind};
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
use crate::code_gen::SourceGenerator;
use crate::flake8_bugbear::constants::IDENTIFIER_REGEX;
use crate::python::keyword::KWLIST;
fn attribute(value: &Expr, attr: &str) -> Expr {
Expr::new(
Default::default(),
Default::default(),
ExprKind::Attribute {
value: Box::new(value.clone()),
attr: attr.to_string(),
ctx: ExprContext::Load,
},
)
}
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() {
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(&attribute(obj, value), 0) {
if let Ok(content) = generator.generate() {
check.amend(Fix::replacement(
content,
expr.location,
expr.end_location.unwrap(),
));
}
}
}
checker.add_check(check);
}
}
}
}
}
}

View File

@@ -4,9 +4,14 @@ pub use assignment_to_os_environ::assignment_to_os_environ;
pub use cannot_raise_literal::cannot_raise_literal;
pub use duplicate_exceptions::{duplicate_exceptions, duplicate_handler_exceptions};
pub use function_call_argument_default::function_call_argument_default;
pub use getattr_with_constant::getattr_with_constant;
pub use mutable_argument_default::mutable_argument_default;
pub use redundant_tuple_in_exception_handler::redundant_tuple_in_exception_handler;
pub use setattr_with_constant::setattr_with_constant;
pub use star_arg_unpacking_after_keyword_arg::star_arg_unpacking_after_keyword_arg;
pub use strip_with_multi_characters::strip_with_multi_characters;
pub use unary_prefix_increment::unary_prefix_increment;
pub use unreliable_callable_check::unreliable_callable_check;
pub use unused_loop_control_variable::unused_loop_control_variable;
pub use useless_comparison::useless_comparison;
pub use useless_expression::useless_expression;
@@ -17,9 +22,14 @@ mod assignment_to_os_environ;
mod cannot_raise_literal;
mod duplicate_exceptions;
mod function_call_argument_default;
mod getattr_with_constant;
mod mutable_argument_default;
mod redundant_tuple_in_exception_handler;
mod setattr_with_constant;
mod star_arg_unpacking_after_keyword_arg;
mod strip_with_multi_characters;
mod unary_prefix_increment;
mod unreliable_callable_check;
mod unused_loop_control_variable;
mod useless_comparison;
mod useless_expression;

View File

@@ -1,6 +1,6 @@
use rustpython_ast::{Arguments, Expr, ExprKind};
use crate::ast::types::{CheckLocator, Range};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
@@ -45,14 +45,14 @@ pub fn mutable_argument_default(checker: &mut Checker, arguments: &Arguments) {
| ExprKind::SetComp { .. } => {
checker.add_check(Check::new(
CheckKind::MutableArgumentDefault,
checker.locate_check(Range::from_located(expr)),
Range::from_located(expr),
));
}
ExprKind::Call { func, .. } => {
if is_mutable_func(func) {
checker.add_check(Check::new(
CheckKind::MutableArgumentDefault,
checker.locate_check(Range::from_located(expr)),
Range::from_located(expr),
));
}
}

View File

@@ -1,6 +1,6 @@
use rustpython_ast::{Excepthandler, ExcepthandlerKind, ExprKind};
use crate::ast::types::{CheckLocator, Range};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
@@ -13,7 +13,7 @@ pub fn redundant_tuple_in_exception_handler(checker: &mut Checker, handlers: &[E
if elts.len() == 1 {
checker.add_check(Check::new(
CheckKind::RedundantTupleInExceptionHandler(elts[0].to_string()),
checker.locate_check(Range::from_located(type_)),
Range::from_located(type_),
));
}
}

View File

@@ -0,0 +1,29 @@
use rustpython_ast::{Constant, Expr, ExprKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
use crate::flake8_bugbear::constants::IDENTIFIER_REGEX;
use crate::python::keyword::KWLIST;
/// 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 [_, arg, _] = args {
if let ExprKind::Constant {
value: Constant::Str(value),
..
} = &arg.node
{
if IDENTIFIER_REGEX.is_match(value) && !KWLIST.contains(&value.as_str()) {
checker.add_check(Check::new(
CheckKind::SetAttrWithConstant,
Range::from_located(expr),
));
}
}
}
}
}
}

View File

@@ -0,0 +1,25 @@
use rustpython_ast::{Expr, ExprKind, Keyword};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
/// B026
pub fn star_arg_unpacking_after_keyword_arg(
checker: &mut Checker,
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),
));
}
}
}
}
}

View File

@@ -0,0 +1,28 @@
use itertools::Itertools;
use rustpython_ast::{Constant, Expr, ExprKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
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),
));
}
}
}
}
}
}

View File

@@ -1,6 +1,6 @@
use rustpython_ast::{Expr, ExprKind, Unaryop};
use crate::ast::types::{CheckLocator, Range};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
@@ -11,7 +11,7 @@ pub fn unary_prefix_increment(checker: &mut Checker, expr: &Expr, op: &Unaryop,
if matches!(op, Unaryop::UAdd) {
checker.add_check(Check::new(
CheckKind::UnaryPrefixIncrement,
checker.locate_check(Range::from_located(expr)),
Range::from_located(expr),
))
}
}

View File

@@ -0,0 +1,27 @@
use rustpython_ast::{Constant, Expr, ExprKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
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),
));
}
}
}
}
}
}

View File

@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use rustpython_ast::{Expr, ExprKind, Stmt};
use crate::ast::types::{CheckLocator, Range};
use crate::ast::types::Range;
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::autofix::Fix;
@@ -64,7 +64,7 @@ pub fn unused_loop_control_variable(checker: &mut Checker, target: &Expr, body:
let mut check = Check::new(
CheckKind::UnusedLoopControlVariable(name.to_string()),
checker.locate_check(Range::from_located(expr)),
Range::from_located(expr),
);
if checker.patch() {
// Prefix the variable name with an underscore.

View File

@@ -1,14 +1,14 @@
use rustpython_ast::{Expr, ExprKind};
use crate::ast::types::{CheckLocator, Range};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
pub fn useless_comparison(checker: &mut Checker, expr: &Expr) {
if let ExprKind::Compare { left, .. } = &expr.node {
if matches!(expr.node, ExprKind::Compare { .. }) {
checker.add_check(Check::new(
CheckKind::UselessComparison,
checker.locate_check(Range::from_located(left)),
Range::from_located(expr),
));
}
}

View File

@@ -1,6 +1,6 @@
use rustpython_ast::{Constant, ExprKind, Stmt, StmtKind};
use crate::ast::types::{CheckLocator, Range};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
@@ -12,7 +12,7 @@ pub fn useless_expression(checker: &mut Checker, body: &[Stmt]) {
ExprKind::List { .. } | ExprKind::Dict { .. } | ExprKind::Set { .. } => {
checker.add_check(Check::new(
CheckKind::UselessExpression,
checker.locate_check(Range::from_located(value)),
Range::from_located(value),
));
}
ExprKind::Constant { value: val, .. } => match &val {
@@ -20,7 +20,7 @@ pub fn useless_expression(checker: &mut Checker, body: &[Stmt]) {
_ => {
checker.add_check(Check::new(
CheckKind::UselessExpression,
checker.locate_check(Range::from_located(value)),
Range::from_located(value),
));
}
},

View File

@@ -149,19 +149,26 @@ pub fn unnecessary_list_comprehension_set(
/// C404 (`dict([...])`)
pub fn unnecessary_list_comprehension_dict(
expr: &Expr,
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
locator: &SourceCodeLocator,
fix: bool,
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 => {
return Some(Check::new(
CheckKind::UnnecessaryListComprehensionDict,
location,
));
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);
}
_ => {}
}
@@ -348,8 +355,11 @@ pub fn unnecessary_list_call(
/// C413
pub fn unnecessary_call_around_sorted(
expr: &Expr,
func: &Expr,
args: &[Expr],
locator: &SourceCodeLocator,
fix: bool,
location: Range,
) -> Option<Check> {
let outer = function_name(func)?;
@@ -358,10 +368,17 @@ pub fn unnecessary_call_around_sorted(
}
if let ExprKind::Call { func, .. } = &args.first()?.node {
if function_name(func)? == "sorted" {
return Some(Check::new(
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);
}
}
None

View File

@@ -1,8 +1,9 @@
use anyhow::Result;
use libcst_native::{
Arg, Call, Codegen, Dict, DictComp, DictElement, Element, Expr, Expression, LeftCurlyBrace,
LeftParen, LeftSquareBracket, List, ListComp, Name, ParenthesizableWhitespace, RightCurlyBrace,
RightParen, RightSquareBracket, Set, SetComp, SimpleString, SimpleWhitespace, Tuple,
Arg, AssignEqual, Call, Codegen, Dict, DictComp, DictElement, Element, Expr, Expression,
LeftCurlyBrace, LeftParen, LeftSquareBracket, List, ListComp, Name, ParenthesizableWhitespace,
RightCurlyBrace, RightParen, RightSquareBracket, Set, SetComp, SimpleString, SimpleWhitespace,
Tuple,
};
use crate::ast::types::Range;
@@ -220,6 +221,64 @@ pub fn fix_unnecessary_list_comprehension_set(
))
}
/// (C404) Convert `dict([(i, i) for i in range(3)])` to `{i: i for i in
/// range(3)}`.
pub fn fix_unnecessary_list_comprehension_dict(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let mut tree = match_module(&module_text)?;
let mut body = match_expr(&mut tree)?;
let call = match_call(body)?;
let arg = match_arg(call)?;
let list_comp = if let Expression::ListComp(list_comp) = &arg.value {
list_comp
} else {
return Err(anyhow::anyhow!("Expected node to be: Expression::ListComp"));
};
let tuple = if let Expression::Tuple(tuple) = &*list_comp.elt {
tuple
} else {
return Err(anyhow::anyhow!("Expected node to be: Expression::Tuple"));
};
let (key, comma, value) = match &tuple.elements[..] {
[Element::Simple {
value: key,
comma: Some(comma),
}, Element::Simple { value, .. }] => (key, comma, value),
_ => return Err(anyhow::anyhow!("Expected tuple with two elements")),
};
body.value = Expression::DictComp(Box::new(DictComp {
key: Box::new(key.clone()),
value: Box::new(value.clone()),
for_in: list_comp.for_in.clone(),
whitespace_before_colon: comma.whitespace_before.clone(),
whitespace_after_colon: comma.whitespace_after.clone(),
lbrace: LeftCurlyBrace {
whitespace_after: call.whitespace_before_args.clone(),
},
rbrace: RightCurlyBrace {
whitespace_before: arg.whitespace_after_arg.clone(),
},
lpar: list_comp.lpar.clone(),
rpar: list_comp.rpar.clone(),
}));
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C405) Convert `set((1, 2))` to `{1, 2}`.
pub fn fix_unnecessary_literal_set(
locator: &SourceCodeLocator,
@@ -591,6 +650,92 @@ pub fn fix_unnecessary_list_call(
))
}
/// (C413) Convert `list(sorted([2, 3, 1]))` to `sorted([2, 3, 1])`.
/// (C413) Convert `reversed(sorted([2, 3, 1]))` to `sorted([2, 3, 1],
/// reverse=True)`.
pub fn fix_unnecessary_call_around_sorted(
locator: &SourceCodeLocator,
expr: &rustpython_ast::Expr,
) -> Result<Fix> {
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let mut tree = match_module(&module_text)?;
let mut body = match_expr(&mut tree)?;
let outer_call = match_call(body)?;
let inner_call = match &outer_call.args[..] {
[arg] => {
if let Expression::Call(call) = &arg.value {
call
} else {
return Err(anyhow::anyhow!("Expected node to be: Expression::Call "));
}
}
_ => {
return Err(anyhow::anyhow!(
"Expected one argument in outer function call"
))
}
};
if let Expression::Name(outer_name) = &*outer_call.func {
if outer_name.value == "list" {
body.value = Expression::Call(inner_call.clone());
} else {
let args = if inner_call.args.iter().any(|arg| {
matches!(
arg.keyword,
Some(Name {
value: "reverse",
..
})
)
}) {
inner_call.args.clone()
} else {
let mut args = inner_call.args.clone();
args.push(Arg {
value: Expression::Name(Box::new(Name {
value: "True",
lpar: Default::default(),
rpar: Default::default(),
})),
keyword: Some(Name {
value: "reverse",
lpar: Default::default(),
rpar: Default::default(),
}),
equal: Some(AssignEqual {
whitespace_before: Default::default(),
whitespace_after: Default::default(),
}),
comma: Default::default(),
star: Default::default(),
whitespace_after_star: Default::default(),
whitespace_after_arg: Default::default(),
});
args
};
body.value = Expression::Call(Box::new(Call {
func: inner_call.func.clone(),
args,
lpar: inner_call.lpar.clone(),
rpar: inner_call.rpar.clone(),
whitespace_after_func: inner_call.whitespace_after_func.clone(),
whitespace_before_args: inner_call.whitespace_before_args.clone(),
}))
}
}
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
expr.location,
expr.end_location.unwrap(),
))
}
/// (C416) Convert `[i for i in x]` to `list(x)`.
pub fn fix_unnecessary_comprehension(
locator: &SourceCodeLocator,

View File

@@ -1,7 +1,7 @@
use log::error;
use rustpython_ast::{Expr, Stmt, StmtKind};
use crate::ast::types::{CheckLocator, Range};
use crate::ast::types::Range;
use crate::autofix::helpers;
use crate::check_ast::Checker;
use crate::checks::CheckCode;
@@ -13,7 +13,7 @@ pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
func,
checker.settings.enabled.contains(&CheckCode::T201),
checker.settings.enabled.contains(&CheckCode::T203),
checker.locate_check(Range::from_located(expr)),
Range::from_located(expr),
) {
if checker.patch() {
let context = checker.binding_context();

View File

@@ -135,172 +135,3 @@ pub fn quotes(
None
}
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use rustpython_parser::lexer::LexResult;
use test_case::test_case;
use crate::autofix::fixer;
use crate::checks::{Check, CheckCode};
use crate::flake8_quotes::settings::Quote;
use crate::linter::tokenize;
use crate::{flake8_quotes, fs, linter, noqa, Settings, SourceCodeLocator};
fn check_path(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> Result<Vec<Check>> {
let contents = fs::read_file(path)?;
let tokens: Vec<LexResult> = tokenize(&contents);
let locator = SourceCodeLocator::new(&contents);
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
linter::check_path(
path,
&contents,
tokens,
&locator,
&noqa_line_for,
settings,
autofix,
)
}
#[test_case(Path::new("doubles.py"))]
#[test_case(Path::new("doubles_escaped.py"))]
#[test_case(Path::new("doubles_multiline_string.py"))]
#[test_case(Path::new("doubles_noqa.py"))]
#[test_case(Path::new("doubles_wrapped.py"))]
fn doubles(path: &Path) -> Result<()> {
let snapshot = format!("doubles_{}", path.to_string_lossy());
let mut checks = check_path(
Path::new("./resources/test/fixtures/flake8_quotes")
.join(path)
.as_path(),
&Settings {
flake8_quotes: flake8_quotes::settings::Settings {
inline_quotes: Quote::Single,
multiline_quotes: Quote::Single,
docstring_quotes: Quote::Single,
avoid_escape: true,
},
..Settings::for_rules(vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
#[test_case(Path::new("singles.py"))]
#[test_case(Path::new("singles_escaped.py"))]
#[test_case(Path::new("singles_multiline_string.py"))]
#[test_case(Path::new("singles_noqa.py"))]
#[test_case(Path::new("singles_wrapped.py"))]
fn singles(path: &Path) -> Result<()> {
let snapshot = format!("singles_{}", path.to_string_lossy());
let mut checks = check_path(
Path::new("./resources/test/fixtures/flake8_quotes")
.join(path)
.as_path(),
&Settings {
flake8_quotes: flake8_quotes::settings::Settings {
inline_quotes: Quote::Double,
multiline_quotes: Quote::Double,
docstring_quotes: Quote::Double,
avoid_escape: true,
},
..Settings::for_rules(vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
#[test_case(Path::new("docstring_doubles.py"))]
#[test_case(Path::new("docstring_doubles_module_multiline.py"))]
#[test_case(Path::new("docstring_doubles_module_singleline.py"))]
#[test_case(Path::new("docstring_doubles_class.py"))]
#[test_case(Path::new("docstring_doubles_function.py"))]
#[test_case(Path::new("docstring_singles.py"))]
#[test_case(Path::new("docstring_singles_module_multiline.py"))]
#[test_case(Path::new("docstring_singles_module_singleline.py"))]
#[test_case(Path::new("docstring_singles_class.py"))]
#[test_case(Path::new("docstring_singles_function.py"))]
fn double_docstring(path: &Path) -> Result<()> {
let snapshot = format!("double_docstring_{}", path.to_string_lossy());
let mut checks = check_path(
Path::new("./resources/test/fixtures/flake8_quotes")
.join(path)
.as_path(),
&Settings {
flake8_quotes: flake8_quotes::settings::Settings {
inline_quotes: Quote::Single,
multiline_quotes: Quote::Single,
docstring_quotes: Quote::Double,
avoid_escape: true,
},
..Settings::for_rules(vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
#[test_case(Path::new("docstring_doubles.py"))]
#[test_case(Path::new("docstring_doubles_module_multiline.py"))]
#[test_case(Path::new("docstring_doubles_module_singleline.py"))]
#[test_case(Path::new("docstring_doubles_class.py"))]
#[test_case(Path::new("docstring_doubles_function.py"))]
#[test_case(Path::new("docstring_singles.py"))]
#[test_case(Path::new("docstring_singles_module_multiline.py"))]
#[test_case(Path::new("docstring_singles_module_singleline.py"))]
#[test_case(Path::new("docstring_singles_class.py"))]
#[test_case(Path::new("docstring_singles_function.py"))]
fn single_docstring(path: &Path) -> Result<()> {
let snapshot = format!("single_docstring_{}", path.to_string_lossy());
let mut checks = check_path(
Path::new("./resources/test/fixtures/flake8_quotes")
.join(path)
.as_path(),
&Settings {
flake8_quotes: flake8_quotes::settings::Settings {
inline_quotes: Quote::Single,
multiline_quotes: Quote::Double,
docstring_quotes: Quote::Single,
avoid_escape: true,
},
..Settings::for_rules(vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
}

View File

@@ -1,2 +1,154 @@
pub mod checks;
pub mod settings;
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use test_case::test_case;
use crate::autofix::fixer;
use crate::checks::CheckCode;
use crate::flake8_quotes::settings::Quote;
use crate::linter::test_path;
use crate::{flake8_quotes, Settings};
#[test_case(Path::new("doubles.py"))]
#[test_case(Path::new("doubles_escaped.py"))]
#[test_case(Path::new("doubles_multiline_string.py"))]
#[test_case(Path::new("doubles_noqa.py"))]
#[test_case(Path::new("doubles_wrapped.py"))]
fn doubles(path: &Path) -> Result<()> {
let snapshot = format!("doubles_{}", path.to_string_lossy());
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_quotes")
.join(path)
.as_path(),
&Settings {
flake8_quotes: flake8_quotes::settings::Settings {
inline_quotes: Quote::Single,
multiline_quotes: Quote::Single,
docstring_quotes: Quote::Single,
avoid_escape: true,
},
..Settings::for_rules(vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
#[test_case(Path::new("singles.py"))]
#[test_case(Path::new("singles_escaped.py"))]
#[test_case(Path::new("singles_multiline_string.py"))]
#[test_case(Path::new("singles_noqa.py"))]
#[test_case(Path::new("singles_wrapped.py"))]
fn singles(path: &Path) -> Result<()> {
let snapshot = format!("singles_{}", path.to_string_lossy());
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_quotes")
.join(path)
.as_path(),
&Settings {
flake8_quotes: flake8_quotes::settings::Settings {
inline_quotes: Quote::Double,
multiline_quotes: Quote::Double,
docstring_quotes: Quote::Double,
avoid_escape: true,
},
..Settings::for_rules(vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
#[test_case(Path::new("docstring_doubles.py"))]
#[test_case(Path::new("docstring_doubles_module_multiline.py"))]
#[test_case(Path::new("docstring_doubles_module_singleline.py"))]
#[test_case(Path::new("docstring_doubles_class.py"))]
#[test_case(Path::new("docstring_doubles_function.py"))]
#[test_case(Path::new("docstring_singles.py"))]
#[test_case(Path::new("docstring_singles_module_multiline.py"))]
#[test_case(Path::new("docstring_singles_module_singleline.py"))]
#[test_case(Path::new("docstring_singles_class.py"))]
#[test_case(Path::new("docstring_singles_function.py"))]
fn double_docstring(path: &Path) -> Result<()> {
let snapshot = format!("double_docstring_{}", path.to_string_lossy());
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_quotes")
.join(path)
.as_path(),
&Settings {
flake8_quotes: flake8_quotes::settings::Settings {
inline_quotes: Quote::Single,
multiline_quotes: Quote::Single,
docstring_quotes: Quote::Double,
avoid_escape: true,
},
..Settings::for_rules(vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
#[test_case(Path::new("docstring_doubles.py"))]
#[test_case(Path::new("docstring_doubles_module_multiline.py"))]
#[test_case(Path::new("docstring_doubles_module_singleline.py"))]
#[test_case(Path::new("docstring_doubles_class.py"))]
#[test_case(Path::new("docstring_doubles_function.py"))]
#[test_case(Path::new("docstring_singles.py"))]
#[test_case(Path::new("docstring_singles_module_multiline.py"))]
#[test_case(Path::new("docstring_singles_module_singleline.py"))]
#[test_case(Path::new("docstring_singles_class.py"))]
#[test_case(Path::new("docstring_singles_function.py"))]
fn single_docstring(path: &Path) -> Result<()> {
let snapshot = format!("single_docstring_{}", path.to_string_lossy());
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_quotes")
.join(path)
.as_path(),
&Settings {
flake8_quotes: flake8_quotes::settings::Settings {
inline_quotes: Quote::Single,
multiline_quotes: Quote::Double,
docstring_quotes: Quote::Single,
avoid_escape: true,
},
..Settings::for_rules(vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
])
},
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(snapshot, checks);
Ok(())
}
}

View File

@@ -0,0 +1,50 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesMultilineString: single
location:
row: 5
column: 0
end_location:
row: 7
column: 3
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 16
column: 4
end_location:
row: 18
column: 7
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 21
column: 20
end_location:
row: 22
column: 37
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 30
column: 8
end_location:
row: 32
column: 11
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 35
column: 12
end_location:
row: 37
column: 15
fix: ~

View File

@@ -0,0 +1,23 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesMultilineString: single
location:
row: 3
column: 4
end_location:
row: 3
column: 27
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 5
column: 22
end_location:
row: 5
column: 43
fix: ~

View File

@@ -0,0 +1,50 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesMultilineString: single
location:
row: 3
column: 4
end_location:
row: 3
column: 26
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 11
column: 4
end_location:
row: 11
column: 26
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 15
column: 38
end_location:
row: 17
column: 3
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 17
column: 4
end_location:
row: 17
column: 19
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 21
column: 4
end_location:
row: 21
column: 27
fix: ~

View File

@@ -0,0 +1,23 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesMultilineString: single
location:
row: 4
column: 0
end_location:
row: 6
column: 3
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 9
column: 0
end_location:
row: 11
column: 3
fix: ~

View File

@@ -0,0 +1,23 @@
---
source: src/flake8_quotes/mod.rs
expression: checks
---
- kind:
BadQuotesMultilineString: single
location:
row: 2
column: 0
end_location:
row: 2
column: 31
fix: ~
- kind:
BadQuotesMultilineString: single
location:
row: 6
column: 0
end_location:
row: 6
column: 31
fix: ~

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