Compare commits

...

62 Commits

Author SHA1 Message Date
Charlie Marsh
e1abe37c6a Bump version to 0.0.62 2022-10-08 20:28:38 -04:00
Charlie Marsh
7fe5945541 Implement PEP 604 annotation rewrites (#369) 2022-10-08 20:28:00 -04:00
Charlie Marsh
806f3fd4f6 Implement PEP 585 annotation rewrites (#368) 2022-10-08 18:22:24 -04:00
Charlie Marsh
2bba643dd2 Use strum to facilitate simple enum serialization (#367) 2022-10-08 17:47:25 -04:00
Charlie Marsh
54090bd7ac Use strum to iterate over all check codes (#366) 2022-10-08 17:41:47 -04:00
Charlie Marsh
c62727db42 Bump version to 0.0.61 2022-10-08 17:25:36 -04:00
Charlie Marsh
d0e1612507 Check newline ending on contents directly (#365) 2022-10-08 17:25:22 -04:00
Harutaka Kawamura
5ccd907398 Implement C408 (#364) 2022-10-08 17:17:34 -04:00
Harutaka Kawamura
346610c2e3 Implement C406 (#363) 2022-10-08 11:15:41 -04:00
Harutaka Kawamura
307fa26515 Implement C405 (#362) 2022-10-08 09:03:21 -04:00
Harutaka Kawamura
136d412edd Add missing C400,C401, and C402 to CheckCode.from_str (#361) 2022-10-08 09:02:03 -04:00
Harutaka Kawamura
d9edec0ac9 Implement C402 (#359) 2022-10-07 22:57:04 -04:00
Chris Pryer
473675fffb Add check for W292 (#339) 2022-10-07 21:10:16 -04:00
Steven Maude
95dfc61315 Update GitHub Actions versions in README (#358) 2022-10-07 20:24:00 -04:00
Charlie Marsh
dd496c7b52 Bump version to 0.0.60 2022-10-07 17:36:33 -04:00
Charlie Marsh
78aafb4b34 Warn the user if an explicitly selected check code is ignored (#356) 2022-10-07 17:36:17 -04:00
Charlie Marsh
99c66d513a Rename refactor checks to upgrade checks (#354) 2022-10-07 16:54:55 -04:00
Charlie Marsh
04ade6a2f3 Update README.md 2022-10-07 16:50:17 -04:00
Charlie Marsh
4cf2682cda Implement type(primitive) (#353) 2022-10-07 16:47:06 -04:00
Charlie Marsh
e3a7357187 Wrap each import in its own backticks (#346) 2022-10-07 15:58:30 -04:00
Charlie Marsh
b60768debb Remove erroneous output.py file 2022-10-07 15:04:23 -04:00
Charlie Marsh
25e476639f Use or_default in lieu of or_insert 2022-10-07 14:56:56 -04:00
Charlie Marsh
4645788205 Bump version to 0.0.59 2022-10-07 14:55:23 -04:00
Charlie Marsh
0b9eda8836 Add target Python version as a configurable setting (#344) 2022-10-07 14:54:50 -04:00
Charlie Marsh
ad23d6acee Remove :: prefix for ruff imports 2022-10-07 14:24:51 -04:00
Harutaka Kawamura
e3d1d01a1f Implement C401 (#343) 2022-10-07 13:00:22 -04:00
Harutaka Kawamura
6dfdd21a7c Implement C400 (#340) 2022-10-07 12:16:23 -04:00
Charlie Marsh
f17d3b3c44 Bump version to 0.0.58 2022-10-07 12:14:03 -04:00
Charlie Marsh
da6b913317 Exit 0 if all errors are fixed (#342) 2022-10-07 12:13:15 -04:00
Harutaka Kawamura
bd34850f98 Implement C404 (#338) 2022-10-07 08:53:18 -04:00
Charlie Marsh
d5b33cdb40 Add missing snapshot for U002 2022-10-07 08:48:59 -04:00
Charlie Marsh
3fb4cf7009 Hide autoformat argument 2022-10-06 22:56:01 -04:00
Charlie Marsh
6e19539e28 Enable abspath(__file__) removal (#336) 2022-10-06 22:49:06 -04:00
Charlie Marsh
4eac7a07f5 Mention flake8-comprehensions in README 2022-10-06 16:26:21 -04:00
Harutaka Kawamura
5141285c8e Implement C403 (#335) 2022-10-06 16:24:23 -04:00
Charlie Marsh
82cc139d2d Bump version to 0.0.57 2022-10-06 09:16:56 -04:00
Adrian Garcia Badaracco
df438ba051 Support PEP 593 annotations (#333) 2022-10-06 09:16:07 -04:00
Adrian Garcia Badaracco
a70624cd47 add instructions for setting up cargo insta (#334) 2022-10-06 08:01:11 -04:00
Charlie Marsh
b307afc00c Move some accesses behind a shared function 2022-10-05 14:54:31 -04:00
Charlie Marsh
3d5bc1f51f Migrate Checker logic to independent plugins (#331) 2022-10-05 14:08:40 -04:00
Charlie Marsh
aba01745f5 Bump version to 0.0.56 2022-10-05 11:58:54 -04:00
Charlie Marsh
1eeeffab66 Add T201 and T203 to string conversion match (#332) 2022-10-05 11:58:33 -04:00
Charlie Marsh
9b564c9cf4 Bump version to 0.0.55 2022-10-04 20:07:31 -04:00
Charlie Marsh
5bf8b13644 Properly combine CLI and pyproject.toml ignores and selects (#329) 2022-10-04 20:07:17 -04:00
Anders Kaseorg
f80d5e70dd Support extend-select in pyproject.toml (#327) 2022-10-04 17:24:30 -04:00
Charlie Marsh
44897b2a5b Enable AST-to-source code generation (#292) 2022-10-04 16:27:57 -04:00
Anders Kaseorg
d1bcc919a2 Remove unnecessary Option wrapper from some pyproject::Config fields (#326) 2022-10-04 16:27:40 -04:00
Charlie Marsh
03e1397427 Bump version to 0.0.54 2022-10-04 14:32:06 -04:00
Charlie Marsh
fdb32330a9 Implement __metaclass__ = type removal (#324) 2022-10-04 14:31:52 -04:00
Charlie Marsh
4e6ae33a3a Only flag super calls in class-function scopes (#323) 2022-10-04 13:55:32 -04:00
Charlie Marsh
295ff8eb1a Add autofix and default status to README (#322) 2022-10-04 12:30:35 -04:00
Parth Shandilya
2449771d2f Fix the broken link to contribution guidelines (#321) 2022-10-04 11:10:10 -04:00
Charlie Marsh
406491a3a2 Bump version to 0.0.53 2022-10-04 08:56:46 -04:00
Charlie Marsh
bfae262359 Simplify noqa extraction logic (#320) 2022-10-04 08:56:14 -04:00
Charlie Marsh
af894f290f Disable plugin-based rules by default (#318) 2022-10-04 08:28:46 -04:00
Charlie Marsh
c901742244 Add plugins mention to README (#309) 2022-10-03 17:23:53 -04:00
Charlie Marsh
7e4faf4b69 Implement flake8-print (#308) 2022-10-03 17:19:56 -04:00
Charlie Marsh
31a0b20271 Bump version to 0.0.52 2022-10-03 15:22:58 -04:00
Charlie Marsh
0966bf2c66 Handle multi-import lines (#307) 2022-10-03 15:22:46 -04:00
Charlie Marsh
64d8e25528 Bump version to 0.0.51 2022-10-03 14:08:39 -04:00
Charlie Marsh
b049cced04 Automatically remove unused imports (#298) 2022-10-03 14:08:16 -04:00
Charlie Marsh
bc335f839e Visit lambda arguments prior to deferral (#303) 2022-10-02 20:54:02 -04:00
90 changed files with 5159 additions and 1180 deletions

View File

@@ -14,6 +14,12 @@ your proposed change.
ruff is written in Rust (1.63.0). You'll need to install the
[Rust toolchain](https://www.rust-lang.org/tools/install) for development.
You'll also need [Insta](https://insta.rs/docs/) to update snapshot tests:
```shell
cargo install cargo-insta
```
### Development
After cloning the repository, run ruff locally with:
@@ -58,7 +64,7 @@ similar rules are implemented.
To add a test fixture, create a file under `resources/test/fixtures`, named to match the `CheckCode`
you defined earlier (e.g., `E402.py`). This file should contain a variety of violations and
non-violations designed to evaluate and demonstrate the behavior of your lint rule. Run ruff locally
with (e.g.) `cargo run resources/test/fixtures/E402.py`. Once you're satisified with the output,
with (e.g.) `cargo run resources/test/fixtures/E402.py`. Once you're satisfied with the output,
codify the behavior as a snapshot test by adding a new function to the `mod tests` section of
`src/linter.rs`, like so:

405
Cargo.lock generated
View File

@@ -21,18 +21,18 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "0.7.18"
version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
@@ -45,9 +45,15 @@ checksum = "c7021ce4924a3f25f802b2cccd1af585e39ea1a363a1aa2e72afe54b67a3a7a7"
[[package]]
name = "anyhow"
version = "1.0.60"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c794e162a5eff65c72ef524dfe393eb923c354e350bb78b9c7383df13f3bc142"
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
[[package]]
name = "ascii"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
[[package]]
name = "ascii-canvas"
@@ -60,9 +66,9 @@ dependencies = [
[[package]]
name = "async-channel"
version = "1.7.0"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b31b87a3367ed04dbcbc252bce3f2a8172fef861d47177524c503c908dff2c6"
checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28"
dependencies = [
"concurrent-queue",
"event-listener",
@@ -85,9 +91,9 @@ dependencies = [
[[package]]
name = "async-global-executor"
version = "2.2.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5262ed948da60dd8956c6c5aca4d4163593dddb7b32d73267c93dab7b2e98940"
checksum = "0da5b41ee986eed3f524c380e6d64965aea573882a8907682ad100f7859305ca"
dependencies = [
"async-channel",
"async-executor",
@@ -95,16 +101,16 @@ dependencies = [
"async-lock",
"blocking",
"futures-lite",
"num_cpus",
"once_cell",
]
[[package]]
name = "async-io"
version = "1.7.0"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07"
checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7"
dependencies = [
"autocfg",
"concurrent-queue",
"futures-lite",
"libc",
@@ -129,11 +135,12 @@ dependencies = [
[[package]]
name = "async-process"
version = "1.4.0"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c"
checksum = "02111fd8655a613c25069ea89fc8d9bb89331fa77486eb3bc059ee757cfa481c"
dependencies = [
"async-io",
"autocfg",
"blocking",
"cfg-if 1.0.0",
"event-listener",
@@ -302,9 +309,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.10.0"
version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
[[package]]
name = "byte-tools"
@@ -376,9 +383,9 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.21"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f725f340c3854e3cb3ab736dc21f0cca183303acea3b3ffec30f141503ac8eb"
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
dependencies = [
"iana-time-zone",
"js-sys",
@@ -397,9 +404,9 @@ checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
[[package]]
name = "clap"
version = "4.0.1"
version = "4.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd03107d0f87139c1774a15f3db2165b0652b5460c58c27e561f89c20c599eaf"
checksum = "30607dd93c420c6f1f80b544be522a0238a7db35e6a12968d28910983fee0df0"
dependencies = [
"atty",
"bitflags",
@@ -412,9 +419,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.0.1"
version = "4.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca689d7434ce44517a12a89456b2be4d1ea1cafcd8f581978c03d45f5a5c12a7"
checksum = "a4a307492e1a34939f79d3b6b9650bd2b971513cd775436bf2b78defeb5af00b"
dependencies = [
"heck",
"proc-macro-error",
@@ -473,27 +480,17 @@ dependencies = [
[[package]]
name = "console"
version = "0.15.1"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847"
checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c"
dependencies = [
"encode_unicode",
"lazy_static",
"libc",
"once_cell",
"terminal_size",
"winapi 0.3.9",
]
[[package]]
name = "core-foundation"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
@@ -502,9 +499,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "cpufeatures"
version = "0.2.2"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
dependencies = [
"libc",
]
@@ -541,26 +538,24 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
version = "0.9.10"
version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1"
checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"crossbeam-utils",
"memoffset",
"once_cell",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.11"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac"
dependencies = [
"cfg-if 1.0.0",
"once_cell",
]
[[package]]
@@ -665,9 +660,9 @@ dependencies = [
[[package]]
name = "either"
version = "1.7.0"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "ena"
@@ -750,11 +745,10 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.0.1"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
dependencies = [
"matches",
"percent-encoding",
]
@@ -795,9 +789,9 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
[[package]]
name = "futures"
version = "0.3.21"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c"
dependencies = [
"futures-channel",
"futures-core",
@@ -810,9 +804,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.21"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050"
dependencies = [
"futures-core",
"futures-sink",
@@ -820,15 +814,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.21"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
[[package]]
name = "futures-executor"
version = "0.3.21"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab"
dependencies = [
"futures-core",
"futures-task",
@@ -837,9 +831,9 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.21"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68"
[[package]]
name = "futures-lite"
@@ -858,9 +852,9 @@ dependencies = [
[[package]]
name = "futures-macro"
version = "0.3.21"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17"
dependencies = [
"proc-macro2",
"quote",
@@ -869,21 +863,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.21"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56"
[[package]]
name = "futures-task"
version = "0.3.21"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1"
[[package]]
name = "futures-util"
version = "0.3.21"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
dependencies = [
"futures-channel",
"futures-core",
@@ -990,13 +984,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "iana-time-zone"
version = "0.1.41"
name = "hexf-parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1779539f58004e5dba1c1f093d44325ebeb244bfc04b791acdc0aaeca9c04570"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]]
name = "iana-time-zone"
version = "0.1.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0"
dependencies = [
"android_system_properties",
"core-foundation",
"core-foundation-sys",
"js-sys",
"wasm-bindgen",
"winapi 0.3.9",
@@ -1004,11 +1004,10 @@ dependencies = [
[[package]]
name = "idna"
version = "0.2.3"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
dependencies = [
"matches",
"unicode-bidi",
"unicode-normalization",
]
@@ -1045,13 +1044,13 @@ dependencies = [
[[package]]
name = "insta"
version = "1.19.1"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc61e98be01e89296f3343a878e9f8ca75a494cb5aaf29df65ef55734aeb85f5"
checksum = "581d4e3314cae4536e5d22ffd23189d4a374696c5ef733eadafae0ed273fd303"
dependencies = [
"console",
"lazy_static",
"linked-hash-map",
"once_cell",
"serde",
"similar",
"yaml-rust",
@@ -1092,9 +1091,9 @@ checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
[[package]]
name = "js-sys"
version = "0.3.59"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2"
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
dependencies = [
"wasm-bindgen",
]
@@ -1163,10 +1162,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.127"
name = "lexical-parse-float"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b"
checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f"
dependencies = [
"lexical-parse-integer",
"lexical-util",
"static_assertions",
]
[[package]]
name = "lexical-parse-integer"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9"
dependencies = [
"lexical-util",
"static_assertions",
]
[[package]]
name = "lexical-util"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc"
dependencies = [
"static_assertions",
]
[[package]]
name = "libc"
version = "0.2.134"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb"
[[package]]
name = "libcst"
@@ -1200,9 +1229,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "lock_api"
version = "0.4.7"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
@@ -1220,9 +1249,9 @@ dependencies = [
[[package]]
name = "lz4_flex"
version = "0.9.4"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c038063f7a78126c539d666a0323a2032de5e7366012cd14a6eafc5ba290bbd6"
checksum = "1a8cbbb2831780bc3b9c15a41f5b49222ef756b6730a95f3decfdd15903eb5a3"
dependencies = [
"twox-hash",
]
@@ -1241,9 +1270,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memmap2"
version = "0.5.5"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7"
checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498"
dependencies = [
"libc",
]
@@ -1259,9 +1288,9 @@ dependencies = [
[[package]]
name = "miniz_oxide"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
dependencies = [
"adler",
]
@@ -1436,9 +1465,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "os_str_bytes"
version = "6.2.0"
version = "6.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4"
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
[[package]]
name = "parking"
@@ -1522,9 +1551,9 @@ checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739"
[[package]]
name = "percent-encoding"
version = "2.1.0"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "petgraph"
@@ -1632,10 +1661,11 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "polling"
version = "2.2.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259"
checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"libc",
"log",
@@ -1681,9 +1711,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.43"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b"
dependencies = [
"unicode-ident",
]
@@ -1697,6 +1727,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.7.3"
@@ -1719,7 +1755,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.3",
"rand_core 0.6.4",
]
[[package]]
@@ -1739,7 +1775,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.3",
"rand_core 0.6.4",
]
[[package]]
@@ -1753,9 +1789,9 @@ dependencies = [
[[package]]
name = "rand_core"
version = "0.6.3"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.7",
]
@@ -1871,7 +1907,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.50"
version = "0.0.62"
dependencies = [
"anyhow",
"bincode",
@@ -1894,9 +1930,13 @@ dependencies = [
"path-absolutize",
"rayon",
"regex",
"rustpython-ast",
"rustpython-common",
"rustpython-parser",
"serde",
"serde_json",
"strum",
"strum_macros",
"toml",
"update-informer",
"walkdir",
@@ -1920,9 +1960,33 @@ version = "0.1.0"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=4f457893efc381ad5c432576b24bcc7e4a08c641#4f457893efc381ad5c432576b24bcc7e4a08c641"
dependencies = [
"num-bigint",
"rustpython-common",
"rustpython-compiler-core",
]
[[package]]
name = "rustpython-common"
version = "0.0.0"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=4f457893efc381ad5c432576b24bcc7e4a08c641#4f457893efc381ad5c432576b24bcc7e4a08c641"
dependencies = [
"ascii",
"cfg-if 1.0.0",
"hexf-parse",
"lexical-parse-float",
"libc",
"lock_api",
"num-bigint",
"num-complex",
"num-traits",
"once_cell",
"radium",
"rand 0.8.5",
"siphasher",
"unic-ucd-category",
"volatile",
"widestring",
]
[[package]]
name = "rustpython-compiler-core"
version = "0.1.2"
@@ -2003,24 +2067,24 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.13"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711"
checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
[[package]]
name = "serde"
version = "1.0.143"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.143"
version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391"
checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
dependencies = [
"proc-macro2",
"quote",
@@ -2029,9 +2093,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.83"
version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
dependencies = [
"itoa",
"ryu",
@@ -2130,15 +2194,15 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.9.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "socket2"
version = "0.4.4"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
dependencies = [
"libc",
"winapi 0.3.9",
@@ -2191,10 +2255,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.99"
name = "strum"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "syn"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2"
dependencies = [
"proc-macro2",
"quote",
@@ -2370,6 +2456,18 @@ dependencies = [
"unic-ucd-version",
]
[[package]]
name = "unic-ucd-category"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8d4591f5fcfe1bd4453baaf803c40e1b1e69ff8455c47620440b46efef91c0"
dependencies = [
"matches",
"unic-char-property",
"unic-char-range",
"unic-ucd-version",
]
[[package]]
name = "unic-ucd-ident"
version = "0.9.0"
@@ -2398,30 +2496,30 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
[[package]]
name = "unicode-ident"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
[[package]]
name = "unicode-normalization"
version = "0.1.21"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-xid"
version = "0.2.3"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "unicode_names2"
version = "0.5.0"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eec8e807a365e5c972debc47b8f06d361b37b94cfd18d48f7adc715fb86404dd"
checksum = "029df4cc8238cefc911704ff8fa210853a0f3bce2694d8f51181dd41ee0f3301"
[[package]]
name = "untrusted"
@@ -2463,13 +2561,12 @@ dependencies = [
[[package]]
name = "url"
version = "2.2.2"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
dependencies = [
"form_urlencoded",
"idna",
"matches",
"percent-encoding",
]
@@ -2489,6 +2586,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "volatile"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8e76fae08f03f96e166d2dfda232190638c10e0383841252416f9cfe2ae60e6"
[[package]]
name = "waker-fn"
version = "1.1.0"
@@ -2526,9 +2629,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.82"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d"
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen-macro",
@@ -2536,9 +2639,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.82"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f"
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
dependencies = [
"bumpalo",
"log",
@@ -2551,9 +2654,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.32"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad"
checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
@@ -2563,9 +2666,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.82"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602"
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -2573,9 +2676,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.82"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da"
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [
"proc-macro2",
"quote",
@@ -2586,15 +2689,15 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.82"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]]
name = "web-sys"
version = "0.3.59"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1"
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -2612,9 +2715,9 @@ dependencies = [
[[package]]
name = "webpki-roots"
version = "0.22.4"
version = "0.22.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf"
checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be"
dependencies = [
"webpki",
]
@@ -2630,15 +2733,21 @@ dependencies = [
[[package]]
name = "which"
version = "4.2.5"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae"
checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b"
dependencies = [
"either",
"lazy_static",
"libc",
"once_cell",
]
[[package]]
name = "widestring"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983"
[[package]]
name = "winapi"
version = "0.2.8"

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.50"
version = "0.0.62"
edition = "2021"
[lib]
@@ -27,12 +27,16 @@ once_cell = { version = "1.13.1" }
path-absolutize = { version = "3.0.13", features = ["once_cell_cache"] }
rayon = { version = "1.5.3" }
regex = { version = "1.6.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/charliermarsh/RustPython.git", rev = "4f457893efc381ad5c432576b24bcc7e4a08c641" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "4f457893efc381ad5c432576b24bcc7e4a08c641" }
rustpython-common = { git = "https://github.com/charliermarsh/RustPython.git", rev = "4f457893efc381ad5c432576b24bcc7e4a08c641" }
serde = { version = "1.0.143", features = ["derive"] }
serde_json = { version = "1.0.83" }
toml = { version = "0.5.9" }
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
walkdir = { version = "2.3.2" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = "0.24.3"
[dev-dependencies]
insta = { version = "1.19.1", features = ["yaml"] }

144
README.md
View File

@@ -130,6 +130,8 @@ Options:
Enable automatic additions of noqa directives to failing lines
--dummy-variable-rgx <DUMMY_VARIABLE_RGX>
Regular expression matching the name of dummy variables
--target-version <TARGET_VERSION>
The minimum Python version that should be supported
-h, --help
Print help information
-V, --version
@@ -201,72 +203,100 @@ stylistic lint rules that are obviated by autoformatting.
### Parity with Flake8
ruff's goal is to achieve feature-parity with Flake8 when used (1) without plugins, (2) alongside
ruff's goal is to achieve feature parity with Flake8 when used (1) without plugins, (2) alongside
Black, and (3) on Python 3 code.
**Under those conditions, ruff implements 44 out of 60 rules.** (ruff is missing: 14 rules related
to string `.format` calls, 1 rule related to docstring parsing, and 1 rule related to redefined
variables.)
ruff also implements some of the most popular Flake8 plugins natively, including:
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) (partial)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (partial)
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
1. Flake8 has a plugin architecture and supports writing custom lint rules.
2. ruff does not yet support a few Python 3.9 and 3.10 language features, including structural
1. ruff does not yet support a few Python 3.9 and 3.10 language features, including structural
pattern matching and parenthesized context managers.
2. Flake8 has a plugin architecture and supports writing custom lint rules.
## Rules
| Code | Name | Message |
| ---- | ----- | ------- |
| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file |
| E501 | LineTooLong | Line too long (89 > 88 characters) |
| E711 | NoneComparison | Comparison to `None` should be `cond is None` |
| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` |
| E713 | NotInTest | Test for membership should be `not in` |
| E714 | NotIsTest | Test for object identity should be `is not` |
| E721 | TypeComparison | do not compare types, use `isinstance()` |
| E722 | DoNotUseBareExcept | Do not use bare `except` |
| E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def |
| E741 | AmbiguousVariableName | ambiguous variable name '...' |
| E742 | AmbiguousClassName | ambiguous class name '...' |
| E743 | AmbiguousFunctionName | ambiguous function name '...' |
| E902 | IOError | ... |
| E999 | SyntaxError | SyntaxError: ... |
| F401 | UnusedImport | `...` imported but unused |
| F402 | ImportShadowedByLoopVar | import '...' from line 1 shadowed by loop variable |
| F403 | ImportStarUsed | `from ... import *` used; unable to detect undefined names |
| F404 | LateFutureImport | from __future__ imports must occur at the beginning of the file |
| F405 | ImportStarUsage | '...' may be undefined, or defined from star imports: ... |
| F406 | ImportStarNotPermitted | `from ... import *` only allowed at module level |
| F407 | FutureFeatureNotDefined | future feature '...' is not defined |
| F541 | FStringMissingPlaceholders | f-string without any placeholders |
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated |
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated |
| F621 | TooManyExpressionsInStarredAssignment | too many expressions in star-unpacking assignment |
| F622 | TwoStarredExpressions | two starred expressions in assignment |
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` |
| F632 | IsLiteral | use ==/!= to compare constant literals |
| F633 | InvalidPrintSyntax | use of >> is invalid with print function |
| F634 | IfTuple | If test is a tuple, which is always `True` |
| F701 | BreakOutsideLoop | `break` outside loop |
| F702 | ContinueOutsideLoop | `continue` not properly in loop |
| F704 | YieldOutsideFunction | a `yield` or `yield from` statement outside of a function/method |
| F706 | ReturnOutsideFunction | a `return` statement outside of a function/method |
| F707 | DefaultExceptNotLast | an `except:` block as not the last exception handler |
| F722 | ForwardAnnotationSyntaxError | syntax error in forward annotation '...' |
| F821 | UndefinedName | Undefined name `...` |
| F822 | UndefinedExport | Undefined name `...` in `__all__` |
| F823 | UndefinedLocal | Local variable `...` referenced before assignment |
| F831 | DuplicateArgumentName | Duplicate argument name in function definition |
| F841 | UnusedVariable | Local variable `...` is assigned to but never used |
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` |
| A001 | BuiltinVariableShadowing | Variable `...` is shadowing a python builtin |
| A002 | BuiltinArgumentShadowing | Argument `...` is shadowing a python builtin |
| A003 | BuiltinAttributeShadowing | class attribute `...` is shadowing a python builtin |
| SPR001 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` |
| R001 | UselessObjectInheritance | Class `...` inherits from object |
| R002 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead |
| M001 | UnusedNOQA | Unused `noqa` directive |
The ✅ emoji indicates a rule is enabled by default.
The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` command-line option.
| Code | Name | Message | | |
| ---- | ---- | ------- | --- | --- |
| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file | ✅ | |
| E501 | LineTooLong | Line too long (89 > 88 characters) | ✅ | |
| E711 | NoneComparison | Comparison to `None` should be `cond is None` | ✅ | |
| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` | ✅ | |
| E713 | NotInTest | Test for membership should be `not in` | ✅ | |
| E714 | NotIsTest | Test for object identity should be `is not` | ✅ | |
| E721 | TypeComparison | Do not compare types, use `isinstance()` | ✅ | |
| E722 | DoNotUseBareExcept | Do not use bare `except` | ✅ | |
| E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def | ✅ | |
| E741 | AmbiguousVariableName | Ambiguous variable name: `...` | ✅ | |
| E742 | AmbiguousClassName | Ambiguous class name: `...` | ✅ | |
| E743 | AmbiguousFunctionName | Ambiguous function name: `...` | ✅ | |
| E902 | IOError | IOError: `...` | ✅ | |
| E999 | SyntaxError | SyntaxError: `...` | ✅ | |
| W292 | NoNewLineAtEndOfFile | No newline at end of file | ✅ | |
| F401 | UnusedImport | `...` imported but unused | ✅ | 🛠 |
| F402 | ImportShadowedByLoopVar | Import `...` from line 1 shadowed by loop variable | ✅ | |
| F403 | ImportStarUsed | `from ... import *` used; unable to detect undefined names | ✅ | |
| F404 | LateFutureImport | `from __future__` imports must occur at the beginning of the file | ✅ | |
| F405 | ImportStarUsage | `...` may be undefined, or defined from star imports: `...` | ✅ | |
| F406 | ImportStarNotPermitted | `from ... import *` only allowed at module level | ✅ | |
| F407 | FutureFeatureNotDefined | Future feature `...` is not defined | ✅ | |
| F541 | FStringMissingPlaceholders | f-string without any placeholders | ✅ | |
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated | ✅ | |
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated | ✅ | |
| F621 | ExpressionsInStarAssignment | Too many expressions in star-unpacking assignment | ✅ | |
| F622 | TwoStarredExpressions | Two starred expressions in assignment | ✅ | |
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` | ✅ | |
| F632 | IsLiteral | Use `==` and `!=` to compare constant literals | ✅ | |
| F633 | InvalidPrintSyntax | Use of `>>` is invalid with `print` function | ✅ | |
| F634 | IfTuple | If test is a tuple, which is always `True` | ✅ | |
| F701 | BreakOutsideLoop | `break` outside loop | ✅ | |
| F702 | ContinueOutsideLoop | `continue` not properly in loop | ✅ | |
| F704 | YieldOutsideFunction | `yield` or `yield from` statement outside of a function/method | ✅ | |
| F706 | ReturnOutsideFunction | `return` statement outside of a function/method | ✅ | |
| F707 | DefaultExceptNotLast | An `except:` block as not the last exception handler | ✅ | |
| F722 | ForwardAnnotationSyntaxError | Syntax error in forward annotation: `...` | ✅ | |
| F821 | UndefinedName | Undefined name `...` | ✅ | |
| F822 | UndefinedExport | Undefined name `...` in `__all__` | ✅ | |
| F823 | UndefinedLocal | Local variable `...` referenced before assignment | ✅ | |
| F831 | DuplicateArgumentName | Duplicate argument name in function definition | ✅ | |
| F841 | UnusedVariable | Local variable `...` is assigned to but never used | ✅ | |
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` | ✅ | |
| A001 | BuiltinVariableShadowing | Variable `...` is shadowing a python builtin | | |
| A002 | BuiltinArgumentShadowing | Argument `...` is shadowing a python builtin | | |
| A003 | BuiltinAttributeShadowing | Class attribute `...` is shadowing a python builtin | | |
| C400 | UnnecessaryGeneratorList | Unnecessary generator - rewrite as a list comprehension | | |
| 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 | | |
| 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 | | |
| SPR001 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | | 🛠 |
| T201 | PrintFound | `print` found | | 🛠 |
| T203 | PPrintFound | `pprint` found | | 🛠 |
| U001 | UselessMetaclassType | `__metaclass__ = type` is implied | | 🛠 |
| U002 | UnnecessaryAbspath | `abspath(__file__)` is unnecessary in Python 3.9 and later | | 🛠 |
| U003 | TypeOfPrimitive | Use `str` instead of `type(...)` | | 🛠 |
| U004 | UselessObjectInheritance | Class `...` inherits from object | | 🛠 |
| U005 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead | | 🛠 |
| U006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | | 🛠 |
| U007 | UsePEP604Annotation | Use `X | Y` for type annotations | | 🛠 |
| M001 | UnusedNOQA | Unused `noqa` directive | | 🛠 |
## Integrations
@@ -293,9 +323,9 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Install Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install dependencies
@@ -469,4 +499,4 @@ MIT
## Contributing
Contributions are welcome and hugely appreciated. To get started, check out the
[contributing guidelines](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTORS.md).
[contributing guidelines](https://github.com/charliermarsh/ruff/blob/main/CONTRIBUTING.md).

View File

@@ -1,19 +1,26 @@
/// Generate a Markdown-compatible table of supported lint rules.
use ruff::checks::{CheckCode, ALL_CHECK_CODES};
use strum::IntoEnumIterator;
use ruff::checks::{CheckCode, DEFAULT_CHECK_CODES};
fn main() {
let mut check_codes: Vec<CheckCode> = ALL_CHECK_CODES.to_vec();
check_codes.sort();
println!("| Code | Name | Message |");
println!("| ---- | ----- | ------- |");
for check_code in check_codes {
println!("| Code | Name | Message | | |");
println!("| ---- | ---- | ------- | --- | --- |");
for check_code in CheckCode::iter() {
let check_kind = check_code.kind();
let default_token = if DEFAULT_CHECK_CODES.contains(&check_code) {
""
} else {
""
};
let fix_token = if check_kind.fixable() { "🛠" } else { "" };
println!(
"| {} | {} | {} |",
check_kind.code().as_str(),
check_kind.name(),
check_kind.body()
"| {} | {} | {} | {} | {} |",
check_kind.code().as_ref(),
check_kind.as_ref(),
check_kind.body(),
default_token,
fix_token
);
}
}

View File

@@ -0,0 +1,26 @@
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use rustpython_parser::parser;
use ruff::code_gen::SourceGenerator;
use ruff::fs;
#[derive(Debug, Parser)]
struct Cli {
#[arg(required = true)]
file: PathBuf,
}
fn main() -> Result<()> {
let cli = Cli::parse();
let contents = fs::read_file(&cli.file)?;
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
let mut generator = SourceGenerator::new();
generator.unparse_suite(&python_ast)?;
println!("{}", generator.generate()?);
Ok(())
}

1
resources/test/fixtures/C400.py vendored Normal file
View File

@@ -0,0 +1 @@
x = list(x for x in range(3))

1
resources/test/fixtures/C401.py vendored Normal file
View File

@@ -0,0 +1 @@
x = set(x for x in range(3))

1
resources/test/fixtures/C402.py vendored Normal file
View File

@@ -0,0 +1 @@
d = dict((x, x) for x in range(3))

1
resources/test/fixtures/C403.py vendored Normal file
View File

@@ -0,0 +1 @@
s = set([x for x in range(3)])

1
resources/test/fixtures/C404.py vendored Normal file
View File

@@ -0,0 +1 @@
d = dict([(i, i) for i in range(3)])

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

@@ -0,0 +1,5 @@
s1 = set([1, 2])
s2 = set((1, 2))
s3 = set([])
s4 = set(())
s5 = set()

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

@@ -0,0 +1,5 @@
d1 = dict([(1, 2)])
d2 = dict(((1, 2),))
d3 = dict([])
d4 = dict(())
d5 = dict()

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

@@ -0,0 +1,5 @@
t = tuple()
l = list()
d1 = dict()
d2 = dict(a=1)
d3 = dict(**d2)

View File

@@ -1,6 +1,5 @@
from __future__ import all_feature_names
import os
import functools
import functools, os
from datetime import datetime
from collections import (
Counter,
@@ -11,14 +10,36 @@ import multiprocessing.pool
import multiprocessing.process
import logging.config
import logging.handlers
from typing import TYPING_CHECK, NamedTuple, Dict, Type, TypeVar, List, Set, Union, cast
from typing import (
TYPE_CHECKING,
NamedTuple,
Dict,
Type,
TypeVar,
List,
Set,
Union,
cast,
)
from dataclasses import MISSING, field
from blah import ClassA, ClassB, ClassC
if TYPING_CHECK:
if TYPE_CHECKING:
from models import Fruit, Nut, Vegetable
if TYPE_CHECKING:
import shelve
import importlib
if TYPE_CHECKING:
"""Hello, world!"""
import pathlib
z = 1
class X:
datetime: datetime
foo: Type["NamedTuple"]
@@ -28,6 +49,9 @@ class X:
y = Counter()
z = multiprocessing.pool.ThreadPool()
def b(self) -> None:
import pickle
__all__ = ["ClassA"] + ["ClassB"]
__all__ += ["ClassC"]
@@ -39,3 +63,5 @@ Z = TypeVar("Z", "List", "Set")
a = list["Fruit"]
b = Union["Nut", None]
c = cast("Vegetable", b)
Field = lambda default=MISSING: field(default=default)

View File

@@ -89,3 +89,36 @@ A = (
f'B'
f'{B}'
)
from typing import Annotated, Literal
def arbitrary_callable() -> None:
...
class PEP593Test:
field: Annotated[
int,
"base64",
arbitrary_callable(),
123,
(1, 2, 3),
]
field_with_stringified_type: Annotated[
"PEP593Test",
123,
]
field_with_undefined_stringified_type: Annotated[
"PEP593Test123",
123,
]
field_with_nested_subscript: Annotated[
dict[Literal["foo"], str],
123,
]
field_with_undefined_nested_subscript: Annotated[
dict["foo", "bar"], # Expected to fail as undefined.
123,
]

View File

@@ -20,3 +20,46 @@ class Child(Parent):
Child,
self,
).method() # wrong
class BaseClass:
def f(self):
print("f")
def defined_outside(self):
super(MyClass, self).f() # CANNOT use super()
class MyClass(BaseClass):
def normal(self):
super(MyClass, self).f() # can use super()
super().f()
def different_argument(self, other):
super(MyClass, other).f() # CANNOT use super()
def comprehension_scope(self):
[super(MyClass, self).f() for x in [1]] # CANNOT use super()
def inner_functions(self):
def outer_argument():
super(MyClass, self).f() # CANNOT use super()
def inner_argument(self):
super(MyClass, self).f() # can use super()
super().f()
outer_argument()
inner_argument(self)
def inner_class(self):
class InnerClass:
super(MyClass, self).f() # CANNOT use super()
def method(inner_self):
super(MyClass, self).f() # CANNOT use super()
InnerClass().method()
defined_outside = defined_outside

1
resources/test/fixtures/T201.py vendored Normal file
View File

@@ -0,0 +1 @@
print("Hello, world!") # T201

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

@@ -0,0 +1,10 @@
from pprint import pprint
pprint("Hello, world!") # T203
import pprint
pprint.pprint("Hello, world!") # T203
pprint.pformat("Hello, world!")

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

@@ -0,0 +1,13 @@
class A:
__metaclass__ = type
class B:
__metaclass__ = type
def __init__(self) -> None:
pass
class C(metaclass=type):
pass

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

@@ -0,0 +1,15 @@
from os.path import abspath
x = abspath(__file__)
import os
y = os.path.abspath(__file__)
from os import path
z = path.abspath(__file__)

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

@@ -0,0 +1,5 @@
type('')
type(b'')
type(0)
type(0.)
type(0j)

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

@@ -0,0 +1,12 @@
from typing import List
def f(x: List[str]) -> None:
...
import typing
def f(x: typing.List[str]) -> None:
...

40
resources/test/fixtures/U007.py vendored Normal file
View File

@@ -0,0 +1,40 @@
from typing import Optional
def f(x: Optional[str]) -> None:
...
import typing
def f(x: typing.Optional[str]) -> None:
...
from typing import Union
def f(x: Union[str, int, Union[float, bytes]]) -> None:
...
import typing
def f(x: typing.Union[str, int]) -> None:
...
from typing import Union
def f(x: "Union[str, int, Union[float, bytes]]") -> None:
...
import typing
def f(x: "typing.Union[str, int]") -> None:
...

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

@@ -0,0 +1,2 @@
def fn() -> None:
pass

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

@@ -0,0 +1,2 @@
def fn() -> None:
pass # noqa: W292

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

@@ -0,0 +1,2 @@
def fn() -> None:
pass

View File

@@ -2,7 +2,10 @@ from __future__ import annotations
from dataclasses import dataclass
from models import Fruit, Nut
from models import (
Fruit,
Nut,
)
@dataclass

View File

@@ -1,4 +1,5 @@
pub mod checks;
pub mod helpers;
pub mod operations;
pub mod relocate;
pub mod types;

View File

@@ -3,16 +3,15 @@ use std::collections::BTreeSet;
use itertools::izip;
use regex::Regex;
use rustpython_parser::ast::{
Arg, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Keyword,
Location, Stmt, StmtKind, Unaryop,
Arg, ArgData, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind,
KeywordData, Located, Stmt, StmtKind, Unaryop,
};
use serde::{Deserialize, Serialize};
use crate::ast::operations::SourceCodeLocator;
use crate::ast::types::{
Binding, BindingKind, CheckLocator, FunctionScope, Range, Scope, ScopeKind,
};
use crate::autofix::{fixer, fixes};
use crate::checks::{Check, CheckKind, Fix, RejectedCmpop};
use crate::checks::{Check, CheckKind, RejectedCmpop};
use crate::python::builtins::BUILTINS;
/// Check IfTuple compliance.
@@ -99,7 +98,7 @@ pub fn check_unused_variables(
{
checks.push(Check::new(
CheckKind::UnusedVariable(name.to_string()),
locator.locate_check(binding.location),
locator.locate_check(binding.range),
));
}
}
@@ -116,6 +115,106 @@ pub fn check_do_not_assign_lambda(value: &Expr, location: Range) -> Option<Check
}
}
/// Check UselessMetaclassType compliance.
pub fn check_useless_metaclass_type(
targets: &Vec<Expr>,
value: &Expr,
location: Range,
) -> Option<Check> {
if targets.len() == 1 {
if let ExprKind::Name { id, .. } = targets.first().map(|expr| &expr.node).unwrap() {
if id == "__metaclass__" {
if let ExprKind::Name { id, .. } = &value.node {
if id == "type" {
return Some(Check::new(CheckKind::UselessMetaclassType, location));
}
}
}
}
}
None
}
/// Check UnnecessaryAbspath compliance.
pub fn check_unnecessary_abspath(func: &Expr, args: &Vec<Expr>, location: Range) -> Option<Check> {
// Validate the arguments.
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &args[0].node {
if id == "__file__" {
match &func.node {
ExprKind::Attribute { attr: id, .. } | ExprKind::Name { id, .. } => {
if id == "abspath" {
return Some(Check::new(CheckKind::UnnecessaryAbspath, location));
}
}
_ => {}
}
}
}
}
None
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Primitive {
Bool,
Str,
Bytes,
Int,
Float,
Complex,
}
impl Primitive {
fn from_constant(constant: &Constant) -> Option<Self> {
match constant {
Constant::Bool(_) => Some(Primitive::Bool),
Constant::Str(_) => Some(Primitive::Str),
Constant::Bytes(_) => Some(Primitive::Bytes),
Constant::Int(_) => Some(Primitive::Int),
Constant::Float(_) => Some(Primitive::Float),
Constant::Complex { .. } => Some(Primitive::Complex),
_ => None,
}
}
pub fn builtin(&self) -> String {
match self {
Primitive::Bool => "bool".to_string(),
Primitive::Str => "str".to_string(),
Primitive::Bytes => "bytes".to_string(),
Primitive::Int => "int".to_string(),
Primitive::Float => "float".to_string(),
Primitive::Complex => "complex".to_string(),
}
}
}
/// Check TypeOfPrimitive compliance.
pub fn check_type_of_primitive(func: &Expr, args: &Vec<Expr>, location: Range) -> Option<Check> {
// Validate the arguments.
if args.len() == 1 {
match &func.node {
ExprKind::Attribute { attr: id, .. } | ExprKind::Name { id, .. } => {
if id == "type" {
if let ExprKind::Constant { value, .. } = &args[0].node {
if let Some(primitive) = Primitive::from_constant(value) {
return Some(Check::new(
CheckKind::TypeOfPrimitive(primitive),
location,
));
}
}
}
}
_ => {}
}
}
None
}
fn is_ambiguous_name(name: &str) -> bool {
name == "l" || name == "I" || name == "O"
}
@@ -158,13 +257,9 @@ pub fn check_ambiguous_function_name(name: &str, location: Range) -> Option<Chec
/// Check UselessObjectInheritance compliance.
pub fn check_useless_object_inheritance(
stmt: &Stmt,
name: &str,
bases: &[Expr],
keywords: &[Keyword],
scope: &Scope,
locator: &mut SourceCodeLocator,
autofix: &fixer::Mode,
) -> Option<Check> {
for expr in bases {
if let ExprKind::Name { id, .. } = &expr.node {
@@ -175,22 +270,10 @@ pub fn check_useless_object_inheritance(
kind: BindingKind::Builtin,
..
}) => {
let mut check = Check::new(
return Some(Check::new(
CheckKind::UselessObjectInheritance(name.to_string()),
Range::from_located(expr),
);
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let Some(fix) = fixes::remove_class_def_base(
locator,
&stmt.location,
expr.location,
bases,
keywords,
) {
check.amend(fix);
}
}
return Some(check);
));
}
_ => {}
}
@@ -278,28 +361,15 @@ pub fn check_duplicate_arguments(arguments: &Arguments) -> Vec<Check> {
}
/// Check AssertEquals compliance.
pub fn check_assert_equals(expr: &Expr, autofix: &fixer::Mode) -> Option<Check> {
pub fn check_assert_equals(expr: &Expr) -> Option<Check> {
if let ExprKind::Attribute { value, attr, .. } = &expr.node {
if attr == "assertEquals" {
if let ExprKind::Name { id, .. } = &value.node {
if id == "self" {
let mut check =
Check::new(CheckKind::NoAssertEquals, Range::from_located(expr));
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix {
content: "assertEqual".to_string(),
location: Location::new(
expr.location.row(),
expr.location.column() + 1,
),
end_location: Location::new(
expr.location.row(),
expr.location.column() + 1 + "assertEquals".len(),
),
applied: false,
});
}
return Some(check);
return Some(Check::new(
CheckKind::NoAssertEquals,
Range::from_located(expr),
));
}
}
}
@@ -576,10 +646,7 @@ pub fn check_starred_expressions(
if check_too_many_expressions {
if let Some(starred_index) = starred_index {
if starred_index >= 1 << 8 || elts.len() - starred_index > 1 << 24 {
return Some(Check::new(
CheckKind::TooManyExpressionsInStarredAssignment,
location,
));
return Some(Check::new(CheckKind::ExpressionsInStarAssignment, location));
}
}
}
@@ -696,28 +763,323 @@ pub fn check_builtin_shadowing(
}
}
// flake8-super
/// Check that `super()` has no args
pub fn check_super_args(
expr: &Expr,
func: &Expr,
args: &Vec<Expr>,
locator: &mut SourceCodeLocator,
autofix: &fixer::Mode,
) -> Option<Check> {
/// Returns `true` if a call is an argumented `super` invocation.
pub fn is_super_call_with_arguments(func: &Expr, args: &Vec<Expr>) -> bool {
// Check: is this a `super` call?
if let ExprKind::Name { id, .. } = &func.node {
if id == "super" && !args.is_empty() {
let mut check = Check::new(
CheckKind::SuperCallWithParameters,
Range::from_located(expr),
);
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let Some(fix) = fixes::remove_super_arguments(locator, expr) {
check.amend(fix);
id == "super" && !args.is_empty()
} else {
false
}
}
// flakes8-comprehensions
/// Check `list(generator)` compliance.
pub fn unnecessary_generator_list(expr: &Expr, func: &Expr, args: &Vec<Expr>) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "list" {
if let ExprKind::GeneratorExp { .. } = &args[0].node {
return Some(Check::new(
CheckKind::UnnecessaryGeneratorList,
Range::from_located(expr),
));
}
}
return Some(check);
}
}
None
}
/// Check `set(generator)` compliance.
pub fn unnecessary_generator_set(expr: &Expr, func: &Expr, args: &Vec<Expr>) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "set" {
if let ExprKind::GeneratorExp { .. } = &args[0].node {
return Some(Check::new(
CheckKind::UnnecessaryGeneratorList,
Range::from_located(expr),
));
}
}
}
}
None
}
/// Check `dict((x, y) for x, y in iterable)` compliance.
pub fn unnecessary_generator_dict(expr: &Expr, func: &Expr, args: &Vec<Expr>) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "dict" {
if let ExprKind::GeneratorExp { elt, .. } = &args[0].node {
match &elt.node {
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
return Some(Check::new(
CheckKind::UnnecessaryListComprehensionDict,
Range::from_located(expr),
));
}
_ => {}
}
}
}
}
}
None
}
/// Check `set([...])` compliance.
pub fn unnecessary_list_comprehension_set(
expr: &Expr,
func: &Expr,
args: &Vec<Expr>,
) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "set" {
if let ExprKind::ListComp { .. } = &args[0].node {
return Some(Check::new(
CheckKind::UnnecessaryListComprehensionSet,
Range::from_located(expr),
));
}
}
}
}
None
}
/// Check `dict([...])` compliance.
pub fn unnecessary_list_comprehension_dict(
expr: &Expr,
func: &Expr,
args: &Vec<Expr>,
) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "dict" {
if let ExprKind::ListComp { elt, .. } = &args[0].node {
match &elt.node {
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
return Some(Check::new(
CheckKind::UnnecessaryListComprehensionDict,
Range::from_located(expr),
));
}
_ => {}
}
}
}
}
}
None
}
/// Check `set([1, 2])` compliance.
pub fn unnecessary_literal_set(expr: &Expr, func: &Expr, args: &Vec<Expr>) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "set" {
match &args[0].node {
ExprKind::List { .. } => {
return Some(Check::new(
CheckKind::UnnecessaryLiteralSet("list".to_string()),
Range::from_located(expr),
));
}
ExprKind::Tuple { .. } => {
return Some(Check::new(
CheckKind::UnnecessaryLiteralSet("tuple".to_string()),
Range::from_located(expr),
));
}
_ => {}
}
}
}
}
None
}
/// Check `dict([(1, 2)])` compliance.
pub fn unnecessary_literal_dict(expr: &Expr, func: &Expr, args: &Vec<Expr>) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "dict" {
match &args[0].node {
ExprKind::Tuple { elts, .. } => {
if let Some(elt) = elts.first() {
match &elt.node {
// dict((1, 2), ...))
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
return Some(Check::new(
CheckKind::UnnecessaryLiteralDict("tuple".to_string()),
Range::from_located(expr),
));
}
_ => {}
}
} else {
// dict(())
return Some(Check::new(
CheckKind::UnnecessaryLiteralDict("tuple".to_string()),
Range::from_located(expr),
));
}
}
ExprKind::List { elts, .. } => {
if let Some(elt) = elts.first() {
match &elt.node {
// dict([(1, 2), ...])
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
return Some(Check::new(
CheckKind::UnnecessaryLiteralDict("list".to_string()),
Range::from_located(expr),
));
}
_ => {}
}
} else {
// dict([])
return Some(Check::new(
CheckKind::UnnecessaryLiteralDict("list".to_string()),
Range::from_located(expr),
));
}
}
_ => {}
}
}
}
}
None
}
pub fn unnecessary_collection_call(
expr: &Expr,
func: &Expr,
args: &Vec<Expr>,
keywords: &Vec<Located<KeywordData>>,
) -> Option<Check> {
if args.is_empty() {
if let ExprKind::Name { id, .. } = &func.node {
if id == "list" || id == "tuple" {
// list() or tuple()
return Some(Check::new(
CheckKind::UnnecessaryCollectionCall(id.to_string()),
Range::from_located(expr),
));
} else if id == "dict" {
// dict() or dict(a=1)
if keywords.is_empty() || keywords.iter().all(|kw| kw.node.arg.is_some()) {
return Some(Check::new(
CheckKind::UnnecessaryCollectionCall(id.to_string()),
Range::from_located(expr),
));
}
}
}
}
None
}
// flake8-super
/// Check that `super()` has no args
pub fn check_super_args(
scope: &Scope,
parents: &[&Stmt],
expr: &Expr,
func: &Expr,
args: &Vec<Expr>,
) -> Option<Check> {
if !is_super_call_with_arguments(func, args) {
return None;
}
// Check: are we in a Function scope?
if !matches!(scope.kind, ScopeKind::Function { .. }) {
return None;
}
let mut parents = parents.iter().rev();
// For a `super` invocation to be unnecessary, the first argument needs to match the enclosing
// class, and the second argument needs to match the first argument to the enclosing function.
if let [first_arg, second_arg] = args.as_slice() {
// Find the enclosing function definition (if any).
if let Some(StmtKind::FunctionDef {
args: parent_args, ..
}) = parents
.find(|stmt| matches!(stmt.node, StmtKind::FunctionDef { .. }))
.map(|stmt| &stmt.node)
{
// Extract the name of the first argument to the enclosing function.
if let Some(ArgData {
arg: parent_arg, ..
}) = parent_args.args.first().map(|expr| &expr.node)
{
// Find the enclosing class definition (if any).
if let Some(StmtKind::ClassDef {
name: parent_name, ..
}) = parents
.find(|stmt| matches!(stmt.node, StmtKind::ClassDef { .. }))
.map(|stmt| &stmt.node)
{
if let (
ExprKind::Name {
id: first_arg_id, ..
},
ExprKind::Name {
id: second_arg_id, ..
},
) = (&first_arg.node, &second_arg.node)
{
if first_arg_id == parent_name && second_arg_id == parent_arg {
return Some(Check::new(
CheckKind::SuperCallWithParameters,
Range::from_located(expr),
));
}
}
}
}
}
}
None
}
// flake8-print
/// Check whether a function call is a `print` or `pprint` invocation
pub fn check_print_call(
expr: &Expr,
func: &Expr,
check_print: bool,
check_pprint: bool,
) -> Option<Check> {
if let ExprKind::Name { id, .. } = &func.node {
if check_print && id == "print" {
return Some(Check::new(CheckKind::PrintFound, Range::from_located(expr)));
} else if check_pprint && id == "pprint" {
return Some(Check::new(
CheckKind::PPrintFound,
Range::from_located(expr),
));
}
}
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if let ExprKind::Name { id, .. } = &value.node {
if check_pprint && id == "pprint" && attr == "pprint" {
return Some(Check::new(
CheckKind::PPrintFound,
Range::from_located(expr),
));
}
}
}
None
}

9
src/ast/helpers.rs Normal file
View File

@@ -0,0 +1,9 @@
use rustpython_ast::{Expr, ExprKind};
pub fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
match &expr.node {
ExprKind::Attribute { attr, .. } => target == attr,
ExprKind::Name { id, .. } => target == id,
_ => false,
}
}

View File

@@ -1,6 +1,6 @@
use rustpython_parser::ast::{Constant, Expr, ExprKind, Location, Stmt, StmtKind};
use crate::ast::types::{BindingKind, Scope};
use crate::ast::types::{BindingKind, Range, Scope};
/// Extract the names bound to a given __all__ assignment.
pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
@@ -148,7 +148,7 @@ impl<'a> SourceCodeLocator<'a> {
&self.content[offset..]
}
pub fn slice_source_code_range(&mut self, start: &Location, end: &Location) -> &'a str {
pub fn slice_source_code_range(&mut self, range: &Range) -> &'a str {
if !self.initialized {
let mut offset = 0;
for i in self.content.lines() {
@@ -158,8 +158,8 @@ impl<'a> SourceCodeLocator<'a> {
}
self.initialized = true;
}
let start = self.offsets[start.row() - 1] + start.column() - 1;
let end = self.offsets[end.row() - 1] + end.column() - 1;
let start = self.offsets[range.location.row() - 1] + range.location.column() - 1;
let end = self.offsets[range.end_location.row() - 1] + range.end_location.column() - 1;
&self.content[start..end]
}
}

View File

@@ -8,7 +8,7 @@ fn id() -> usize {
COUNTER.fetch_add(1, Ordering::Relaxed)
}
#[derive(Clone, Copy, Debug, Default)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Range {
pub location: Location,
pub end_location: Location,
@@ -55,6 +55,12 @@ impl Scope {
}
}
#[derive(Clone, Debug)]
pub struct BindingContext {
pub defined_by: usize,
pub defined_in: Option<usize>,
}
#[derive(Clone, Debug)]
pub enum BindingKind {
Annotation,
@@ -67,15 +73,16 @@ pub enum BindingKind {
Definition,
Export(Vec<String>),
FutureImportation,
Importation(String),
StarImportation,
SubmoduleImportation(String),
Importation(String, BindingContext),
FromImportation(String, BindingContext),
SubmoduleImportation(String, BindingContext),
}
#[derive(Clone, Debug)]
pub struct Binding {
pub kind: BindingKind,
pub location: Range,
pub range: Range,
/// Tuple of (scope index, range) indicating the scope and range at which the binding was
/// last used.
pub used: Option<(usize, Range)>,
@@ -84,3 +91,9 @@ pub struct Binding {
pub trait CheckLocator {
fn locate_check(&self, default: Range) -> Range;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ImportKind {
Import,
ImportFrom,
}

View File

@@ -2,6 +2,7 @@ use std::fs;
use std::path::Path;
use anyhow::Result;
use itertools::Itertools;
use rustpython_parser::ast::Location;
use crate::checks::{Check, Fix};
@@ -43,7 +44,7 @@ fn apply_fixes<'a>(fixes: impl Iterator<Item = &'a mut Fix>, contents: &str) ->
let mut output = "".to_string();
let mut last_pos: Location = Location::new(0, 0);
for fix in fixes {
for fix in fixes.sorted_by_key(|fix| fix.location) {
// Best-effort approach: if this fix overlaps with a fix we've already applied, skip it.
if last_pos > fix.location {
continue;
@@ -71,13 +72,18 @@ fn apply_fixes<'a>(fixes: impl Iterator<Item = &'a mut Fix>, contents: &str) ->
fix.applied = true;
}
if last_pos.row() > 0 || last_pos.column() > 0 {
if last_pos.row() > 0
&& (last_pos.row() - 1) < lines.len()
&& (last_pos.row() > 0 || last_pos.column() > 0)
{
output.push_str(&lines[last_pos.row() - 1][last_pos.column() - 1..]);
output.push('\n');
}
for line in &lines[last_pos.row()..] {
output.push_str(line);
output.push('\n');
if last_pos.row() < lines.len() {
for line in &lines[last_pos.row()..] {
output.push_str(line);
output.push('\n');
}
}
output

View File

@@ -1,9 +1,14 @@
use anyhow::Result;
use itertools::Itertools;
use libcst_native::ImportNames::Aliases;
use libcst_native::NameOrAttribute::N;
use libcst_native::{Codegen, Expression, SmallStatement, Statement};
use rustpython_parser::ast::{Expr, Keyword, Location};
use rustpython_parser::ast::{ExcepthandlerKind, Expr, Keyword, Location, Stmt, StmtKind};
use rustpython_parser::lexer;
use rustpython_parser::token::Tok;
use crate::ast::operations::SourceCodeLocator;
use crate::ast::types::Range;
use crate::checks::Fix;
/// Convert a location within a file (relative to `base`) to an absolute position.
@@ -127,7 +132,8 @@ pub fn remove_class_def_base(
}
pub fn remove_super_arguments(locator: &mut SourceCodeLocator, expr: &Expr) -> Option<Fix> {
let contents = locator.slice_source_code_range(&expr.location, &expr.end_location);
let range = Range::from_located(expr);
let contents = locator.slice_source_code_range(&range);
let mut tree = match libcst_native::parse_module(contents, None) {
Ok(m) => m,
@@ -146,8 +152,8 @@ pub fn remove_super_arguments(locator: &mut SourceCodeLocator, expr: &Expr) -> O
return Some(Fix {
content: state.to_string(),
location: expr.location,
end_location: expr.end_location,
location: range.location,
end_location: range.end_location,
applied: false,
});
}
@@ -156,3 +162,231 @@ pub fn remove_super_arguments(locator: &mut SourceCodeLocator, expr: &Expr) -> O
None
}
/// Determine if a body contains only a single statement, taking into account deleted.
fn has_single_child(body: &[Stmt], deleted: &[&Stmt]) -> bool {
body.iter().filter(|child| !deleted.contains(child)).count() == 1
}
/// Determine if a child is the only statement in its body.
fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool> {
match &parent.node {
StmtKind::FunctionDef { body, .. }
| StmtKind::AsyncFunctionDef { body, .. }
| StmtKind::ClassDef { body, .. }
| StmtKind::With { body, .. }
| StmtKind::AsyncWith { body, .. } => {
if body.iter().contains(child) {
Ok(has_single_child(body, deleted))
} else {
Err(anyhow::anyhow!("Unable to find child in parent body."))
}
}
StmtKind::For { body, orelse, .. }
| StmtKind::AsyncFor { body, orelse, .. }
| StmtKind::While { body, orelse, .. }
| StmtKind::If { body, orelse, .. } => {
if body.iter().contains(child) {
Ok(has_single_child(body, deleted))
} else if orelse.iter().contains(child) {
Ok(has_single_child(orelse, deleted))
} else {
Err(anyhow::anyhow!("Unable to find child in parent body."))
}
}
StmtKind::Try {
body,
handlers,
orelse,
finalbody,
} => {
if body.iter().contains(child) {
Ok(has_single_child(body, deleted))
} else if orelse.iter().contains(child) {
Ok(has_single_child(orelse, deleted))
} else if finalbody.iter().contains(child) {
Ok(has_single_child(finalbody, deleted))
} else if let Some(body) = handlers.iter().find_map(|handler| match &handler.node {
ExcepthandlerKind::ExceptHandler { body, .. } => {
if body.iter().contains(child) {
Some(body)
} else {
None
}
}
}) {
Ok(has_single_child(body, deleted))
} else {
Err(anyhow::anyhow!("Unable to find child in parent body."))
}
}
_ => Err(anyhow::anyhow!("Unable to find child in parent body.")),
}
}
pub fn remove_stmt(stmt: &Stmt, parent: Option<&Stmt>, deleted: &[&Stmt]) -> Result<Fix> {
if parent
.map(|parent| is_lone_child(stmt, parent, deleted))
.map_or(Ok(None), |v| v.map(Some))?
.unwrap_or_default()
{
// If removing this node would lead to an invalid syntax tree, replace
// it with a `pass`.
Ok(Fix {
location: stmt.location,
end_location: stmt.end_location,
content: "pass".to_string(),
applied: false,
})
} else {
// Otherwise, nuke the entire line.
// TODO(charlie): This logic assumes that there are no multi-statement physical lines.
Ok(Fix {
location: Location::new(stmt.location.row(), 1),
end_location: Location::new(stmt.end_location.row() + 1, 1),
content: "".to_string(),
applied: false,
})
}
}
/// Generate a Fix to remove any unused imports from an `import` statement.
pub fn remove_unused_imports(
locator: &mut SourceCodeLocator,
full_names: &[&str],
stmt: &Stmt,
parent: Option<&Stmt>,
deleted: &[&Stmt],
) -> Result<Fix> {
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(stmt)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
};
let body = if let Some(SmallStatement::Import(body)) = body.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::ImportFrom."
));
};
let aliases = &mut body.names;
// Preserve the trailing comma (or not) from the last entry.
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
// Identify unused imports from within the `import from`.
let mut removable = vec![];
for (index, alias) in aliases.iter().enumerate() {
if let N(import_name) = &alias.name {
if full_names.contains(&import_name.value) {
removable.push(index);
}
}
}
// TODO(charlie): This is quadratic.
for index in removable.iter().rev() {
aliases.remove(*index);
}
if let Some(alias) = aliases.last_mut() {
alias.comma = trailing_comma;
}
if aliases.is_empty() {
remove_stmt(stmt, parent, deleted)
} else {
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix {
content: state.to_string(),
location: stmt.location,
end_location: stmt.end_location,
applied: false,
})
}
}
/// Generate a Fix to remove any unused imports from an `import from` statement.
pub fn remove_unused_import_froms(
locator: &mut SourceCodeLocator,
full_names: &[&str],
stmt: &Stmt,
parent: Option<&Stmt>,
deleted: &[&Stmt],
) -> Result<Fix> {
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(stmt)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
};
let body = if let Some(SmallStatement::ImportFrom(body)) = body.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::ImportFrom."
));
};
let aliases = if let Aliases(aliases) = &mut body.names {
aliases
} else {
return Err(anyhow::anyhow!("Expected node to be: Aliases."));
};
// Preserve the trailing comma (or not) from the last entry.
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
// Identify unused imports from within the `import from`.
let mut removable = vec![];
for (index, alias) in aliases.iter().enumerate() {
if let N(name) = &alias.name {
let import_name = if let Some(N(module_name)) = &body.module {
format!("{}.{}", module_name.value, name.value)
} else {
name.value.to_string()
};
if full_names.contains(&import_name.as_str()) {
removable.push(index);
}
}
}
// TODO(charlie): This is quadratic.
for index in removable.iter().rev() {
aliases.remove(*index);
}
if let Some(alias) = aliases.last_mut() {
alias.comma = trailing_comma;
}
if aliases.is_empty() {
remove_stmt(stmt, parent, deleted)
} else {
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix {
content: state.to_string(),
location: stmt.location,
end_location: stmt.end_location,
applied: false,
})
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -32,8 +32,8 @@ pub fn check_lines(
settings: &Settings,
autofix: &fixer::Mode,
) {
let enforce_line_too_long = settings.select.contains(&CheckCode::E501);
let enforce_noqa = settings.select.contains(&CheckCode::M001);
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();
@@ -66,12 +66,12 @@ pub fn check_lines(
match noqa {
(Directive::All(_, _), matches) => {
matches.push(check.kind.code().as_str());
matches.push(check.kind.code().as_ref());
ignored.push(index)
}
(Directive::Codes(_, _, codes), matches) => {
if codes.contains(&check.kind.code().as_str()) {
matches.push(check.kind.code().as_str());
if codes.contains(&check.kind.code().as_ref()) {
matches.push(check.kind.code().as_ref());
ignored.push(index);
}
}
@@ -98,11 +98,11 @@ pub fn check_lines(
match noqa {
(Directive::All(_, _), matches) => {
matches.push(check.kind.code().as_str());
matches.push(check.kind.code().as_ref());
}
(Directive::Codes(_, _, codes), matches) => {
if codes.contains(&check.kind.code().as_str()) {
matches.push(check.kind.code().as_str());
if codes.contains(&check.kind.code().as_ref()) {
matches.push(check.kind.code().as_ref());
} else {
line_checks.push(check);
}
@@ -113,6 +113,45 @@ pub fn check_lines(
}
}
// Enforce newlines at end of files.
if settings.enabled.contains(&CheckCode::W292) && !contents.ends_with('\n') {
// Note: if `lines.last()` is `None`, then `contents` is empty (and so we don't want to
// raise W292 anyway).
if let Some(line) = lines.last() {
let lineno = lines.len() - 1;
let noqa_lineno = noqa_line_for
.get(lineno)
.map(|lineno| lineno - 1)
.unwrap_or(lineno);
let noqa = noqa_directives
.entry(noqa_lineno)
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
let check = Check::new(
CheckKind::NoNewLineAtEndOfFile,
Range {
location: Location::new(lines.len(), line.len() + 1),
end_location: Location::new(lines.len(), line.len() + 1),
},
);
match noqa {
(Directive::All(_, _), matches) => {
matches.push(check.kind.code().as_ref());
}
(Directive::Codes(_, _, codes), matches) => {
if codes.contains(&check.kind.code().as_ref()) {
matches.push(check.kind.code().as_ref());
} else {
line_checks.push(check);
}
}
(Directive::None, _) => line_checks.push(check),
}
}
}
// Enforce that the noqa directive was actually used.
if enforce_noqa {
for (row, (directive, matches)) in noqa_directives {

View File

@@ -1,12 +1,13 @@
use std::str::FromStr;
use crate::ast::types::Range;
use anyhow::Result;
use itertools::Itertools;
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
use strum_macros::{AsRefStr, EnumIter, EnumString};
pub const DEFAULT_CHECK_CODES: [CheckCode; 46] = [
// pycodestyle
use crate::ast::checks::Primitive;
use crate::ast::types::Range;
pub const DEFAULT_CHECK_CODES: [CheckCode; 43] = [
// pycodestyle errors
CheckCode::E402,
CheckCode::E501,
CheckCode::E711,
@@ -21,6 +22,8 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 46] = [
CheckCode::E743,
CheckCode::E902,
CheckCode::E999,
// pycodestyle warnings
CheckCode::W292,
// pyflakes
CheckCode::F401,
CheckCode::F402,
@@ -50,75 +53,24 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 46] = [
CheckCode::F831,
CheckCode::F841,
CheckCode::F901,
// flake8-builtins
CheckCode::A001,
CheckCode::A002,
CheckCode::A003,
// flake8-super
CheckCode::SPR001,
];
pub const ALL_CHECK_CODES: [CheckCode; 49] = [
// pycodestyle
CheckCode::E402,
CheckCode::E501,
CheckCode::E711,
CheckCode::E712,
CheckCode::E713,
CheckCode::E714,
CheckCode::E721,
CheckCode::E722,
CheckCode::E731,
CheckCode::E741,
CheckCode::E742,
CheckCode::E743,
CheckCode::E902,
CheckCode::E999,
// pyflakes
CheckCode::F401,
CheckCode::F402,
CheckCode::F403,
CheckCode::F404,
CheckCode::F405,
CheckCode::F406,
CheckCode::F407,
CheckCode::F541,
CheckCode::F601,
CheckCode::F602,
CheckCode::F621,
CheckCode::F622,
CheckCode::F631,
CheckCode::F632,
CheckCode::F633,
CheckCode::F634,
CheckCode::F701,
CheckCode::F702,
CheckCode::F704,
CheckCode::F706,
CheckCode::F707,
CheckCode::F722,
CheckCode::F821,
CheckCode::F822,
CheckCode::F823,
CheckCode::F831,
CheckCode::F841,
CheckCode::F901,
// flake8-builtins
CheckCode::A001,
CheckCode::A002,
CheckCode::A003,
// flake8-super
CheckCode::SPR001,
// Meta
CheckCode::M001,
// Refactor
CheckCode::R001,
CheckCode::R002,
];
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Hash, PartialOrd, Ord)]
#[derive(
AsRefStr,
EnumIter,
EnumString,
Debug,
PartialEq,
Eq,
Clone,
Serialize,
Deserialize,
Hash,
PartialOrd,
Ord,
)]
pub enum CheckCode {
// pycodestyle
// pycodestyle errors
E402,
E501,
E711,
@@ -133,6 +85,8 @@ pub enum CheckCode {
E743,
E902,
E999,
// pycodestyle warnings
W292,
// pyflakes
F401,
F402,
@@ -166,141 +120,123 @@ pub enum CheckCode {
A001,
A002,
A003,
// flake8-comprehensions
C400,
C401,
C402,
C403,
C404,
C405,
C406,
C408,
// flake8-super
SPR001,
// Refactor
R001,
R002,
// flake8-print
T201,
T203,
// pyupgrade
U001,
U002,
U003,
U004,
U005,
U006,
U007,
// Meta
M001,
}
impl FromStr for CheckCode {
type Err = anyhow::Error;
#[allow(clippy::upper_case_acronyms)]
pub enum LintSource {
AST,
Lines,
FileSystem,
}
fn from_str(s: &str) -> Result<Self> {
match s {
// pycodestyle
"E402" => Ok(CheckCode::E402),
"E501" => Ok(CheckCode::E501),
"E711" => Ok(CheckCode::E711),
"E712" => Ok(CheckCode::E712),
"E713" => Ok(CheckCode::E713),
"E714" => Ok(CheckCode::E714),
"E721" => Ok(CheckCode::E721),
"E722" => Ok(CheckCode::E722),
"E731" => Ok(CheckCode::E731),
"E741" => Ok(CheckCode::E741),
"E742" => Ok(CheckCode::E742),
"E743" => Ok(CheckCode::E743),
"E902" => Ok(CheckCode::E902),
"E999" => Ok(CheckCode::E999),
// pyflakes
"F401" => Ok(CheckCode::F401),
"F402" => Ok(CheckCode::F402),
"F403" => Ok(CheckCode::F403),
"F404" => Ok(CheckCode::F404),
"F405" => Ok(CheckCode::F405),
"F406" => Ok(CheckCode::F406),
"F407" => Ok(CheckCode::F407),
"F541" => Ok(CheckCode::F541),
"F601" => Ok(CheckCode::F601),
"F602" => Ok(CheckCode::F602),
"F621" => Ok(CheckCode::F621),
"F622" => Ok(CheckCode::F622),
"F631" => Ok(CheckCode::F631),
"F632" => Ok(CheckCode::F632),
"F633" => Ok(CheckCode::F633),
"F634" => Ok(CheckCode::F634),
"F701" => Ok(CheckCode::F701),
"F702" => Ok(CheckCode::F702),
"F704" => Ok(CheckCode::F704),
"F706" => Ok(CheckCode::F706),
"F707" => Ok(CheckCode::F707),
"F722" => Ok(CheckCode::F722),
"F821" => Ok(CheckCode::F821),
"F822" => Ok(CheckCode::F822),
"F823" => Ok(CheckCode::F823),
"F831" => Ok(CheckCode::F831),
"F841" => Ok(CheckCode::F841),
"F901" => Ok(CheckCode::F901),
// flake8-builtins
"A001" => Ok(CheckCode::A001),
"A002" => Ok(CheckCode::A002),
"A003" => Ok(CheckCode::A003),
// flake8-super
"SPR001" => Ok(CheckCode::SPR001),
// Refactor
"R001" => Ok(CheckCode::R001),
"R002" => Ok(CheckCode::R002),
// Meta
"M001" => Ok(CheckCode::M001),
_ => Err(anyhow::anyhow!("Unknown check code: {s}")),
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum RejectedCmpop {
Eq,
NotEq,
}
#[derive(AsRefStr, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CheckKind {
// pycodestyle errors
AmbiguousClassName(String),
AmbiguousFunctionName(String),
AmbiguousVariableName(String),
AssertTuple,
BreakOutsideLoop,
ContinueOutsideLoop,
DefaultExceptNotLast,
DoNotAssignLambda,
DoNotUseBareExcept,
DuplicateArgumentName,
ExpressionsInStarAssignment,
FStringMissingPlaceholders,
ForwardAnnotationSyntaxError(String),
FutureFeatureNotDefined(String),
IOError(String),
IfTuple,
ImportShadowedByLoopVar(String, usize),
ImportStarNotPermitted(String),
ImportStarUsage(String, Vec<String>),
ImportStarUsed(String),
InvalidPrintSyntax,
IsLiteral,
LateFutureImport,
LineTooLong(usize, usize),
ModuleImportNotAtTopOfFile,
MultiValueRepeatedKeyLiteral,
MultiValueRepeatedKeyVariable(String),
NoneComparison(RejectedCmpop),
NotInTest,
NotIsTest,
RaiseNotImplemented,
ReturnOutsideFunction,
SyntaxError(String),
TrueFalseComparison(bool, RejectedCmpop),
TwoStarredExpressions,
TypeComparison,
UndefinedExport(String),
UndefinedLocal(String),
UndefinedName(String),
UnusedImport(Vec<String>),
UnusedVariable(String),
YieldOutsideFunction,
// pycodestyle warnings
NoNewLineAtEndOfFile,
// flake8-builtin
BuiltinVariableShadowing(String),
BuiltinArgumentShadowing(String),
BuiltinAttributeShadowing(String),
// flakes8-comprehensions
UnnecessaryGeneratorList,
UnnecessaryGeneratorSet,
UnnecessaryGeneratorDict,
UnnecessaryListComprehensionSet,
UnnecessaryListComprehensionDict,
UnnecessaryLiteralSet(String),
UnnecessaryLiteralDict(String),
UnnecessaryCollectionCall(String),
// flake8-super
SuperCallWithParameters,
// flake8-print
PrintFound,
PPrintFound,
// pyupgrade
TypeOfPrimitive(Primitive),
UnnecessaryAbspath,
UselessMetaclassType,
NoAssertEquals,
UselessObjectInheritance(String),
UsePEP585Annotation(String),
UsePEP604Annotation,
// Meta
UnusedNOQA(Option<String>),
}
impl CheckCode {
pub fn as_str(&self) -> &str {
match self {
// pycodestyle
CheckCode::E402 => "E402",
CheckCode::E501 => "E501",
CheckCode::E711 => "E711",
CheckCode::E712 => "E712",
CheckCode::E713 => "E713",
CheckCode::E714 => "E714",
CheckCode::E721 => "E721",
CheckCode::E722 => "E722",
CheckCode::E731 => "E731",
CheckCode::E741 => "E741",
CheckCode::E742 => "E742",
CheckCode::E743 => "E743",
CheckCode::E902 => "E902",
CheckCode::E999 => "E999",
// pyflakes
CheckCode::F401 => "F401",
CheckCode::F402 => "F402",
CheckCode::F403 => "F403",
CheckCode::F404 => "F404",
CheckCode::F405 => "F405",
CheckCode::F406 => "F406",
CheckCode::F407 => "F407",
CheckCode::F541 => "F541",
CheckCode::F601 => "F601",
CheckCode::F602 => "F602",
CheckCode::F621 => "F621",
CheckCode::F622 => "F622",
CheckCode::F631 => "F631",
CheckCode::F632 => "F632",
CheckCode::F633 => "F633",
CheckCode::F634 => "F634",
CheckCode::F701 => "F701",
CheckCode::F702 => "F702",
CheckCode::F704 => "F704",
CheckCode::F706 => "F706",
CheckCode::F707 => "F707",
CheckCode::F722 => "F722",
CheckCode::F821 => "F821",
CheckCode::F822 => "F822",
CheckCode::F823 => "F823",
CheckCode::F831 => "F831",
CheckCode::F841 => "F841",
CheckCode::F901 => "F901",
// flake8-builtins
CheckCode::A001 => "A001",
CheckCode::A002 => "A002",
CheckCode::A003 => "A003",
// flake8-super
CheckCode::SPR001 => "SPR001",
// Refactor
CheckCode::R001 => "R001",
CheckCode::R002 => "R002",
// Meta
CheckCode::M001 => "M001",
}
}
/// The source for the check (either the AST, the filesystem, or the physical lines).
pub fn lint_source(&self) -> &'static LintSource {
match self {
@@ -313,7 +249,7 @@ impl CheckCode {
/// A placeholder representation of the CheckKind for the check.
pub fn kind(&self) -> CheckKind {
match self {
// pycodestyle
// pycodestyle errors
CheckCode::E402 => CheckKind::ModuleImportNotAtTopOfFile,
CheckCode::E501 => CheckKind::LineTooLong(89, 88),
CheckCode::E711 => CheckKind::NoneComparison(RejectedCmpop::Eq),
@@ -326,20 +262,24 @@ impl CheckCode {
CheckCode::E741 => CheckKind::AmbiguousVariableName("...".to_string()),
CheckCode::E742 => CheckKind::AmbiguousClassName("...".to_string()),
CheckCode::E743 => CheckKind::AmbiguousFunctionName("...".to_string()),
CheckCode::E902 => CheckKind::IOError("...".to_string()),
CheckCode::E999 => CheckKind::SyntaxError("...".to_string()),
CheckCode::E902 => CheckKind::IOError("IOError: `...`".to_string()),
CheckCode::E999 => CheckKind::SyntaxError("`...`".to_string()),
// pycodestyle warnings
CheckCode::W292 => CheckKind::NoNewLineAtEndOfFile,
// pyflakes
CheckCode::F401 => CheckKind::UnusedImport("...".to_string()),
CheckCode::F401 => CheckKind::UnusedImport(vec!["...".to_string()]),
CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1),
CheckCode::F403 => CheckKind::ImportStarUsed("...".to_string()),
CheckCode::F404 => CheckKind::LateFutureImport,
CheckCode::F405 => CheckKind::ImportStarUsage("...".to_string(), "...".to_string()),
CheckCode::F405 => {
CheckKind::ImportStarUsage("...".to_string(), vec!["...".to_string()])
}
CheckCode::F406 => CheckKind::ImportStarNotPermitted("...".to_string()),
CheckCode::F407 => CheckKind::FutureFeatureNotDefined("...".to_string()),
CheckCode::F541 => CheckKind::FStringMissingPlaceholders,
CheckCode::F601 => CheckKind::MultiValueRepeatedKeyLiteral,
CheckCode::F602 => CheckKind::MultiValueRepeatedKeyVariable("...".to_string()),
CheckCode::F621 => CheckKind::TooManyExpressionsInStarredAssignment,
CheckCode::F621 => CheckKind::ExpressionsInStarAssignment,
CheckCode::F622 => CheckKind::TwoStarredExpressions,
CheckCode::F631 => CheckKind::AssertTuple,
CheckCode::F632 => CheckKind::IsLiteral,
@@ -361,148 +301,41 @@ impl CheckCode {
CheckCode::A001 => CheckKind::BuiltinVariableShadowing("...".to_string()),
CheckCode::A002 => CheckKind::BuiltinArgumentShadowing("...".to_string()),
CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()),
// flake8-comprehensions
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
CheckCode::C401 => CheckKind::UnnecessaryGeneratorSet,
CheckCode::C402 => CheckKind::UnnecessaryGeneratorDict,
CheckCode::C403 => CheckKind::UnnecessaryListComprehensionSet,
CheckCode::C404 => CheckKind::UnnecessaryListComprehensionDict,
CheckCode::C405 => CheckKind::UnnecessaryLiteralSet("<list/tuple>".to_string()),
CheckCode::C406 => CheckKind::UnnecessaryLiteralDict("<list/tuple>".to_string()),
CheckCode::C408 => {
CheckKind::UnnecessaryCollectionCall("<dict/list/tuple>".to_string())
}
// flake8-super
CheckCode::SPR001 => CheckKind::SuperCallWithParameters,
// Refactor
CheckCode::R001 => CheckKind::UselessObjectInheritance("...".to_string()),
CheckCode::R002 => CheckKind::NoAssertEquals,
// flake8-print
CheckCode::T201 => CheckKind::PrintFound,
CheckCode::T203 => CheckKind::PPrintFound,
// pyupgrade
CheckCode::U001 => CheckKind::UselessMetaclassType,
CheckCode::U002 => CheckKind::UnnecessaryAbspath,
CheckCode::U003 => CheckKind::TypeOfPrimitive(Primitive::Str),
CheckCode::U004 => CheckKind::UselessObjectInheritance("...".to_string()),
CheckCode::U005 => CheckKind::NoAssertEquals,
CheckCode::U006 => CheckKind::UsePEP585Annotation("List".to_string()),
CheckCode::U007 => CheckKind::UsePEP604Annotation,
// Meta
CheckCode::M001 => CheckKind::UnusedNOQA(None),
}
}
}
#[allow(clippy::upper_case_acronyms)]
pub enum LintSource {
AST,
Lines,
FileSystem,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum RejectedCmpop {
Eq,
NotEq,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CheckKind {
UnusedNOQA(Option<String>),
AmbiguousClassName(String),
AmbiguousFunctionName(String),
AmbiguousVariableName(String),
AssertTuple,
BreakOutsideLoop,
ContinueOutsideLoop,
DefaultExceptNotLast,
DoNotAssignLambda,
DoNotUseBareExcept,
DuplicateArgumentName,
ForwardAnnotationSyntaxError(String),
FStringMissingPlaceholders,
FutureFeatureNotDefined(String),
IOError(String),
IfTuple,
ImportShadowedByLoopVar(String, usize),
ImportStarNotPermitted(String),
ImportStarUsage(String, String),
ImportStarUsed(String),
InvalidPrintSyntax,
IsLiteral,
LateFutureImport,
LineTooLong(usize, usize),
ModuleImportNotAtTopOfFile,
MultiValueRepeatedKeyLiteral,
MultiValueRepeatedKeyVariable(String),
NoAssertEquals,
NoneComparison(RejectedCmpop),
NotInTest,
NotIsTest,
RaiseNotImplemented,
ReturnOutsideFunction,
SyntaxError(String),
TooManyExpressionsInStarredAssignment,
TrueFalseComparison(bool, RejectedCmpop),
TwoStarredExpressions,
TypeComparison,
UndefinedExport(String),
UndefinedLocal(String),
UndefinedName(String),
UnusedImport(String),
UnusedVariable(String),
UselessObjectInheritance(String),
YieldOutsideFunction,
// flake8-builtin
BuiltinVariableShadowing(String),
BuiltinArgumentShadowing(String),
BuiltinAttributeShadowing(String),
// flake8-super
SuperCallWithParameters,
}
impl CheckKind {
/// The name of the check.
pub fn name(&self) -> &'static str {
match self {
CheckKind::AmbiguousClassName(_) => "AmbiguousClassName",
CheckKind::AmbiguousFunctionName(_) => "AmbiguousFunctionName",
CheckKind::AmbiguousVariableName(_) => "AmbiguousVariableName",
CheckKind::AssertTuple => "AssertTuple",
CheckKind::BreakOutsideLoop => "BreakOutsideLoop",
CheckKind::ContinueOutsideLoop => "ContinueOutsideLoop",
CheckKind::DefaultExceptNotLast => "DefaultExceptNotLast",
CheckKind::DoNotAssignLambda => "DoNotAssignLambda",
CheckKind::DoNotUseBareExcept => "DoNotUseBareExcept",
CheckKind::DuplicateArgumentName => "DuplicateArgumentName",
CheckKind::FStringMissingPlaceholders => "FStringMissingPlaceholders",
CheckKind::ForwardAnnotationSyntaxError(_) => "ForwardAnnotationSyntaxError",
CheckKind::FutureFeatureNotDefined(_) => "FutureFeatureNotDefined",
CheckKind::IOError(_) => "IOError",
CheckKind::IfTuple => "IfTuple",
CheckKind::ImportShadowedByLoopVar(_, _) => "ImportShadowedByLoopVar",
CheckKind::ImportStarNotPermitted(_) => "ImportStarNotPermitted",
CheckKind::ImportStarUsage(_, _) => "ImportStarUsage",
CheckKind::ImportStarUsed(_) => "ImportStarUsed",
CheckKind::InvalidPrintSyntax => "InvalidPrintSyntax",
CheckKind::IsLiteral => "IsLiteral",
CheckKind::LateFutureImport => "LateFutureImport",
CheckKind::LineTooLong(_, _) => "LineTooLong",
CheckKind::ModuleImportNotAtTopOfFile => "ModuleImportNotAtTopOfFile",
CheckKind::MultiValueRepeatedKeyLiteral => "MultiValueRepeatedKeyLiteral",
CheckKind::MultiValueRepeatedKeyVariable(_) => "MultiValueRepeatedKeyVariable",
CheckKind::NoAssertEquals => "NoAssertEquals",
CheckKind::NoneComparison(_) => "NoneComparison",
CheckKind::NotInTest => "NotInTest",
CheckKind::NotIsTest => "NotIsTest",
CheckKind::RaiseNotImplemented => "RaiseNotImplemented",
CheckKind::ReturnOutsideFunction => "ReturnOutsideFunction",
CheckKind::SyntaxError(_) => "SyntaxError",
CheckKind::TooManyExpressionsInStarredAssignment => {
"TooManyExpressionsInStarredAssignment"
}
CheckKind::TrueFalseComparison(_, _) => "TrueFalseComparison",
CheckKind::TwoStarredExpressions => "TwoStarredExpressions",
CheckKind::TypeComparison => "TypeComparison",
CheckKind::UndefinedExport(_) => "UndefinedExport",
CheckKind::UndefinedLocal(_) => "UndefinedLocal",
CheckKind::UndefinedName(_) => "UndefinedName",
CheckKind::UnusedImport(_) => "UnusedImport",
CheckKind::UnusedVariable(_) => "UnusedVariable",
CheckKind::UselessObjectInheritance(_) => "UselessObjectInheritance",
CheckKind::YieldOutsideFunction => "YieldOutsideFunction",
CheckKind::UnusedNOQA(_) => "UnusedNOQA",
// flake8-builtins
CheckKind::BuiltinVariableShadowing(_) => "BuiltinVariableShadowing",
CheckKind::BuiltinArgumentShadowing(_) => "BuiltinArgumentShadowing",
CheckKind::BuiltinAttributeShadowing(_) => "BuiltinAttributeShadowing",
// flake8-super
CheckKind::SuperCallWithParameters => "SuperCallWithParameters",
}
}
/// A four-letter shorthand code for the check.
pub fn code(&self) -> &'static CheckCode {
match self {
// pycodestyle errors
CheckKind::AmbiguousClassName(_) => &CheckCode::E742,
CheckKind::AmbiguousFunctionName(_) => &CheckCode::E743,
CheckKind::AmbiguousVariableName(_) => &CheckCode::E741,
@@ -529,14 +362,13 @@ impl CheckKind {
CheckKind::ModuleImportNotAtTopOfFile => &CheckCode::E402,
CheckKind::MultiValueRepeatedKeyLiteral => &CheckCode::F601,
CheckKind::MultiValueRepeatedKeyVariable(_) => &CheckCode::F602,
CheckKind::NoAssertEquals => &CheckCode::R002,
CheckKind::NoneComparison(_) => &CheckCode::E711,
CheckKind::NotInTest => &CheckCode::E713,
CheckKind::NotIsTest => &CheckCode::E714,
CheckKind::RaiseNotImplemented => &CheckCode::F901,
CheckKind::ReturnOutsideFunction => &CheckCode::F706,
CheckKind::SyntaxError(_) => &CheckCode::E999,
CheckKind::TooManyExpressionsInStarredAssignment => &CheckCode::F621,
CheckKind::ExpressionsInStarAssignment => &CheckCode::F621,
CheckKind::TrueFalseComparison(_, _) => &CheckCode::E712,
CheckKind::TwoStarredExpressions => &CheckCode::F622,
CheckKind::TypeComparison => &CheckCode::E721,
@@ -544,30 +376,53 @@ impl CheckKind {
CheckKind::UndefinedLocal(_) => &CheckCode::F823,
CheckKind::UndefinedName(_) => &CheckCode::F821,
CheckKind::UnusedImport(_) => &CheckCode::F401,
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
CheckKind::UnusedVariable(_) => &CheckCode::F841,
CheckKind::UselessObjectInheritance(_) => &CheckCode::R001,
CheckKind::YieldOutsideFunction => &CheckCode::F704,
// pycodestyle warnings
CheckKind::NoNewLineAtEndOfFile => &CheckCode::W292,
// flake8-builtins
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
// flake8-comprehensions
CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
CheckKind::UnnecessaryGeneratorSet => &CheckCode::C401,
CheckKind::UnnecessaryGeneratorDict => &CheckCode::C402,
CheckKind::UnnecessaryListComprehensionSet => &CheckCode::C403,
CheckKind::UnnecessaryListComprehensionDict => &CheckCode::C404,
CheckKind::UnnecessaryLiteralSet(_) => &CheckCode::C405,
CheckKind::UnnecessaryLiteralDict(_) => &CheckCode::C406,
CheckKind::UnnecessaryCollectionCall(_) => &CheckCode::C408,
// flake8-super
CheckKind::SuperCallWithParameters => &CheckCode::SPR001,
// flake8-print
CheckKind::PrintFound => &CheckCode::T201,
CheckKind::PPrintFound => &CheckCode::T203,
// pyupgrade
CheckKind::TypeOfPrimitive(_) => &CheckCode::U003,
CheckKind::UnnecessaryAbspath => &CheckCode::U002,
CheckKind::UselessMetaclassType => &CheckCode::U001,
CheckKind::NoAssertEquals => &CheckCode::U005,
CheckKind::UsePEP585Annotation(_) => &CheckCode::U006,
CheckKind::UsePEP604Annotation => &CheckCode::U007,
CheckKind::UselessObjectInheritance(_) => &CheckCode::U004,
// Meta
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
}
}
/// The body text for the check.
pub fn body(&self) -> String {
match self {
// pycodestyle errors
CheckKind::AmbiguousClassName(name) => {
format!("ambiguous class name '{}'", name)
format!("Ambiguous class name: `{}`", name)
}
CheckKind::AmbiguousFunctionName(name) => {
format!("ambiguous function name '{}'", name)
format!("Ambiguous function name: `{}`", name)
}
CheckKind::AmbiguousVariableName(name) => {
format!("ambiguous variable name '{}'", name)
format!("Ambiguous variable name: `{}`", name)
}
CheckKind::AssertTuple => {
"Assert test is a non-empty tuple, which is always `True`".to_string()
@@ -575,7 +430,7 @@ impl CheckKind {
CheckKind::BreakOutsideLoop => "`break` outside loop".to_string(),
CheckKind::ContinueOutsideLoop => "`continue` not properly in loop".to_string(),
CheckKind::DefaultExceptNotLast => {
"an `except:` block as not the last exception handler".to_string()
"An `except:` block as not the last exception handler".to_string()
}
CheckKind::DoNotAssignLambda => {
"Do not assign a lambda expression, use a def".to_string()
@@ -585,19 +440,21 @@ impl CheckKind {
"Duplicate argument name in function definition".to_string()
}
CheckKind::ForwardAnnotationSyntaxError(body) => {
format!("syntax error in forward annotation '{body}'")
format!("Syntax error in forward annotation: `{body}`")
}
CheckKind::FStringMissingPlaceholders => {
"f-string without any placeholders".to_string()
}
CheckKind::FutureFeatureNotDefined(name) => {
format!("future feature '{name}' is not defined")
format!("Future feature `{name}` is not defined")
}
CheckKind::IOError(message) => message.clone(),
CheckKind::IfTuple => "If test is a tuple, which is always `True`".to_string(),
CheckKind::InvalidPrintSyntax => "use of >> is invalid with print function".to_string(),
CheckKind::InvalidPrintSyntax => {
"Use of `>>` is invalid with `print` function".to_string()
}
CheckKind::ImportShadowedByLoopVar(name, line) => {
format!("import '{name}' from line {line} shadowed by loop variable")
format!("Import `{name}` from line {line} shadowed by loop variable")
}
CheckKind::ImportStarNotPermitted(name) => {
format!("`from {name} import *` only allowed at module level")
@@ -606,11 +463,15 @@ impl CheckKind {
format!("`from {name} import *` used; unable to detect undefined names")
}
CheckKind::ImportStarUsage(name, sources) => {
format!("'{name}' may be undefined, or defined from star imports: {sources}")
let sources = sources
.iter()
.map(|source| format!("`{}`", source))
.join(", ");
format!("`{name}` may be undefined, or defined from star imports: {sources}")
}
CheckKind::IsLiteral => "use ==/!= to compare constant literals".to_string(),
CheckKind::IsLiteral => "Use `==` and `!=` to compare constant literals".to_string(),
CheckKind::LateFutureImport => {
"from __future__ imports must occur at the beginning of the file".to_string()
"`from __future__` imports must occur at the beginning of the file".to_string()
}
CheckKind::LineTooLong(length, limit) => {
format!("Line too long ({length} > {limit} characters)")
@@ -624,9 +485,6 @@ impl CheckKind {
CheckKind::MultiValueRepeatedKeyVariable(name) => {
format!("Dictionary key `{name}` repeated")
}
CheckKind::NoAssertEquals => {
"`assertEquals` is deprecated, use `assertEqual` instead".to_string()
}
CheckKind::NoneComparison(op) => match op {
RejectedCmpop::Eq => "Comparison to `None` should be `cond is None`".to_string(),
RejectedCmpop::NotEq => {
@@ -639,11 +497,11 @@ impl CheckKind {
"`raise NotImplemented` should be `raise NotImplementedError`".to_string()
}
CheckKind::ReturnOutsideFunction => {
"a `return` statement outside of a function/method".to_string()
"`return` statement outside of a function/method".to_string()
}
CheckKind::SyntaxError(message) => format!("SyntaxError: {message}"),
CheckKind::TooManyExpressionsInStarredAssignment => {
"too many expressions in star-unpacking assignment".to_string()
CheckKind::ExpressionsInStarAssignment => {
"Too many expressions in star-unpacking assignment".to_string()
}
CheckKind::TrueFalseComparison(value, op) => match *value {
true => match op {
@@ -663,8 +521,8 @@ impl CheckKind {
}
},
},
CheckKind::TwoStarredExpressions => "two starred expressions in assignment".to_string(),
CheckKind::TypeComparison => "do not compare types, use `isinstance()`".to_string(),
CheckKind::TwoStarredExpressions => "Two starred expressions in assignment".to_string(),
CheckKind::TypeComparison => "Do not compare types, use `isinstance()`".to_string(),
CheckKind::UndefinedExport(name) => {
format!("Undefined name `{name}` in `__all__`")
}
@@ -674,20 +532,18 @@ impl CheckKind {
CheckKind::UndefinedName(name) => {
format!("Undefined name `{name}`")
}
CheckKind::UnusedImport(name) => format!("`{name}` imported but unused"),
CheckKind::UnusedImport(names) => {
let names = names.iter().map(|name| format!("`{name}`")).join(", ");
format!("{names} imported but unused")
}
CheckKind::UnusedVariable(name) => {
format!("Local variable `{name}` is assigned to but never used")
}
CheckKind::UselessObjectInheritance(name) => {
format!("Class `{name}` inherits from object")
}
CheckKind::YieldOutsideFunction => {
"a `yield` or `yield from` statement outside of a function/method".to_string()
"`yield` or `yield from` statement outside of a function/method".to_string()
}
CheckKind::UnusedNOQA(code) => match code {
None => "Unused `noqa` directive".to_string(),
Some(code) => format!("Unused `noqa` directive for: {code}"),
},
// pycodestyle warnings
CheckKind::NoNewLineAtEndOfFile => "No newline at end of file".to_string(),
// flake8-builtins
CheckKind::BuiltinVariableShadowing(name) => {
format!("Variable `{name}` is shadowing a python builtin")
@@ -696,12 +552,67 @@ impl CheckKind {
format!("Argument `{name}` is shadowing a python builtin")
}
CheckKind::BuiltinAttributeShadowing(name) => {
format!("class attribute `{name}` is shadowing a python builtin")
format!("Class attribute `{name}` is shadowing a python builtin")
}
// flake8-comprehensions
CheckKind::UnnecessaryGeneratorList => {
"Unnecessary generator - rewrite as a list comprehension".to_string()
}
CheckKind::UnnecessaryGeneratorSet => {
"Unnecessary generator - rewrite as a set comprehension".to_string()
}
CheckKind::UnnecessaryGeneratorDict => {
"Unnecessary generator - rewrite as a dict comprehension".to_string()
}
CheckKind::UnnecessaryListComprehensionSet => {
"Unnecessary list comprehension - rewrite as a set comprehension".to_string()
}
CheckKind::UnnecessaryListComprehensionDict => {
"Unnecessary list comprehension - rewrite as a dict comprehension".to_string()
}
CheckKind::UnnecessaryLiteralSet(obj_type) => {
format!("Unnecessary {obj_type} literal - rewrite as a set literal")
}
CheckKind::UnnecessaryLiteralDict(obj_type) => {
format!("Unnecessary {obj_type} literal - rewrite as a dict literal")
}
CheckKind::UnnecessaryCollectionCall(obj_type) => {
format!("Unnecessary {obj_type} call - rewrite as a literal")
}
// flake8-super
CheckKind::SuperCallWithParameters => {
"Use `super()` instead of `super(__class__, self)`".to_string()
}
// flake8-print
CheckKind::PrintFound => "`print` found".to_string(),
CheckKind::PPrintFound => "`pprint` found".to_string(),
// pyupgrade
CheckKind::TypeOfPrimitive(primitive) => {
format!("Use `{}` instead of `type(...)`", primitive.builtin())
}
CheckKind::UnnecessaryAbspath => {
"`abspath(__file__)` is unnecessary in Python 3.9 and later".to_string()
}
CheckKind::UselessMetaclassType => "`__metaclass__ = type` is implied".to_string(),
CheckKind::NoAssertEquals => {
"`assertEquals` is deprecated, use `assertEqual` instead".to_string()
}
CheckKind::UselessObjectInheritance(name) => {
format!("Class `{name}` inherits from object")
}
CheckKind::UsePEP585Annotation(name) => {
format!(
"Use `{}` instead of `{}` for type annotations",
name.to_lowercase(),
name,
)
}
CheckKind::UsePEP604Annotation => "Use `X | Y` for type annotations".to_string(),
// Meta
CheckKind::UnusedNOQA(code) => match code {
None => "Unused `noqa` directive".to_string(),
Some(code) => format!("Unused `noqa` directive for: {code}"),
},
}
}
@@ -710,9 +621,17 @@ impl CheckKind {
matches!(
self,
CheckKind::NoAssertEquals
| CheckKind::UselessObjectInheritance(_)
| CheckKind::UnusedNOQA(_)
| CheckKind::PPrintFound
| CheckKind::PrintFound
| CheckKind::SuperCallWithParameters
| CheckKind::TypeOfPrimitive(_)
| CheckKind::UnnecessaryAbspath
| CheckKind::UnusedImport(_)
| CheckKind::UnusedNOQA(_)
| CheckKind::UselessMetaclassType
| CheckKind::UselessObjectInheritance(_)
| CheckKind::UsePEP585Annotation(_)
| CheckKind::UsePEP604Annotation
)
}
}
@@ -747,3 +666,25 @@ impl Check {
self.fix = Some(fix);
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use anyhow::Result;
use strum::IntoEnumIterator;
use crate::checks::CheckCode;
#[test]
fn check_code_serialization() -> Result<()> {
for check_code in CheckCode::iter() {
assert!(
CheckCode::from_str(check_code.as_ref()).is_ok(),
"{:?} could not be round-trip serialized.",
check_code
);
}
Ok(())
}
}

136
src/cli.rs Normal file
View File

@@ -0,0 +1,136 @@
use std::fmt;
use std::path::PathBuf;
use clap::{command, Parser};
use log::warn;
use regex::Regex;
use crate::checks::CheckCode;
use crate::printer::SerializationFormat;
use crate::pyproject::StrCheckCodePair;
use crate::settings::PythonVersion;
use crate::RawSettings;
#[derive(Debug, Parser)]
#[command(author, about = "ruff: An extremely fast Python linter.")]
#[command(version)]
pub struct Cli {
#[arg(required = true)]
pub files: Vec<PathBuf>,
/// Enable verbose logging.
#[arg(short, long)]
pub verbose: bool,
/// Disable all logging (but still exit with status code "1" upon detecting errors).
#[arg(short, long)]
pub quiet: bool,
/// Exit with status code "0", even upon detecting errors.
#[arg(short, long)]
pub exit_zero: bool,
/// Run in watch mode by re-running whenever files change.
#[arg(short, long)]
pub watch: bool,
/// Attempt to automatically fix lint errors.
#[arg(short, long)]
pub fix: bool,
/// Disable cache reads.
#[arg(short, long)]
pub no_cache: bool,
/// List of error codes to enable.
#[arg(long, value_delimiter = ',')]
pub select: Vec<CheckCode>,
/// Like --select, but adds additional error codes on top of the selected ones.
#[arg(long, value_delimiter = ',')]
pub extend_select: Vec<CheckCode>,
/// List of error codes to ignore.
#[arg(long, value_delimiter = ',')]
pub ignore: Vec<CheckCode>,
/// Like --ignore, but adds additional error codes on top of the ignored ones.
#[arg(long, value_delimiter = ',')]
pub extend_ignore: Vec<CheckCode>,
/// List of paths, used to exclude files and/or directories from checks.
#[arg(long, value_delimiter = ',')]
pub exclude: Vec<String>,
/// Like --exclude, but adds additional files and directories on top of the excluded ones.
#[arg(long, value_delimiter = ',')]
pub extend_exclude: Vec<String>,
/// List of mappings from file pattern to code to exclude
#[arg(long, value_delimiter = ',')]
pub per_file_ignores: Vec<StrCheckCodePair>,
/// Output serialization format for error messages.
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
pub format: SerializationFormat,
/// See the files ruff will be run against with the current settings.
#[arg(long)]
pub show_files: bool,
/// See ruff's settings.
#[arg(long)]
pub show_settings: bool,
/// Enable automatic additions of noqa directives to failing lines.
#[arg(long)]
pub add_noqa: bool,
/// Regular expression matching the name of dummy variables.
#[arg(long)]
pub dummy_variable_rgx: Option<Regex>,
/// The minimum Python version that should be supported.
#[arg(long)]
pub target_version: Option<PythonVersion>,
/// Round-trip auto-formatting.
// TODO(charlie): This should be a sub-command.
#[arg(long, hide = true)]
pub autoformat: bool,
}
pub enum Warnable {
Select,
ExtendSelect,
}
impl fmt::Display for Warnable {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Warnable::Select => fmt.write_str("--select"),
Warnable::ExtendSelect => fmt.write_str("--extend-select"),
}
}
}
/// Warn the user if they attempt to enable a code that won't be respected.
pub fn warn_on(
flag: Warnable,
codes: &Vec<CheckCode>,
cli_ignore: &Vec<CheckCode>,
cli_extend_ignore: &Vec<CheckCode>,
pyproject_settings: &RawSettings,
pyproject_path: &Option<PathBuf>,
) {
for code in codes {
if !cli_ignore.is_empty() {
if cli_ignore.contains(code) {
warn!("{code:?} was passed to {flag}, but ignored via --ignore")
}
} else if pyproject_settings.ignore.contains(code) {
if let Some(path) = pyproject_path {
warn!(
"{code:?} was passed to {flag}, but ignored by the `ignore` field in {}",
path.to_string_lossy()
)
} else {
warn!("{code:?} was passed to {flag}, but ignored by the default `ignore` field",)
}
}
if !cli_extend_ignore.is_empty() {
if cli_extend_ignore.contains(code) {
warn!("{code:?} was passed to {flag}, but ignored via --extend-ignore")
}
} else if pyproject_settings.extend_ignore.contains(code) {
if let Some(path) = pyproject_path {
warn!(
"{code:?} was passed to {flag}, but ignored by the `extend_ignore` field in {}",
path.to_string_lossy()
)
} else {
warn!("{code:?} was passed to {flag}, but ignored by the default `extend_ignore` field")
}
}
}
}

1039
src/code_gen.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ use rustpython_parser::lexer::LexResult;
use crate::autofix::fixer::Mode;
use crate::linter::{check_path, tokenize};
use crate::message::Message;
use crate::settings::Settings;
use crate::settings::{RawSettings, Settings};
mod ast;
mod autofix;
@@ -15,11 +15,14 @@ pub mod cache;
pub mod check_ast;
mod check_lines;
pub mod checks;
pub mod cli;
pub mod code_gen;
pub mod fs;
pub mod linter;
pub mod logging;
pub mod message;
mod noqa;
mod plugins;
pub mod printer;
pub mod pyproject;
mod python;
@@ -39,7 +42,7 @@ pub fn check(path: &Path, contents: &str) -> Result<Vec<Message>> {
None => debug!("Unable to find pyproject.toml; using default settings..."),
};
let settings = Settings::from_pyproject(pyproject, project_root)?;
let settings = Settings::from_raw(RawSettings::from_pyproject(&pyproject, &project_root)?);
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(contents);

View File

@@ -1,3 +1,4 @@
use std::fs::write;
use std::path::Path;
use anyhow::Result;
@@ -11,6 +12,7 @@ use crate::autofix::fixer::fix_file;
use crate::check_ast::check_ast;
use crate::check_lines::check_lines;
use crate::checks::{Check, CheckCode, CheckKind, LintSource};
use crate::code_gen::SourceGenerator;
use crate::message::Message;
use crate::noqa::add_noqa;
use crate::settings::Settings;
@@ -42,7 +44,7 @@ pub(crate) fn check_path(
// Run the AST-based checks.
if settings
.select
.enabled
.iter()
.any(|check_code| matches!(check_code.lint_source(), LintSource::AST))
{
@@ -51,7 +53,7 @@ pub(crate) fn check_path(
checks.extend(check_ast(&python_ast, contents, settings, autofix, path))
}
Err(parse_error) => {
if settings.select.contains(&CheckCode::E999) {
if settings.enabled.contains(&CheckCode::E999) {
checks.push(Check::new(
CheckKind::SyntaxError(parse_error.error.to_string()),
Range {
@@ -151,6 +153,22 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
add_noqa(&checks, &contents, &noqa_line_for, path)
}
pub fn autoformat_path(path: &Path) -> Result<()> {
// Read the file from disk.
let contents = fs::read_file(path)?;
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(&contents);
// Generate the AST.
let python_ast = parser::parse_program_tokens(tokens, "<filename>")?;
let mut generator: SourceGenerator = Default::default();
generator.unparse_suite(&python_ast)?;
write(path, generator.generate()?)?;
Ok(())
}
#[cfg(test)]
mod tests {
use std::path::Path;
@@ -321,6 +339,42 @@ mod tests {
Ok(())
}
#[test]
fn w292_0() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/W292_0.py"),
&settings::Settings::for_rule(CheckCode::W292),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn w292_1() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/W292_1.py"),
&settings::Settings::for_rule(CheckCode::W292),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn w292_2() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/W292_2.py"),
&settings::Settings::for_rule(CheckCode::W292),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn f401() -> Result<()> {
let mut checks = check_path(
@@ -672,30 +726,6 @@ mod tests {
Ok(())
}
#[test]
fn r001() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/R001.py"),
&settings::Settings::for_rule(CheckCode::R001),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn r002() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/R002.py"),
&settings::Settings::for_rule(CheckCode::R002),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn init() -> Result<()> {
let mut checks = check_path(
@@ -768,6 +798,102 @@ mod tests {
Ok(())
}
#[test]
fn c400() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/C400.py"),
&settings::Settings::for_rule(CheckCode::C400),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn c401() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/C401.py"),
&settings::Settings::for_rule(CheckCode::C401),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn c402() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/C402.py"),
&settings::Settings::for_rule(CheckCode::C402),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn c403() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/C403.py"),
&settings::Settings::for_rule(CheckCode::C403),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn c404() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/C404.py"),
&settings::Settings::for_rule(CheckCode::C404),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn c405() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/C405.py"),
&settings::Settings::for_rule(CheckCode::C405),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn c406() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/C406.py"),
&settings::Settings::for_rule(CheckCode::C406),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn c408() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/C408.py"),
&settings::Settings::for_rule(CheckCode::C408),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn spr001() -> Result<()> {
let mut checks = check_path(
@@ -779,4 +905,112 @@ mod tests {
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn t201() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/T201.py"),
&settings::Settings::for_rule(CheckCode::T201),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn t203() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/T203.py"),
&settings::Settings::for_rule(CheckCode::T203),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn u001() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U001.py"),
&settings::Settings::for_rule(CheckCode::U001),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn u002() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U002.py"),
&settings::Settings::for_rule(CheckCode::U002),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn u003() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U003.py"),
&settings::Settings::for_rule(CheckCode::U003),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn u004() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U004.py"),
&settings::Settings::for_rule(CheckCode::U004),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn u005() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U005.py"),
&settings::Settings::for_rule(CheckCode::U005),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn u006() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U006.py"),
&settings::Settings::for_rule(CheckCode::U006),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn u007() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U007.py"),
&settings::Settings::for_rule(CheckCode::U007),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
}

View File

@@ -5,93 +5,33 @@ use std::sync::mpsc::channel;
use std::time::Instant;
use anyhow::Result;
use clap::{command, Parser};
use clap::Parser;
use colored::Colorize;
use log::{debug, error};
use notify::{raw_watcher, RecursiveMode, Watcher};
use rayon::prelude::*;
use regex::Regex;
use walkdir::DirEntry;
use ::ruff::cache;
use ::ruff::checks::CheckCode;
use ::ruff::checks::CheckKind;
use ::ruff::fs::iter_python_files;
use ::ruff::linter::add_noqa_to_path;
use ::ruff::linter::lint_path;
use ::ruff::logging::set_up_logging;
use ::ruff::message::Message;
use ::ruff::printer::{Printer, SerializationFormat};
use ::ruff::pyproject::{self, StrCheckCodePair};
use ::ruff::settings::CurrentSettings;
use ::ruff::settings::{FilePattern, PerFileIgnore, Settings};
use ::ruff::tell_user;
use ruff::cache;
use ruff::checks::CheckCode;
use ruff::checks::CheckKind;
use ruff::cli::{warn_on, Cli, Warnable};
use ruff::fs::iter_python_files;
use ruff::linter::add_noqa_to_path;
use ruff::linter::autoformat_path;
use ruff::linter::lint_path;
use ruff::logging::set_up_logging;
use ruff::message::Message;
use ruff::printer::{Printer, SerializationFormat};
use ruff::pyproject::{self};
use ruff::settings::CurrentSettings;
use ruff::settings::RawSettings;
use ruff::settings::{FilePattern, PerFileIgnore, Settings};
use ruff::tell_user;
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Debug, Parser)]
#[command(author, about = "ruff: An extremely fast Python linter.")]
#[command(version)]
struct Cli {
#[arg(required = true)]
files: Vec<PathBuf>,
/// Enable verbose logging.
#[arg(short, long)]
verbose: bool,
/// Disable all logging (but still exit with status code "1" upon detecting errors).
#[arg(short, long)]
quiet: bool,
/// Exit with status code "0", even upon detecting errors.
#[arg(short, long)]
exit_zero: bool,
/// Run in watch mode by re-running whenever files change.
#[arg(short, long)]
watch: bool,
/// Attempt to automatically fix lint errors.
#[arg(short, long)]
fix: bool,
/// Disable cache reads.
#[arg(short, long)]
no_cache: bool,
/// List of error codes to enable.
#[arg(long, value_delimiter = ',')]
select: Vec<CheckCode>,
/// Like --select, but adds additional error codes on top of the selected ones.
#[arg(long, value_delimiter = ',')]
extend_select: Vec<CheckCode>,
/// List of error codes to ignore.
#[arg(long, value_delimiter = ',')]
ignore: Vec<CheckCode>,
/// Like --ignore, but adds additional error codes on top of the ignored ones.
#[arg(long, value_delimiter = ',')]
extend_ignore: Vec<CheckCode>,
/// List of paths, used to exclude files and/or directories from checks.
#[arg(long, value_delimiter = ',')]
exclude: Vec<String>,
/// Like --exclude, but adds additional files and directories on top of the excluded ones.
#[arg(long, value_delimiter = ',')]
extend_exclude: Vec<String>,
/// List of mappings from file pattern to code to exclude
#[arg(long, value_delimiter = ',')]
per_file_ignores: Vec<StrCheckCodePair>,
/// Output serialization format for error messages.
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
format: SerializationFormat,
/// See the files ruff will be run against with the current settings.
#[arg(long)]
show_files: bool,
/// See ruff's settings.
#[arg(long)]
show_settings: bool,
/// Enable automatic additions of noqa directives to failing lines.
#[arg(long)]
add_noqa: bool,
/// Regular expression matching the name of dummy variables.
#[arg(long)]
dummy_variable_rgx: Option<Regex>,
}
#[cfg(feature = "update-informer")]
fn check_for_updates() {
use update_informer::{registry, Check};
@@ -116,8 +56,11 @@ fn check_for_updates() {
}
}
fn show_settings(settings: Settings) {
println!("{:#?}", CurrentSettings::from_settings(settings));
fn show_settings(settings: RawSettings, project_root: Option<PathBuf>, pyproject: Option<PathBuf>) {
println!(
"{:#?}",
CurrentSettings::from_settings(settings, project_root, pyproject)
);
}
fn show_files(files: &[PathBuf], settings: &Settings) {
@@ -165,7 +108,7 @@ fn run_once(
}
.unwrap_or_else(|(path, message)| {
if let Some(path) = path {
if settings.select.contains(&CheckCode::E902) {
if settings.enabled.contains(&CheckCode::E902) {
vec![Message {
kind: CheckKind::IOError(message),
fixed: false,
@@ -222,6 +165,33 @@ fn add_noqa(files: &[PathBuf], settings: &Settings) -> Result<usize> {
Ok(modifications)
}
fn autoformat(files: &[PathBuf], settings: &Settings) -> Result<usize> {
// Collect all the files to format.
let start = Instant::now();
let paths: Vec<DirEntry> = files
.iter()
.flat_map(|path| iter_python_files(path, &settings.exclude, &settings.extend_exclude))
.flatten()
.collect();
let duration = start.elapsed();
debug!("Identified files to lint in: {:?}", duration);
let start = Instant::now();
let modifications = paths
.par_iter()
.map(|entry| {
let path = entry.path();
autoformat_path(path)
})
.flatten()
.count();
let duration = start.elapsed();
debug!("Auto-formatted files in: {:?}", duration);
Ok(modifications)
}
fn inner_main() -> Result<ExitCode> {
let cli = Cli::parse();
@@ -256,7 +226,7 @@ fn inner_main() -> Result<ExitCode> {
.map(|pair| PerFileIgnore::new(pair, &project_root))
.collect();
let mut settings = Settings::from_pyproject(pyproject, project_root)?;
let mut settings = RawSettings::from_pyproject(&pyproject, &project_root)?;
if !exclude.is_empty() {
settings.exclude = exclude;
}
@@ -267,17 +237,35 @@ fn inner_main() -> Result<ExitCode> {
settings.per_file_ignores = per_file_ignores;
}
if !cli.select.is_empty() {
settings.clear();
settings.select(cli.select);
warn_on(
Warnable::Select,
&cli.select,
&cli.ignore,
&cli.extend_ignore,
&settings,
&pyproject,
);
settings.select = cli.select;
}
if !cli.extend_select.is_empty() {
settings.select(cli.extend_select);
warn_on(
Warnable::ExtendSelect,
&cli.extend_select,
&cli.ignore,
&cli.extend_ignore,
&settings,
&pyproject,
);
settings.extend_select = cli.extend_select;
}
if !cli.ignore.is_empty() {
settings.ignore(&cli.ignore);
settings.ignore = cli.ignore;
}
if !cli.extend_ignore.is_empty() {
settings.ignore(&cli.extend_ignore);
settings.extend_ignore = cli.extend_ignore;
}
if let Some(target_version) = cli.target_version {
settings.target_version = target_version;
}
if let Some(dummy_variable_rgx) = cli.dummy_variable_rgx {
settings.dummy_variable_rgx = dummy_variable_rgx;
@@ -287,12 +275,15 @@ fn inner_main() -> Result<ExitCode> {
eprintln!("Error: specify --show-settings or show-files (not both).");
return Ok(ExitCode::FAILURE);
}
if cli.show_files {
show_files(&cli.files, &settings);
if cli.show_settings {
show_settings(settings, project_root, pyproject);
return Ok(ExitCode::SUCCESS);
}
if cli.show_settings {
show_settings(settings);
let settings = Settings::from_raw(settings);
if cli.show_files {
show_files(&cli.files, &settings);
return Ok(ExitCode::SUCCESS);
}
@@ -308,6 +299,10 @@ fn inner_main() -> Result<ExitCode> {
eprintln!("Warning: --no-qa is not enabled in watch mode.");
}
if cli.autoformat {
eprintln!("Warning: --autoformat is not enabled in watch mode.");
}
if cli.format != SerializationFormat::Text {
eprintln!("Warning: --format 'text' is used in watch mode.");
}
@@ -351,6 +346,11 @@ fn inner_main() -> Result<ExitCode> {
if modifications > 0 {
println!("Added {modifications} noqa directives.");
}
} else if cli.autoformat {
let modifications = autoformat(&cli.files, &settings)?;
if modifications > 0 {
println!("Formatted {modifications} files.");
}
} else {
let messages = run_once(&cli.files, &settings, !cli.no_cache, cli.fix)?;
if !cli.quiet {
@@ -360,7 +360,7 @@ fn inner_main() -> Result<ExitCode> {
#[cfg(feature = "update-informer")]
check_for_updates();
if !messages.is_empty() && !cli.exit_zero {
if messages.iter().any(|message| !message.fixed) && !cli.exit_zero {
return Ok(ExitCode::FAILURE);
}
}

View File

@@ -45,7 +45,7 @@ impl fmt::Display for Message {
":".cyan(),
self.location.column(),
":".cyan(),
self.kind.code().as_str().red().bold(),
self.kind.code().as_ref().red().bold(),
self.kind.body()
)
}

View File

@@ -47,10 +47,9 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
pub fn extract_noqa_line_for(lxr: &[LexResult]) -> Vec<usize> {
let mut noqa_line_for: Vec<usize> = vec![];
let mut last_is_string = false;
let mut last_seen = usize::MIN;
let mut min_line = usize::MAX;
let mut max_line = usize::MIN;
let mut in_string = false;
for (start, tok, end) in lxr.iter().flatten() {
if matches!(tok, Tok::EndOfFile) {
@@ -62,29 +61,20 @@ pub fn extract_noqa_line_for(lxr: &[LexResult]) -> Vec<usize> {
max_line = max(max_line, start.row());
// For now, we only care about preserving noqa directives across multi-line strings.
if last_is_string {
noqa_line_for.extend(vec![max_line; (max_line + 1) - min_line]);
} else {
for i in (min_line - 1)..(max_line) {
if in_string {
for i in (noqa_line_for.len())..(min_line - 1) {
noqa_line_for.push(i + 1);
}
noqa_line_for.extend(vec![max_line; (max_line + 1) - min_line]);
}
min_line = usize::MAX;
max_line = usize::MIN;
} else {
// Handle empty lines.
if start.row() > last_seen {
for i in last_seen..(start.row() - 1) {
noqa_line_for.push(i + 1);
}
}
min_line = min(min_line, start.row());
max_line = max(max_line, end.row());
}
last_seen = start.row();
last_is_string = matches!(tok, Tok::String { .. });
in_string = matches!(tok, Tok::String { .. });
}
noqa_line_for
@@ -114,9 +104,7 @@ fn add_noqa_inner(
.unwrap_or(lineno);
if !codes.is_empty() {
let matches = matches_by_line
.entry(noqa_lineno)
.or_insert_with(BTreeSet::new);
let matches = matches_by_line.entry(noqa_lineno).or_default();
matches.append(&mut codes);
}
}
@@ -137,7 +125,7 @@ fn add_noqa_inner(
Directive::All(start, _) => output.push_str(&line[..start]),
Directive::Codes(start, _, _) => output.push_str(&line[..start]),
};
let codes: Vec<&str> = codes.iter().map(|code| code.as_str()).collect();
let codes: Vec<&str> = codes.iter().map(|code| code.as_ref()).collect();
output.push_str(" # noqa: ");
output.push_str(&codes.join(", "));
output.push('\n');
@@ -173,6 +161,8 @@ mod tests {
#[test]
fn extraction() -> Result<()> {
let empty: Vec<usize> = Default::default();
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
y = 2
@@ -180,7 +170,7 @@ z = x + 1",
)
.collect();
println!("{:?}", extract_noqa_line_for(&lxr));
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3]);
assert_eq!(extract_noqa_line_for(&lxr), empty);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"
@@ -190,7 +180,7 @@ z = x + 1",
)
.collect();
println!("{:?}", extract_noqa_line_for(&lxr));
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3, 4]);
assert_eq!(extract_noqa_line_for(&lxr), empty);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
@@ -200,7 +190,7 @@ z = x + 1
)
.collect();
println!("{:?}", extract_noqa_line_for(&lxr));
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3]);
assert_eq!(extract_noqa_line_for(&lxr), empty);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
@@ -211,7 +201,7 @@ z = x + 1
)
.collect();
println!("{:?}", extract_noqa_line_for(&lxr));
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 2, 3, 4]);
assert_eq!(extract_noqa_line_for(&lxr), empty);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = '''abc
@@ -222,7 +212,28 @@ y = 2
z = x + 1",
)
.collect();
assert_eq!(extract_noqa_line_for(&lxr), vec![4, 4, 4, 4, 5, 6]);
assert_eq!(extract_noqa_line_for(&lxr), vec![4, 4, 4, 4]);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
y = '''abc
def
ghi
'''
z = 2",
)
.collect();
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 5, 5, 5, 5]);
let lxr: Vec<LexResult> = lexer::make_tokenizer(
"x = 1
y = '''abc
def
ghi
'''",
)
.collect();
assert_eq!(extract_noqa_line_for(&lxr), vec![1, 5, 5, 5, 5]);
Ok(())
}

25
src/plugins.rs Normal file
View File

@@ -0,0 +1,25 @@
mod assert_equals;
mod assert_tuple;
mod if_tuple;
mod invalid_print_syntax;
mod print_call;
mod super_call_with_parameters;
mod type_of_primitive;
mod unnecessary_abspath;
mod use_pep585_annotation;
mod use_pep604_annotation;
mod useless_metaclass_type;
mod useless_object_inheritance;
pub use assert_equals::assert_equals;
pub use assert_tuple::assert_tuple;
pub use if_tuple::if_tuple;
pub use invalid_print_syntax::invalid_print_syntax;
pub use print_call::print_call;
pub use super_call_with_parameters::super_call_with_parameters;
pub use type_of_primitive::type_of_primitive;
pub use unnecessary_abspath::unnecessary_abspath;
pub use use_pep585_annotation::use_pep585_annotation;
pub use use_pep604_annotation::use_pep604_annotation;
pub use useless_metaclass_type::useless_metaclass_type;
pub use useless_object_inheritance::useless_object_inheritance;

View File

@@ -0,0 +1,23 @@
use rustpython_ast::{Expr, Location};
use crate::ast::checks;
use crate::autofix::fixer;
use crate::check_ast::Checker;
use crate::checks::Fix;
pub fn assert_equals(checker: &mut Checker, expr: &Expr) {
if let Some(mut check) = checks::check_assert_equals(expr) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix {
content: "assertEqual".to_string(),
location: Location::new(expr.location.row(), expr.location.column() + 1),
end_location: Location::new(
expr.location.row(),
expr.location.column() + 1 + "assertEquals".len(),
),
applied: false,
});
}
checker.add_check(check);
}
}

View File

@@ -0,0 +1,13 @@
use rustpython_ast::{Expr, Stmt};
use crate::ast::checks;
use crate::ast::types::{CheckLocator, Range};
use crate::check_ast::Checker;
pub fn assert_tuple(checker: &mut Checker, stmt: &Stmt, test: &Expr) {
if let Some(check) =
checks::check_assert_tuple(test, checker.locate_check(Range::from_located(stmt)))
{
checker.add_check(check);
}
}

13
src/plugins/if_tuple.rs Normal file
View File

@@ -0,0 +1,13 @@
use rustpython_ast::{Expr, Stmt};
use crate::ast::checks;
use crate::ast::types::{CheckLocator, Range};
use crate::check_ast::Checker;
pub fn if_tuple(checker: &mut Checker, stmt: &Stmt, test: &Expr) {
if let Some(check) =
checks::check_if_tuple(test, checker.locate_check(Range::from_located(stmt)))
{
checker.add_check(check);
}
}

View File

@@ -0,0 +1,23 @@
use rustpython_ast::{Expr, ExprKind};
use crate::ast::types::{Binding, BindingKind, Range};
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
pub fn invalid_print_syntax(checker: &mut Checker, left: &Expr) {
if let ExprKind::Name { id, .. } = &left.node {
if id == "print" {
let scope = checker.current_scope();
if let Some(Binding {
kind: BindingKind::Builtin,
..
}) = scope.values.get("print")
{
checker.add_check(Check::new(
CheckKind::InvalidPrintSyntax,
Range::from_located(left),
));
}
}
}
}

46
src/plugins/print_call.rs Normal file
View File

@@ -0,0 +1,46 @@
use log::error;
use rustpython_ast::{Expr, Stmt, StmtKind};
use crate::ast::checks;
use crate::autofix::{fixer, fixes};
use crate::check_ast::Checker;
use crate::checks::CheckCode;
pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
if let Some(mut check) = checks::check_print_call(
expr,
func,
checker.settings.enabled.contains(&CheckCode::T201),
checker.settings.enabled.contains(&CheckCode::T203),
) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
let context = checker.binding_context();
if matches!(
checker.parents[context.defined_by].node,
StmtKind::Expr { .. }
) {
let deleted: Vec<&Stmt> = checker
.deletions
.iter()
.map(|index| checker.parents[*index])
.collect();
match fixes::remove_stmt(
checker.parents[context.defined_by],
context.defined_in.map(|index| checker.parents[index]),
&deleted,
) {
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
checker.deletions.insert(context.defined_by);
}
check.amend(fix)
}
Err(e) => error!("Failed to fix unused imports: {}", e),
}
}
}
checker.add_check(check);
}
}

View File

@@ -0,0 +1,31 @@
use rustpython_ast::{Expr, Stmt};
use crate::ast::checks;
use crate::autofix::{fixer, fixes};
use crate::check_ast::Checker;
pub fn super_call_with_parameters(
checker: &mut Checker,
expr: &Expr,
func: &Expr,
args: &Vec<Expr>,
) {
// Only bother going through the super check at all if we're in a `super` call.
// (We check this in `check_super_args` too, so this is just an optimization.)
if checks::is_super_call_with_arguments(func, args) {
let scope = checker.current_scope();
let parents: Vec<&Stmt> = checker
.parent_stack
.iter()
.map(|index| checker.parents[*index])
.collect();
if let Some(mut check) = checks::check_super_args(scope, &parents, expr, func, args) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let Some(fix) = fixes::remove_super_arguments(&mut checker.locator, expr) {
check.amend(fix);
}
}
checker.add_check(check)
}
}
}

View File

@@ -0,0 +1,25 @@
use rustpython_ast::Expr;
use crate::ast::checks;
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::fixer;
use crate::check_ast::Checker;
use crate::checks::{CheckKind, Fix};
pub fn type_of_primitive(checker: &mut Checker, expr: &Expr, func: &Expr, args: &Vec<Expr>) {
if let Some(mut check) =
checks::check_type_of_primitive(func, args, checker.locate_check(Range::from_located(expr)))
{
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let CheckKind::TypeOfPrimitive(primitive) = &check.kind {
check.amend(Fix {
content: primitive.builtin(),
location: expr.location,
end_location: expr.end_location,
applied: false,
});
}
}
checker.add_check(check);
}
}

View File

@@ -0,0 +1,25 @@
use rustpython_ast::Expr;
use crate::ast::checks;
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::fixer;
use crate::check_ast::Checker;
use crate::checks::Fix;
pub fn unnecessary_abspath(checker: &mut Checker, expr: &Expr, func: &Expr, args: &Vec<Expr>) {
if let Some(mut check) = checks::check_unnecessary_abspath(
func,
args,
checker.locate_check(Range::from_located(expr)),
) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix {
content: "__file__".to_string(),
location: expr.location,
end_location: expr.end_location,
applied: false,
});
}
checker.add_check(check);
}
}

View File

@@ -0,0 +1,26 @@
use rustpython_ast::Expr;
use crate::ast::types::Range;
use crate::autofix::fixer;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind, Fix};
use crate::python::typing;
pub fn use_pep585_annotation(checker: &mut Checker, expr: &Expr, id: &str) {
// TODO(charlie): Verify that the builtin is imported from the `typing` module.
if typing::is_pep585_builtin(id) {
let mut check = Check::new(
CheckKind::UsePEP585Annotation(id.to_string()),
Range::from_located(expr),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix {
content: id.to_lowercase(),
location: expr.location,
end_location: expr.end_location,
applied: false,
})
}
checker.add_check(check);
}
}

View File

@@ -0,0 +1,77 @@
use anyhow::{anyhow, Result};
use rustpython_ast::{Expr, ExprKind};
use crate::ast::helpers::match_name_or_attr;
use crate::ast::types::Range;
use crate::autofix::fixer;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind, Fix};
use crate::code_gen::SourceGenerator;
pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, slice: &Expr) {
if match_name_or_attr(value, "Optional") {
let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr));
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(slice, 0) {
if let Ok(content) = generator.generate() {
check.amend(Fix {
content: format!("{} | None", content),
location: expr.location,
end_location: expr.end_location,
applied: false,
})
}
}
}
checker.add_check(check);
} else if match_name_or_attr(value, "Union") {
let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr));
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
match &slice.node {
ExprKind::Slice { .. } => {
// Invalid type annotation.
}
ExprKind::Tuple { elts, .. } => {
// Multiple arguments.
let parts: Result<Vec<String>> = elts
.iter()
.map(|expr| {
let mut generator = SourceGenerator::new();
generator
.unparse_expr(expr, 0)
.map_err(|_| anyhow!("Failed to parse."))?;
generator
.generate()
.map_err(|_| anyhow!("Failed to generate."))
})
.collect();
if let Ok(parts) = parts {
let content = parts.join(" | ");
check.amend(Fix {
content,
location: expr.location,
end_location: expr.end_location,
applied: false,
})
}
}
_ => {
// Single argument.
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(slice, 0) {
if let Ok(content) = generator.generate() {
check.amend(Fix {
content,
location: expr.location,
end_location: expr.end_location,
applied: false,
});
}
}
}
}
}
checker.add_check(check);
}
}

View File

@@ -0,0 +1,44 @@
use log::error;
use rustpython_ast::{Expr, Stmt};
use crate::ast::checks;
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::{fixer, fixes};
use crate::check_ast::Checker;
pub fn useless_metaclass_type(
checker: &mut Checker,
stmt: &Stmt,
value: &Expr,
targets: &Vec<Expr>,
) {
if let Some(mut check) = checks::check_useless_metaclass_type(
targets,
value,
checker.locate_check(Range::from_located(stmt)),
) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
let context = checker.binding_context();
let deleted: Vec<&Stmt> = checker
.deletions
.iter()
.map(|index| checker.parents[*index])
.collect();
match fixes::remove_stmt(
checker.parents[context.defined_by],
context.defined_in.map(|index| checker.parents[index]),
&deleted,
) {
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
checker.deletions.insert(context.defined_by);
}
check.amend(fix)
}
Err(e) => error!("Failed to fix unused imports: {}", e),
}
}
checker.add_check(check);
}
}

View File

@@ -0,0 +1,29 @@
use rustpython_ast::{Expr, Keyword, Stmt};
use crate::ast::checks;
use crate::autofix::{fixer, fixes};
use crate::check_ast::Checker;
pub fn useless_object_inheritance(
checker: &mut Checker,
stmt: &Stmt,
name: &str,
bases: &[Expr],
keywords: &[Keyword],
) {
let scope = checker.current_scope();
if let Some(mut check) = checks::check_useless_object_inheritance(name, bases, scope) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let Some(fix) = fixes::remove_class_def_base(
&mut checker.locator,
&stmt.location,
check.location,
bases,
keywords,
) {
check.amend(fix);
}
}
checker.add_check(check);
}
}

View File

@@ -9,6 +9,7 @@ use serde::{Deserialize, Deserializer};
use crate::checks::CheckCode;
use crate::fs;
use crate::settings::PythonVersion;
pub fn load_config(pyproject: &Option<PathBuf>) -> Result<Config> {
match pyproject {
@@ -29,11 +30,19 @@ pub fn load_config(pyproject: &Option<PathBuf>) -> Result<Config> {
pub struct Config {
pub line_length: Option<usize>,
pub exclude: Option<Vec<String>>,
pub extend_exclude: Option<Vec<String>>,
#[serde(default)]
pub extend_exclude: Vec<String>,
pub select: Option<Vec<CheckCode>>,
pub ignore: Option<Vec<CheckCode>>,
pub per_file_ignores: Option<Vec<StrCheckCodePair>>,
#[serde(default)]
pub extend_select: Vec<CheckCode>,
#[serde(default)]
pub ignore: Vec<CheckCode>,
#[serde(default)]
pub extend_ignore: Vec<CheckCode>,
#[serde(default)]
pub per_file_ignores: Vec<StrCheckCodePair>,
pub dummy_variable_rgx: Option<String>,
pub target_version: Option<PythonVersion>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -175,11 +184,14 @@ mod tests {
ruff: Some(Config {
line_length: None,
exclude: None,
extend_exclude: None,
extend_exclude: vec![],
select: None,
ignore: None,
per_file_ignores: None,
extend_select: vec![],
ignore: vec![],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
target_version: None,
})
})
);
@@ -197,11 +209,14 @@ line-length = 79
ruff: Some(Config {
line_length: Some(79),
exclude: None,
extend_exclude: None,
extend_exclude: vec![],
select: None,
ignore: None,
per_file_ignores: None,
extend_select: vec![],
ignore: vec![],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
target_version: None,
})
})
);
@@ -219,11 +234,14 @@ exclude = ["foo.py"]
ruff: Some(Config {
line_length: None,
exclude: Some(vec!["foo.py".to_string()]),
extend_exclude: None,
extend_exclude: vec![],
select: None,
ignore: None,
per_file_ignores: None,
extend_select: vec![],
ignore: vec![],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
target_version: None,
})
})
);
@@ -241,11 +259,14 @@ select = ["E501"]
ruff: Some(Config {
line_length: None,
exclude: None,
extend_exclude: None,
extend_exclude: vec![],
select: Some(vec![CheckCode::E501]),
ignore: None,
per_file_ignores: None,
extend_select: vec![],
ignore: vec![],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
target_version: None,
})
})
);
@@ -254,6 +275,7 @@ select = ["E501"]
r#"
[tool.black]
[tool.ruff]
extend-select = ["M001"]
ignore = ["E501"]
"#,
)?;
@@ -263,11 +285,14 @@ ignore = ["E501"]
ruff: Some(Config {
line_length: None,
exclude: None,
extend_exclude: None,
extend_exclude: vec![],
select: None,
ignore: Some(vec![CheckCode::E501]),
per_file_ignores: None,
extend_select: vec![CheckCode::M001],
ignore: vec![CheckCode::E501],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
target_version: None,
})
})
);
@@ -325,15 +350,18 @@ other-attribute = 1
Config {
line_length: Some(88),
exclude: None,
extend_exclude: Some(vec![
extend_exclude: vec![
"excluded.py".to_string(),
"migrations".to_string(),
"directory/also_excluded.py".to_string(),
]),
],
select: None,
ignore: None,
per_file_ignores: None,
extend_select: vec![],
ignore: vec![],
extend_ignore: vec![],
per_file_ignores: vec![],
dummy_variable_rgx: None,
target_version: None,
}
);

View File

@@ -7,6 +7,7 @@ static ANNOTATED_SUBSCRIPTS: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
"AbstractAsyncContextManager",
"AbstractContextManager",
"AbstractSet",
// "Annotated",
"AsyncContextManager",
"AsyncGenerator",
"AsyncIterable",
@@ -87,3 +88,14 @@ static ANNOTATED_SUBSCRIPTS: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
pub fn is_annotated_subscript(name: &str) -> bool {
ANNOTATED_SUBSCRIPTS.contains(name)
}
pub fn is_pep593_annotated_subscript(name: &str) -> bool {
name == "Annotated"
}
static PEP_585_BUILTINS: Lazy<BTreeSet<&'static str>> =
Lazy::new(|| BTreeSet::from(["Dict", "FrozenSet", "List", "Set", "Tuple", "Type"]));
pub fn is_pep585_builtin(name: &str) -> bool {
PEP_585_BUILTINS.contains(name)
}

View File

@@ -1,16 +1,50 @@
use std::collections::BTreeSet;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use anyhow::{anyhow, Result};
use glob::Pattern;
use once_cell::sync::Lazy;
use regex::Regex;
use serde::{Deserialize, Serialize};
use crate::checks::{CheckCode, DEFAULT_CHECK_CODES};
use crate::fs;
use crate::pyproject::{load_config, StrCheckCodePair};
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq, Serialize, Deserialize)]
pub enum PythonVersion {
Py33,
Py34,
Py35,
Py36,
Py37,
Py38,
Py39,
Py310,
Py311,
}
impl FromStr for PythonVersion {
type Err = anyhow::Error;
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
"py33" => Ok(PythonVersion::Py33),
"py34" => Ok(PythonVersion::Py34),
"py35" => Ok(PythonVersion::Py35),
"py36" => Ok(PythonVersion::Py36),
"py37" => Ok(PythonVersion::Py37),
"py38" => Ok(PythonVersion::Py38),
"py39" => Ok(PythonVersion::Py39),
"py310" => Ok(PythonVersion::Py310),
"py311" => Ok(PythonVersion::Py311),
_ => Err(anyhow!("Unknown version: {}", string)),
}
}
}
#[derive(Debug, Clone, Hash)]
pub enum FilePattern {
Simple(&'static str),
@@ -51,56 +85,17 @@ impl PerFileIgnore {
}
#[derive(Debug)]
pub struct Settings {
pub pyproject: Option<PathBuf>,
pub project_root: Option<PathBuf>,
pub line_length: usize,
pub struct RawSettings {
pub dummy_variable_rgx: Regex,
pub exclude: Vec<FilePattern>,
pub extend_exclude: Vec<FilePattern>,
pub select: BTreeSet<CheckCode>,
pub extend_ignore: Vec<CheckCode>,
pub extend_select: Vec<CheckCode>,
pub ignore: Vec<CheckCode>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
pub dummy_variable_rgx: Regex,
}
impl Settings {
pub fn for_rule(check_code: CheckCode) -> Self {
Self {
pyproject: None,
project_root: None,
line_length: 88,
exclude: vec![],
extend_exclude: vec![],
select: BTreeSet::from([check_code]),
per_file_ignores: vec![],
dummy_variable_rgx: DEFAULT_DUMMY_VARIABLE_RGX.clone(),
}
}
pub fn for_rules(check_codes: Vec<CheckCode>) -> Self {
Self {
pyproject: None,
project_root: None,
line_length: 88,
exclude: vec![],
extend_exclude: vec![],
select: BTreeSet::from_iter(check_codes),
per_file_ignores: vec![],
dummy_variable_rgx: DEFAULT_DUMMY_VARIABLE_RGX.clone(),
}
}
}
impl Hash for Settings {
fn hash<H: Hasher>(&self, state: &mut H) {
self.line_length.hash(state);
self.dummy_variable_rgx.as_str().hash(state);
for value in self.select.iter() {
value.hash(state);
}
for value in self.per_file_ignores.iter() {
value.hash(state);
}
}
pub select: Vec<CheckCode>,
pub target_version: PythonVersion,
}
static DEFAULT_EXCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
@@ -130,73 +125,117 @@ static DEFAULT_EXCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
static DEFAULT_DUMMY_VARIABLE_RGX: Lazy<Regex> =
Lazy::new(|| Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap());
impl Settings {
impl RawSettings {
pub fn from_pyproject(
pyproject: Option<PathBuf>,
project_root: Option<PathBuf>,
pyproject: &Option<PathBuf>,
project_root: &Option<PathBuf>,
) -> Result<Self> {
let config = load_config(&pyproject)?;
let mut settings = Settings {
line_length: config.line_length.unwrap_or(88),
exclude: config
.exclude
.map(|paths| {
paths
.iter()
.map(|path| FilePattern::from_user(path, &project_root))
.collect()
})
.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()),
extend_exclude: config
.extend_exclude
.map(|paths| {
paths
.iter()
.map(|path| FilePattern::from_user(path, &project_root))
.collect()
})
.unwrap_or_default(),
select: if let Some(select) = config.select {
BTreeSet::from_iter(select)
} else {
BTreeSet::from_iter(DEFAULT_CHECK_CODES)
},
per_file_ignores: config
.per_file_ignores
.map(|ignore_strings| {
ignore_strings
.into_iter()
.map(|pair| PerFileIgnore::new(pair, &project_root))
.collect()
})
.unwrap_or_default(),
let config = load_config(pyproject)?;
Ok(RawSettings {
dummy_variable_rgx: match config.dummy_variable_rgx {
Some(pattern) => Regex::new(&pattern)
.map_err(|e| anyhow!("Invalid dummy-variable-rgx value: {e}"))?,
None => DEFAULT_DUMMY_VARIABLE_RGX.clone(),
},
pyproject,
project_root,
};
if let Some(ignore) = &config.ignore {
settings.ignore(ignore);
target_version: config.target_version.unwrap_or(PythonVersion::Py310),
exclude: config
.exclude
.map(|paths| {
paths
.iter()
.map(|path| FilePattern::from_user(path, project_root))
.collect()
})
.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()),
extend_exclude: config
.extend_exclude
.iter()
.map(|path| FilePattern::from_user(path, project_root))
.collect(),
extend_ignore: config.extend_ignore,
select: config
.select
.unwrap_or_else(|| DEFAULT_CHECK_CODES.to_vec()),
extend_select: config.extend_select,
ignore: config.ignore,
line_length: config.line_length.unwrap_or(88),
per_file_ignores: config
.per_file_ignores
.into_iter()
.map(|pair| PerFileIgnore::new(pair, project_root))
.collect(),
})
}
}
#[derive(Debug)]
pub struct Settings {
pub dummy_variable_rgx: Regex,
pub enabled: BTreeSet<CheckCode>,
pub exclude: Vec<FilePattern>,
pub extend_exclude: Vec<FilePattern>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
pub target_version: PythonVersion,
}
impl Settings {
pub fn from_raw(settings: RawSettings) -> Self {
// Materialize the set of enabled CheckCodes.
let mut enabled: BTreeSet<CheckCode> = BTreeSet::new();
enabled.extend(settings.select);
enabled.extend(settings.extend_select);
for code in &settings.ignore {
enabled.remove(code);
}
Ok(settings)
}
pub fn clear(&mut self) {
self.select.clear();
}
pub fn select(&mut self, codes: Vec<CheckCode>) {
for code in codes {
self.select.insert(code);
for code in &settings.extend_ignore {
enabled.remove(code);
}
Self {
dummy_variable_rgx: settings.dummy_variable_rgx,
enabled,
exclude: settings.exclude,
extend_exclude: settings.extend_exclude,
line_length: settings.line_length,
per_file_ignores: settings.per_file_ignores,
target_version: PythonVersion::Py310,
}
}
pub fn ignore(&mut self, codes: &[CheckCode]) {
for code in codes {
self.select.remove(code);
pub fn for_rule(check_code: CheckCode) -> Self {
Self {
dummy_variable_rgx: DEFAULT_DUMMY_VARIABLE_RGX.clone(),
enabled: BTreeSet::from([check_code]),
exclude: vec![],
extend_exclude: vec![],
line_length: 88,
per_file_ignores: vec![],
target_version: PythonVersion::Py310,
}
}
pub fn for_rules(check_codes: Vec<CheckCode>) -> Self {
Self {
dummy_variable_rgx: DEFAULT_DUMMY_VARIABLE_RGX.clone(),
enabled: BTreeSet::from_iter(check_codes),
exclude: vec![],
extend_exclude: vec![],
line_length: 88,
per_file_ignores: vec![],
target_version: PythonVersion::Py310,
}
}
}
impl Hash for Settings {
fn hash<H: Hasher>(&self, state: &mut H) {
self.line_length.hash(state);
self.dummy_variable_rgx.as_str().hash(state);
for value in self.enabled.iter() {
value.hash(state);
}
for value in self.per_file_ignores.iter() {
value.hash(state);
}
}
}
@@ -227,22 +266,28 @@ impl Exclusion {
/// Struct to render user-facing Settings.
#[derive(Debug)]
pub struct CurrentSettings {
pub pyproject: Option<PathBuf>,
pub project_root: Option<PathBuf>,
pub line_length: usize,
pub dummy_variable_rgx: Regex,
pub exclude: Vec<Exclusion>,
pub extend_exclude: Vec<Exclusion>,
pub select: BTreeSet<CheckCode>,
pub extend_ignore: Vec<CheckCode>,
pub extend_select: Vec<CheckCode>,
pub ignore: Vec<CheckCode>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
pub dummy_variable_rgx: Regex,
pub select: Vec<CheckCode>,
pub target_version: PythonVersion,
pub project_root: Option<PathBuf>,
pub pyproject: Option<PathBuf>,
}
impl CurrentSettings {
pub fn from_settings(settings: Settings) -> Self {
pub fn from_settings(
settings: RawSettings,
project_root: Option<PathBuf>,
pyproject: Option<PathBuf>,
) -> Self {
Self {
pyproject: settings.pyproject,
project_root: settings.project_root,
line_length: settings.line_length,
dummy_variable_rgx: settings.dummy_variable_rgx,
exclude: settings
.exclude
.into_iter()
@@ -253,9 +298,15 @@ impl CurrentSettings {
.into_iter()
.map(Exclusion::from_file_pattern)
.collect(),
select: settings.select,
extend_ignore: settings.extend_ignore,
extend_select: settings.extend_select,
ignore: settings.ignore,
line_length: settings.line_length,
per_file_ignores: settings.per_file_ignores,
dummy_variable_rgx: settings.dummy_variable_rgx,
select: settings.select,
target_version: settings.target_version,
project_root,
pyproject,
}
}
}

View File

@@ -0,0 +1,13 @@
---
source: src/linter.rs
expression: checks
---
- kind: UnnecessaryGeneratorList
location:
row: 1
column: 5
end_location:
row: 1
column: 30
fix: ~

View File

@@ -0,0 +1,13 @@
---
source: src/linter.rs
expression: checks
---
- kind: UnnecessaryGeneratorList
location:
row: 1
column: 5
end_location:
row: 1
column: 29
fix: ~

View File

@@ -0,0 +1,13 @@
---
source: src/linter.rs
expression: checks
---
- kind: UnnecessaryListComprehensionDict
location:
row: 1
column: 5
end_location:
row: 1
column: 35
fix: ~

View File

@@ -0,0 +1,13 @@
---
source: src/linter.rs
expression: checks
---
- kind: UnnecessaryListComprehensionSet
location:
row: 1
column: 5
end_location:
row: 1
column: 31
fix: ~

View File

@@ -0,0 +1,13 @@
---
source: src/linter.rs
expression: checks
---
- kind: UnnecessaryListComprehensionDict
location:
row: 1
column: 5
end_location:
row: 1
column: 37
fix: ~

View File

@@ -0,0 +1,41 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UnnecessaryLiteralSet: list
location:
row: 1
column: 6
end_location:
row: 1
column: 17
fix: ~
- kind:
UnnecessaryLiteralSet: tuple
location:
row: 2
column: 6
end_location:
row: 2
column: 17
fix: ~
- kind:
UnnecessaryLiteralSet: list
location:
row: 3
column: 6
end_location:
row: 3
column: 13
fix: ~
- kind:
UnnecessaryLiteralSet: tuple
location:
row: 4
column: 6
end_location:
row: 4
column: 13
fix: ~

View File

@@ -0,0 +1,41 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UnnecessaryLiteralDict: list
location:
row: 1
column: 6
end_location:
row: 1
column: 20
fix: ~
- kind:
UnnecessaryLiteralDict: tuple
location:
row: 2
column: 6
end_location:
row: 2
column: 21
fix: ~
- kind:
UnnecessaryLiteralDict: list
location:
row: 3
column: 6
end_location:
row: 3
column: 14
fix: ~
- kind:
UnnecessaryLiteralDict: tuple
location:
row: 4
column: 6
end_location:
row: 4
column: 14
fix: ~

View File

@@ -0,0 +1,41 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UnnecessaryCollectionCall: tuple
location:
row: 1
column: 5
end_location:
row: 1
column: 12
fix: ~
- kind:
UnnecessaryCollectionCall: list
location:
row: 2
column: 5
end_location:
row: 2
column: 11
fix: ~
- kind:
UnnecessaryCollectionCall: dict
location:
row: 3
column: 6
end_location:
row: 3
column: 12
fix: ~
- kind:
UnnecessaryCollectionCall: dict
location:
row: 4
column: 6
end_location:
row: 4
column: 15
fix: ~

View File

@@ -3,30 +3,129 @@ source: src/linter.rs
expression: checks
---
- kind:
UnusedImport: functools
UnusedImport:
- functools
location:
row: 3
row: 2
column: 1
end_location:
row: 3
column: 17
fix: ~
row: 2
column: 21
fix:
content: import os
location:
row: 2
column: 1
end_location:
row: 2
column: 21
applied: false
- kind:
UnusedImport: collections.OrderedDict
UnusedImport:
- collections.OrderedDict
location:
row: 5
row: 4
column: 1
end_location:
row: 9
row: 8
column: 2
fix: ~
fix:
content: "from collections import (\n Counter,\n namedtuple,\n)"
location:
row: 4
column: 1
end_location:
row: 8
column: 2
applied: false
- kind:
UnusedImport: logging.handlers
UnusedImport:
- logging.handlers
location:
row: 13
row: 12
column: 1
end_location:
row: 13
row: 12
column: 24
fix: ~
fix:
content: import logging.handlers
location:
row: 12
column: 1
end_location:
row: 12
column: 24
applied: false
- kind:
UnusedImport:
- shelve
location:
row: 33
column: 5
end_location:
row: 33
column: 18
fix:
content: ""
location:
row: 33
column: 1
end_location:
row: 34
column: 1
applied: false
- kind:
UnusedImport:
- importlib
location:
row: 34
column: 5
end_location:
row: 34
column: 21
fix:
content: ""
location:
row: 34
column: 1
end_location:
row: 35
column: 1
applied: false
- kind:
UnusedImport:
- pathlib
location:
row: 38
column: 5
end_location:
row: 38
column: 19
fix:
content: ""
location:
row: 38
column: 1
end_location:
row: 39
column: 1
applied: false
- kind:
UnusedImport:
- pickle
location:
row: 53
column: 9
end_location:
row: 53
column: 22
fix:
content: pass
location:
row: 53
column: 9
end_location:
row: 53
column: 22
applied: false

View File

@@ -5,7 +5,7 @@ expression: checks
- kind:
ImportStarUsage:
- name
- mymodule
- - mymodule
location:
row: 5
column: 11
@@ -16,7 +16,7 @@ expression: checks
- kind:
ImportStarUsage:
- a
- mymodule
- - mymodule
location:
row: 11
column: 1

View File

@@ -9,6 +9,6 @@ expression: checks
column: 13
end_location:
row: 9
column: 17
column: 16
fix: ~

View File

@@ -45,7 +45,7 @@ expression: checks
column: 5
end_location:
row: 58
column: 9
column: 8
fix: ~
- kind:
UndefinedName: TOMATO
@@ -74,4 +74,31 @@ expression: checks
row: 89
column: 9
fix: ~
- kind:
UndefinedName: PEP593Test123
location:
row: 114
column: 10
end_location:
row: 114
column: 23
fix: ~
- kind:
UndefinedName: foo
location:
row: 122
column: 15
end_location:
row: 122
column: 18
fix: ~
- kind:
UndefinedName: bar
location:
row: 122
column: 22
end_location:
row: 122
column: 25
fix: ~

View File

@@ -3,21 +3,30 @@ source: src/linter.rs
expression: checks
---
- kind:
UnusedImport: models.Nut
UnusedImport:
- models.Nut
location:
row: 5
column: 1
end_location:
row: 5
column: 30
fix: ~
row: 8
column: 2
fix:
content: "from models import (\n Fruit,\n)"
location:
row: 5
column: 1
end_location:
row: 8
column: 2
applied: false
- kind:
UndefinedName: Bar
location:
row: 22
row: 25
column: 19
end_location:
row: 22
row: 25
column: 22
fix: ~

View File

@@ -50,4 +50,36 @@ expression: checks
row: 22
column: 10
applied: false
- kind: SuperCallWithParameters
location:
row: 36
column: 9
end_location:
row: 36
column: 29
fix:
content: super()
location:
row: 36
column: 9
end_location:
row: 36
column: 29
applied: false
- kind: SuperCallWithParameters
location:
row: 50
column: 13
end_location:
row: 50
column: 33
fix:
content: super()
location:
row: 50
column: 13
end_location:
row: 50
column: 33
applied: false

View File

@@ -0,0 +1,21 @@
---
source: src/linter.rs
expression: checks
---
- kind: PrintFound
location:
row: 1
column: 1
end_location:
row: 1
column: 23
fix:
content: ""
location:
row: 1
column: 1
end_location:
row: 2
column: 1
applied: false

View File

@@ -0,0 +1,37 @@
---
source: src/linter.rs
expression: checks
---
- kind: PPrintFound
location:
row: 3
column: 1
end_location:
row: 3
column: 24
fix:
content: ""
location:
row: 3
column: 1
end_location:
row: 4
column: 1
applied: false
- kind: PPrintFound
location:
row: 8
column: 1
end_location:
row: 8
column: 31
fix:
content: ""
location:
row: 8
column: 1
end_location:
row: 9
column: 1
applied: false

View File

@@ -0,0 +1,37 @@
---
source: src/linter.rs
expression: checks
---
- kind: UselessMetaclassType
location:
row: 2
column: 5
end_location:
row: 2
column: 25
fix:
content: pass
location:
row: 2
column: 5
end_location:
row: 2
column: 25
applied: false
- kind: UselessMetaclassType
location:
row: 6
column: 5
end_location:
row: 6
column: 25
fix:
content: ""
location:
row: 6
column: 1
end_location:
row: 7
column: 1
applied: false

View File

@@ -0,0 +1,53 @@
---
source: src/linter.rs
expression: checks
---
- kind: UnnecessaryAbspath
location:
row: 3
column: 5
end_location:
row: 3
column: 22
fix:
content: __file__
location:
row: 3
column: 5
end_location:
row: 3
column: 22
applied: false
- kind: UnnecessaryAbspath
location:
row: 9
column: 5
end_location:
row: 9
column: 30
fix:
content: __file__
location:
row: 9
column: 5
end_location:
row: 9
column: 30
applied: false
- kind: UnnecessaryAbspath
location:
row: 15
column: 5
end_location:
row: 15
column: 27
fix:
content: __file__
location:
row: 15
column: 5
end_location:
row: 15
column: 27
applied: false

View File

@@ -0,0 +1,90 @@
---
source: src/linter.rs
expression: checks
---
- kind:
TypeOfPrimitive: Str
location:
row: 1
column: 1
end_location:
row: 1
column: 9
fix:
content: str
location:
row: 1
column: 1
end_location:
row: 1
column: 9
applied: false
- kind:
TypeOfPrimitive: Bytes
location:
row: 2
column: 1
end_location:
row: 2
column: 10
fix:
content: bytes
location:
row: 2
column: 1
end_location:
row: 2
column: 10
applied: false
- kind:
TypeOfPrimitive: Int
location:
row: 3
column: 1
end_location:
row: 3
column: 8
fix:
content: int
location:
row: 3
column: 1
end_location:
row: 3
column: 8
applied: false
- kind:
TypeOfPrimitive: Float
location:
row: 4
column: 1
end_location:
row: 4
column: 9
fix:
content: float
location:
row: 4
column: 1
end_location:
row: 4
column: 9
applied: false
- kind:
TypeOfPrimitive: Complex
location:
row: 5
column: 1
end_location:
row: 5
column: 9
fix:
content: complex
location:
row: 5
column: 1
end_location:
row: 5
column: 9
applied: false

View File

@@ -0,0 +1,39 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UsePEP585Annotation: List
location:
row: 4
column: 10
end_location:
row: 4
column: 14
fix:
content: list
location:
row: 4
column: 10
end_location:
row: 4
column: 14
applied: false
- kind:
UsePEP585Annotation: List
location:
row: 11
column: 10
end_location:
row: 11
column: 21
fix:
content: list
location:
row: 11
column: 10
end_location:
row: 11
column: 21
applied: false

View File

@@ -0,0 +1,133 @@
---
source: src/linter.rs
expression: checks
---
- kind: UsePEP604Annotation
location:
row: 4
column: 10
end_location:
row: 4
column: 23
fix:
content: str | None
location:
row: 4
column: 10
end_location:
row: 4
column: 23
applied: false
- kind: UsePEP604Annotation
location:
row: 11
column: 10
end_location:
row: 11
column: 30
fix:
content: str | None
location:
row: 11
column: 10
end_location:
row: 11
column: 30
applied: false
- kind: UsePEP604Annotation
location:
row: 18
column: 10
end_location:
row: 18
column: 46
fix:
content: "str | int | Union[float, bytes]"
location:
row: 18
column: 10
end_location:
row: 18
column: 46
applied: false
- kind: UsePEP604Annotation
location:
row: 18
column: 26
end_location:
row: 18
column: 45
fix:
content: float | bytes
location:
row: 18
column: 26
end_location:
row: 18
column: 45
applied: false
- kind: UsePEP604Annotation
location:
row: 25
column: 10
end_location:
row: 25
column: 32
fix:
content: str | int
location:
row: 25
column: 10
end_location:
row: 25
column: 32
applied: false
- kind: UsePEP604Annotation
location:
row: 32
column: 11
end_location:
row: 32
column: 47
fix:
content: "str | int | Union[float, bytes]"
location:
row: 32
column: 11
end_location:
row: 32
column: 47
applied: false
- kind: UsePEP604Annotation
location:
row: 32
column: 11
end_location:
row: 32
column: 47
fix:
content: float | bytes
location:
row: 32
column: 11
end_location:
row: 32
column: 47
applied: false
- kind: UsePEP604Annotation
location:
row: 39
column: 11
end_location:
row: 39
column: 33
fix:
content: str | int
location:
row: 39
column: 11
end_location:
row: 39
column: 33
applied: false

View File

@@ -0,0 +1,13 @@
---
source: src/linter.rs
expression: checks
---
- kind: NoNewLineAtEndOfFile
location:
row: 2
column: 9
end_location:
row: 2
column: 9
fix: ~

View File

@@ -0,0 +1,13 @@
---
source: src/linter.rs
expression: checks
---
- kind: NoNewLineAtEndOfFile
location:
row: 2
column: 9
end_location:
row: 2
column: 9
fix: ~

View File

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

View File

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