Compare commits

...

90 Commits

Author SHA1 Message Date
Charlie Marsh
8cf745045f Bump version to 0.0.110 2022-11-10 19:22:45 -05:00
Charlie Marsh
f6992cc98c Add a test utility for running lint checks (#672) 2022-11-10 19:22:00 -05:00
Charlie Marsh
3cc74c0564 Implement import sorting (#633) 2022-11-10 19:05:56 -05:00
Charlie Marsh
887b9aa840 Rename some fixture files (#671) 2022-11-10 17:28:10 -05:00
Charlie Marsh
faf8556a5c Limit Ropey to newlines and carriage returns (#670) 2022-11-10 17:25:30 -05:00
Harutaka Kawamura
1888f6d41b Implement B009 (#669) 2022-11-10 13:52:20 -05:00
Charlie Marsh
9d8cd2d2fe Bump version to 0.0.109 2022-11-10 10:54:27 -05:00
Harutaka Kawamura
05c19f0091 Implement B026 (#668) 2022-11-10 10:47:42 -05:00
Chammika Mannakkara
8213b64ad5 Fix unnecessary params in lru_cache (#667) 2022-11-10 10:45:12 -05:00
Chammika Mannakkara
ff0f5968fa Detect unnecessary params in lru_cache (#664) 2022-11-09 10:02:48 -05:00
Charlie Marsh
6c17670aa5 Upgrade LibCST and other crates (#663) 2022-11-08 17:42:12 -05:00
Charlie Marsh
1347b7ebb6 Add notes to README on editor integrations (#655) 2022-11-08 16:12:42 -05:00
Reiner Gerecke
f40609f524 Implement autofix for C413 (#661) 2022-11-08 16:12:29 -05:00
Charlie Marsh
f572acab30 Bump version to 0.0.108 2022-11-08 13:20:35 -05:00
Charlie Marsh
1eb5f3ea75 Support allow_star_arg_any in flake8-to-ruff 2022-11-08 09:51:11 -05:00
Edgar R. M
24aa177912 Implement ANN401 (#657) 2022-11-08 09:49:44 -05:00
Reiner Gerecke
2ddc768412 Implement fix for C404 (#656) 2022-11-08 09:37:17 -05:00
Chammika Mannakkara
0f9508f549 Remove unnecessary __future__ imports (#634) 2022-11-07 22:26:57 -05:00
Charlie Marsh
7c3d387abd Implement confusing unicode character detection for comments (#653) 2022-11-07 21:16:34 -05:00
Charlie Marsh
d600650214 Upgrade RustPython (#652) 2022-11-07 21:07:49 -05:00
Charlie Marsh
43383bb696 Bump version to 0.0.107 2022-11-07 16:39:03 -05:00
Charlie Marsh
ff83f356af Fix failing flake8-to-ruff tests 2022-11-07 16:38:46 -05:00
Anders Kaseorg
89622425d4 Run annotations plugin if ANN204, ANN205, ANN206 are selected (#649) 2022-11-07 16:37:49 -05:00
Charlie Marsh
1aafe31c00 Avoid U009 violations when disabled (#650) 2022-11-07 16:36:39 -05:00
Charlie Marsh
16c5ac1e91 Bump version to 0.0.106 2022-11-07 15:32:54 -05:00
Charlie Marsh
da39cddccb Include function and argument names in ANN checks (#648) 2022-11-07 15:27:03 -05:00
Charlie Marsh
de6435f41d Add a flake8-to-ruff mention (#644) 2022-11-07 10:59:47 -05:00
Charlie Marsh
589ae48f8c Update --help 2022-11-07 10:41:04 -05:00
Harutaka Kawamura
35cc3094b5 Implement B005 (#643) 2022-11-07 10:10:59 -05:00
Charlie Marsh
07c9fc55f6 Add fix option to pyproject.toml (#639) 2022-11-07 09:46:21 -05:00
Harutaka Kawamura
7773e9728b Implement B004 (#638) 2022-11-07 09:25:09 -05:00
Charlie Marsh
8fc435bad9 Fix --ignore for ANN101 and ANN102 (#637) 2022-11-07 09:03:09 -05:00
Charlie Marsh
dd20b23576 Infer plugins based on per-file-ignores, ignores, etc. (#632) 2022-11-06 22:39:06 -05:00
Charlie Marsh
da8ee1df3e Add TODO in flake8_annotations 2022-11-06 21:24:46 -05:00
Charlie Marsh
7f77ed0f86 Bump version to 0.0.105 2022-11-06 21:17:00 -05:00
Charlie Marsh
1b33cfb9cb Respect project root in per-file ignores (#631) 2022-11-06 21:15:49 -05:00
Charlie Marsh
dbc64f1faa Remove erroneous Literal entry from subscript list (#630) 2022-11-06 21:03:41 -05:00
Charlie Marsh
f4b5f0d259 Remove foo.py 2022-11-06 21:03:23 -05:00
Charlie Marsh
85b882fc54 Remove CheckLocator abstraction (#627) 2022-11-06 17:42:10 -05:00
Charlie Marsh
99d9aa61bf Implement flake8-annotations (#625) 2022-11-06 17:25:49 -05:00
Charlie Marsh
050f34dd25 Bump version to 0.0.104 2022-11-06 15:31:10 -05:00
Charlie Marsh
1cd82d588b Categorize functions in pep8-naming (#624) 2022-11-06 15:29:49 -05:00
Charlie Marsh
cea9e34942 Update CONTRIBUTING.md (#623) 2022-11-06 14:31:16 -05:00
Reiner Gerecke
1ede377402 Improve discoverability of dev commands (#621) 2022-11-06 14:25:59 -05:00
Reiner Gerecke
82eff641fb Remove utf-8 encoding declaration (#618) 2022-11-06 14:23:06 -05:00
Charlie Marsh
eb1bc9f092 Allow underscore names in N803 (#622) 2022-11-06 14:19:02 -05:00
Reiner Gerecke
df88504dea pyflakes F632 Autofix (#612) 2022-11-06 06:57:46 -05:00
Anders Kaseorg
b9ec3e9137 Correct source link in CONFUSABLES comment (#617) 2022-11-05 20:53:05 -04:00
Anders Kaseorg
b067b665ff Fix B015 false positive on comparison deep inside expression statement (#616) 2022-11-05 20:13:22 -04:00
Charlie Marsh
22cfd03b13 Bump ruff_dev to v0.0.103 2022-11-05 19:53:27 -04:00
Charlie Marsh
3ae408a364 Only track noqa directives for multi-line strings (#615) 2022-11-05 19:53:13 -04:00
Anders Kaseorg
95073b1586 Fix “Code” misspelling (#614) 2022-11-05 19:28:52 -04:00
Charlie Marsh
3597a94818 Add a README link to each plugin (#611) 2022-11-05 16:18:27 -04:00
Charlie Marsh
2727e5f3b7 Remove some usages of Ruff internals in ruff_dev (#610) 2022-11-05 16:07:21 -04:00
Charlie Marsh
de53b567f5 Add a list of projects using Ruff (#608) 2022-11-05 16:03:10 -04:00
Charlie Marsh
21479a151d Bump ruff_dev to v0.0.102 2022-11-05 16:00:24 -04:00
Charlie Marsh
d822458d97 Fix reference to src/checks_gen.rs 2022-11-05 15:59:51 -04:00
Charlie Marsh
6741ea9790 Create a separate dev crate for development scripts (#607) 2022-11-05 15:59:18 -04:00
Charlie Marsh
2e1799dd80 Automatically update README.md from generate_rules_table.rs (#606) 2022-11-05 15:33:47 -04:00
Reiner Gerecke
c48c7669a8 Remove rust version from CONTRIBUTING.md (#605) 2022-11-05 15:03:10 -04:00
Charlie Marsh
d5fbcd708d Automatically write to src/checks_gen.rs (#604) 2022-11-05 14:30:18 -04:00
Charlie Marsh
b335a6a5ec Bump version to 0.0.102 2022-11-05 14:13:02 -04:00
Charlie Marsh
f5cbee452a Increase flake8-bugbear count to 15/32 2022-11-05 14:12:35 -04:00
Harutaka Kawamura
5d1ea4410a Ignore ellipsis in B018 (#598) 2022-11-05 14:11:44 -04:00
Harutaka Kawamura
f27163d2c7 Remove needless return (#597) 2022-11-05 14:11:26 -04:00
Harutaka Kawamura
62c126f70e Implement B003 (#596) 2022-11-05 14:08:02 -04:00
Harutaka Kawamura
8344d5151c Implement B016 (#595) 2022-11-05 14:06:00 -04:00
Harutaka Kawamura
f7780eb720 Implement B008 (#594) 2022-11-05 14:05:04 -04:00
Charlie Marsh
c544d84b46 Rely on token locations for noqa map extraction (#603) 2022-11-05 10:51:19 -04:00
Charlie Marsh
fbdc075e5c Change Ruff's error prefix to RUF (#592) 2022-11-04 23:13:50 -04:00
Charlie Marsh
d3472104d0 Bump flake8-bugbear count to 12/32 2022-11-04 14:49:14 -04:00
Charlie Marsh
0c8b981216 Bump version to 0.0.101 2022-11-04 14:46:48 -04:00
Charlie Marsh
0e3f08aa68 Split ambiguous unicode detection into string vs. docstring rules (#590) 2022-11-04 14:46:20 -04:00
Charlie Marsh
a75b1c85ee Fix invalid escape handling for CRLF files (#589) 2022-11-04 14:26:39 -04:00
Harutaka Kawamura
726e6c68cf Implement B015 (#587) 2022-11-04 13:47:52 -04:00
Charlie Marsh
34c91224d7 Bump version to 0.0.100 2022-11-04 12:10:17 -04:00
Charlie Marsh
50c4bbc52b Implement ambiguous unicode character detection (#578) 2022-11-04 12:08:26 -04:00
Charlie Marsh
f0239de559 Use a shared Rope between AST checker and fixer (#585) 2022-11-04 12:05:46 -04:00
Charlie Marsh
2be632f3cc Use a Rope to power fixer (#584) 2022-11-04 12:04:14 -04:00
Harutaka Kawamura
ec3fed5a61 Implement B018 (#582) 2022-11-04 10:53:30 -04:00
Charlie Marsh
1da44485d5 Confine subscript annotation checks to ExprContext::Load (#583) 2022-11-04 10:51:05 -04:00
Charlie Marsh
e5f30ff5a8 Use a rope to manage string slicing (#576) 2022-11-03 23:23:38 -04:00
Charlie Marsh
c92f5c14a3 Bump Rust version to 1.65.0 (#575) 2022-11-03 22:49:21 -04:00
Charlie Marsh
2f92399f4e Add W to list of default flake8-to-ruff codes (#574) 2022-11-03 22:13:00 -04:00
Charlie Marsh
83dfb5fe8b Infer Flake8 plugins from .flake8 config (#573) 2022-11-03 22:05:46 -04:00
Charlie Marsh
cc915371ca Implement autofix for F901 (#571) 2022-11-03 11:58:54 -04:00
Charlie Marsh
5576db3d5a Bump version to 0.0.99 2022-11-03 11:47:15 -04:00
Charlie Marsh
f26f38d023 Enable autofix for C406 (#570) 2022-11-03 11:27:46 -04:00
Charlie Marsh
578ec4d843 Implement autofix for C416 (#568) 2022-11-03 11:05:08 -04:00
Charlie Marsh
2f3bebe5a2 Enable autofix for dict(a=1)-like dictionaries (#567) 2022-11-03 10:42:54 -04:00
250 changed files with 11277 additions and 1773 deletions

2
.cargo/config.toml Normal file
View File

@@ -0,0 +1,2 @@
[alias]
dev = "run --package ruff_dev --bin ruff_dev"

View File

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

View File

@@ -1,17 +1,17 @@
# Contributing to ruff
# Contributing to Ruff
Welcome! We're happy to have you here. Thank you in advance for your contribution to ruff.
Welcome! We're happy to have you here. Thank you in advance for your contribution to Ruff.
## The basics
ruff welcomes contributions in the form of Pull Requests. For small changes (e.g., bug fixes), feel
Ruff welcomes contributions in the form of Pull Requests. For small changes (e.g., bug fixes), feel
free to submit a PR. For larger changes (e.g., new lint rules, new functionality, new configuration
options), consider submitting an [Issue](https://github.com/charliermarsh/ruff/issues) outlining
your proposed change.
### Prerequisites
ruff is written in Rust (1.63.0). You'll need to install the
Ruff is written in Rust. 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:
@@ -22,7 +22,7 @@ cargo install cargo-insta
### Development
After cloning the repository, run ruff locally with:
After cloning the repository, run Ruff locally with:
```shell
cargo run resources/test/fixtures --no-cache
@@ -32,9 +32,9 @@ Prior to opening a pull request, ensure that your code has been auto-formatted,
both the lint and test validation checks:
```shell
cargo fmt # Auto-formatting...
cargo clippy # Linting...
cargo test # Testing...
cargo +nightly fmt --all # Auto-formatting...
cargo +nightly clippy --all # Linting...
cargo +nightly test --all # Testing...
```
These checks will run on GitHub Actions when you open your Pull Request, but running them locally
@@ -45,12 +45,13 @@ prior to merging.
### Example: Adding a new lint rule
There are three phases to adding a new lint rule:
There are four phases to adding a new lint rule:
1. Define the rule in `src/checks.rs`.
2. Define the _logic_ for triggering the rule in `src/check_ast.rs` (for AST-based checks)
or `src/check_lines.rs` (for text-based checks).
2. Define the _logic_ for triggering the rule in `src/check_ast.rs` (for AST-based checks),
`src/check_tokens.rs` (for token-based checks), or `src/check_lines.rs` (for text-based checks).
3. Add a test fixture.
4. Update the generated files (documentation and generated code).
To define the rule, open up `src/checks.rs`. You'll need to define both a `CheckCode` and
`CheckKind`. As an example, you can grep for `E402` and `ModuleImportNotAtTopOfFile`, and follow the
@@ -58,37 +59,33 @@ pattern implemented therein.
To trigger the rule, you'll likely want to augment the logic in `src/check_ast.rs`, which defines
the Python AST visitor, responsible for iterating over the abstract syntax tree and collecting
lint-rule violations as it goes. Grep for the `Check::new` invocations to understand how other,
similar rules are implemented.
lint-rule violations as it goes. If you need to inspect the AST, you can run `cargo dev print-ast`
with a Python file. Grep for the `Check::new` invocations to understand how other, 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 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:
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 --no-cache`. Once you're satisfied with the
output, codify the behavior as a snapshot test by adding a new `testcase` macro to the `mod tests`
section of `src/linter.rs`, like so:
```rust
#[test]
fn e402() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/E402.py"),
&settings::Settings::for_rule(CheckCode::E402),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test_case(CheckCode::A001, Path::new("A001.py"); "A001")]
...
```
Then, run `cargo test`. Your test will fail, but you'll be prompted to follow-up with
`cargo insta review`. Accept the generated snapshot, then commit the snapshot file alongside the
rest of your changes.
Finally, to update the documentation, run `cargo dev generate-rules-table` from the repo root. To
update the generated prefix map, run `cargo dev generate-check-code-prefix`. Both of these commands
should be run whenever a new check is added to the codebase.
### Example: Adding a new configuration option
ruff's user-facing settings live in two places: first, the command-line options defined with
Ruff's user-facing settings live in two places: first, the command-line options defined with
[clap](https://docs.rs/clap/latest/clap/) via the `Cli` struct in `src/main.rs`; and second, the
`Config` struct defined `src/pyproject.rs`, which is responsible for extracting user-defined
settings from a `pyproject.toml` file.
@@ -103,9 +100,9 @@ acceptable unused variables (e.g., `_`).
## Release process
As of now, ruff has an ad hoc release process: releases are cut with high frequency via GitHub
As of now, Ruff has an ad hoc release process: releases are cut with high frequency via GitHub
Actions, which automatically generates the appropriate wheels across architectures and publishes
them to [PyPI](https://pypi.org/project/ruff/).
ruff follows the [semver](https://semver.org/) versioning standard. However, as pre-1.0 software,
Ruff follows the [semver](https://semver.org/) versioning standard. However, as pre-1.0 software,
even patch releases may contain [non-backwards-compatible changes](https://semver.org/#spec-item-4).

332
Cargo.lock generated
View File

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

View File

@@ -1,18 +1,19 @@
[workspace]
members = [
"flake8_to_ruff",
"flake8_to_ruff",
"ruff_dev",
]
[package]
name = "ruff"
version = "0.0.98"
version = "0.0.110"
edition = "2021"
[lib]
name = "ruff"
[dependencies]
anyhow = { version = "1.0.60" }
anyhow = { version = "1.0.66" }
bincode = { version = "1.3.3" }
chrono = { version = "0.4.21" }
clap = { version = "4.0.1", features = ["derive"] }
@@ -23,25 +24,26 @@ fern = { version = "0.6.1" }
filetime = { version = "0.2.17" }
glob = { version = "0.3.0" }
itertools = { version = "0.10.5" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "32a044c127668df44582f85699358e67803b0d73" }
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "a13ec97dd4eb925bde4d426c6e422582793b260c" }
log = { version = "0.4.17" }
notify = { version = "4.0.17" }
num-bigint = { version = "0.4.3" }
once_cell = { version = "1.13.1" }
once_cell = { version = "1.16.0" }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
rayon = { version = "1.5.3" }
regex = { version = "1.6.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "77b821a1941019fe34f73ce17cea013ae1b98fd0" }
serde = { version = "1.0.143", features = ["derive"] }
serde_json = { version = "1.0.83" }
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
textwrap = { version = "0.15.1" }
textwrap = { version = "0.16.0" }
titlecase = { version = "2.2.1" }
toml = { version = "0.5.9" }
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
update-informer = { version = "0.5.0", default-features = false, features = ["pypi"], optional = true }
walkdir = { version = "2.3.2" }
[target.'cfg(not(target_family = "wasm"))'.dependencies]
@@ -55,7 +57,6 @@ getrandom = { version = "0.2.7", features = ["js"] }
[dev-dependencies]
assert_cmd = { version = "2.0.4" }
codegen = { version = "0.2.0" }
criterion = { version = "0.4.0" }
insta = { version = "1.19.1", features = ["yaml"] }
test-case = { version = "2.2.2" }

220
README.md
View File

@@ -15,21 +15,29 @@ An extremely fast Python linter, written in Rust.
<i>Linting the CPython codebase from scratch.</i>
</p>
- ⚡️ 10-100x faster than existing linters
- 🐍 Installable via `pip`
- 🤝 Python 3.10 compatibility
- 🛠️ `pyproject.toml` support
- 📦 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired cache support
- 🔧 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix)-inspired autofix support (e.g., automatically remove unused imports)
- 👀 [TypeScript](https://www.typescriptlang.org/docs/handbook/configuring-watch.html)-inspired `--watch` support, for continuous file monitoring
- ⚖️ [Near-parity](#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
- 🔌 Native re-implementations of popular Flake8 plugins, like [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) ([`pydocstyle`](https://pypi.org/project/pydocstyle/))
- ⚡️ 10-100x faster than existing linters
- 🐍 Installable via `pip`
- 🤝 Python 3.10 compatibility
- 🛠️ `pyproject.toml` support
- 📦 Built-in caching, to avoid re-analyzing unchanged files
- 🔧 `--fix` support, for automatic error correction (e.g., automatically remove unused imports)
- 👀 `--watch` support, for continuous file monitoring
- ⚖️ [Near-parity](#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
- 🔌 Native re-implementations of popular Flake8 plugins, like [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) ([`pydocstyle`](https://pypi.org/project/pydocstyle/))
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
functionality behind a single, common interface. Ruff can be used to replace Flake8 (plus a variety
of plugins), [`pydocstyle`](https://pypi.org/project/pydocstyle/), [`yesqa`](https://github.com/asottile/yesqa),
and even a subset of [`pyupgrade`](https://pypi.org/project/pyupgrade/) and [`autoflake`](https://pypi.org/project/autoflake/)
all while executing tens or hundreds of times faster than any individual tool.
of plugins), [`isort`](https://pypi.org/project/isort/), [`pydocstyle`](https://pypi.org/project/pydocstyle/),
[`yesqa`](https://github.com/asottile/yesqa), and even a subset of [`pyupgrade`](https://pypi.org/project/pyupgrade/)
and [`autoflake`](https://pypi.org/project/autoflake/) all while executing tens or hundreds of times
faster than any individual tool.
(Coming from Flake8? Try [`flake8-to-ruff`](https://pypi.org/project/flake8-to-ruff/) to
automatically convert your existing configuration.)
Ruff is actively developed and used in major open-source projects
like [Zulip](https://github.com/zulip/zulip), [pydantic](https://github.com/pydantic/pydantic),
and [Saleor](https://github.com/saleor/saleor).
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
@@ -39,17 +47,18 @@ Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-mu
2. [Configuration](#configuration)
3. [Supported Rules](#supported-rules)
1. [Pyflakes](#pyflakes)
2. [pycodestyle (error)](#pycodestyle-error)
3. [pycodestyle (warning)](#pycodestyle-warning)
4. [pydocstyle](#pydocstyle)
5. [pyupgrade](#pyupgrade)
6. [pep8-naming](#pep8-naming)
7. [flake8-comprehensions](#flake8-comprehensions)
8. [flake8-bugbear](#flake8-bugbear)
9. [flake8-builtins](#flake8-builtins)
10. [flake8-print](#flake8-print)
11. [flake8-quotes](#flake8-quotes)
12. [Meta rules](#meta-rules)
2. [pycodestyle](#pycodestyle)
3. [pydocstyle](#pydocstyle)
4. [pyupgrade](#pyupgrade)
5. [pep8-naming](#pep8-naming)
6. [flake8-comprehensions](#flake8-comprehensions)
7. [flake8-bugbear](#flake8-bugbear)
8. [flake8-builtins](#flake8-builtins)
9. [flake8-print](#flake8-print)
10. [flake8-quotes](#flake8-quotes)
11. [flake8-annotations](#flake8-annotations)
12. [Ruff-specific rules](#ruff-specific-rules)
13. [Meta rules](#meta-rules)
5. [Editor Integrations](#editor-integrations)
6. [FAQ](#faq)
7. [Development](#development)
@@ -89,7 +98,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.98
rev: v0.0.110
hooks:
- id: ruff
```
@@ -195,7 +204,7 @@ Options:
Exit with status code "0", even upon detecting errors
-w, --watch
Run in watch mode by re-running whenever files change
-f, --fix
--fix
Attempt to automatically fix lint errors
-n, --no-cache
Disable cache reads
@@ -277,25 +286,32 @@ Ruff supports several workflows to aid in `noqa` management.
First, Ruff provides a special error code, `M001`, to enforce that your `noqa` directives are
"valid", in that the errors they _say_ they ignore are actually being triggered on that line (and
thus suppressed). **You can run `ruff /path/to/file.py --extend-select M001` to flag unused `noqa`
directives.**
thus suppressed). You can run `ruff /path/to/file.py --extend-select M001` to flag unused `noqa`
directives.
Second, Ruff can _automatically remove_ unused `noqa` directives via its autofix functionality.
**You can run `ruff /path/to/file.py --extend-select M001 --fix` to automatically remove unused
`noqa` directives.**
You can run `ruff /path/to/file.py --extend-select M001 --fix` to automatically remove unused
`noqa` directives.
Third, Ruff can _automatically add_ `noqa` directives to all failing lines. This is useful when
migrating a new codebase to Ruff. **You can run `ruff /path/to/file.py --add-noqa` to automatically
add `noqa` directives to all failing lines, with the appropriate error codes.**
migrating a new codebase to Ruff. You can run `ruff /path/to/file.py --add-noqa` to automatically
add `noqa` directives to all failing lines, with the appropriate error codes.
## Supported Rules
Regardless of the rule's origin, Ruff re-implements every rule in Rust as a first-party feature.
By default, Ruff enables all `E` and `F` error codes, which correspond to those built-in to Flake8.
The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` command-line option.
<!-- Sections automatically generated by `cargo dev generate-rules-table`. -->
<!-- Begin auto-generated sections. -->
### Pyflakes
For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| F401 | UnusedImport | `...` imported but unused | 🛠 |
@@ -311,7 +327,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| 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 | |
| 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 | |
@@ -325,9 +341,11 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| 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` | |
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` | 🛠 |
### pycodestyle (error)
### pycodestyle
For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
@@ -345,16 +363,21 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| E743 | AmbiguousFunctionName | Ambiguous function name: `...` | |
| E902 | IOError | IOError: `...` | |
| E999 | SyntaxError | SyntaxError: `...` | |
### pycodestyle (warning)
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| W292 | NoNewLineAtEndOfFile | No newline at end of file | |
| W605 | InvalidEscapeSequence | Invalid escape sequence: '\c' | |
### isort
For more, see [isort](https://pypi.org/project/isort/5.10.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| I001 | UnsortedImports | Import block is un-sorted or un-formatted | 🛠 |
### pydocstyle
For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| D100 | PublicModule | Missing docstring in public module | |
@@ -404,6 +427,8 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
### pyupgrade
For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| U001 | UselessMetaclassType | `__metaclass__ = type` is implied | 🛠 |
@@ -414,9 +439,14 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| U006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | 🛠 |
| U007 | UsePEP604Annotation | Use `X \| Y` for type annotations | 🛠 |
| U008 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | 🛠 |
| U009 | PEP3120UnnecessaryCodingComment | utf-8 encoding declaration is unnecessary | 🛠 |
| U010 | UnnecessaryFutureImport | Unnessary __future__ import `...` for target Python version | |
| U011 | UnnecessaryLRUCacheParams | Unnessary parameters to functools.lru_cache | 🛠 |
### pep8-naming
For more, see [pep8-naming](https://pypi.org/project/pep8-naming/0.13.2/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| N801 | InvalidClassName | Class name `...` should use CapWords convention | |
@@ -437,15 +467,17 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
### flake8-comprehensions
For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/3.10.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| 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) | |
| 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) | |
| C406 | UnnecessaryLiteralDict | Unnecessary `(list\|tuple)` literal (rewrite as a `dict` literal) | 🛠 |
| C408 | UnnecessaryCollectionCall | Unnecessary `(dict\|list\|tuple)` call (rewrite as a literal) | 🛠 |
| C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary `(list\|tuple)` literal passed to `tuple()` (remove the outer call to `tuple()`) | 🛠 |
| C410 | UnnecessaryLiteralWithinListCall | Unnecessary `(list\|tuple)` literal passed to `list()` (rewrite as a `list` literal) | 🛠 |
@@ -453,24 +485,37 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| C413 | UnnecessaryCallAroundSorted | Unnecessary `(list\|reversed)` call around `sorted()` | |
| C414 | UnnecessaryDoubleCastOrProcess | Unnecessary `(list\|reversed\|set\|sorted\|tuple)` call within `(list\|set\|sorted\|tuple)()` | |
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within `(reversed\|set\|sorted)()` | |
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | |
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | 🛠 |
| C417 | UnnecessaryMap | Unnecessary `map` usage (rewrite using a `(list\|set\|dict)` comprehension) | |
### flake8-bugbear
For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| B002 | UnaryPrefixIncrement | Python does not support the unary prefix increment. | |
| B003 | AssignmentToOsEnviron | Assigning to `os.environ` doesn't clear the environment. | |
| B004 | UnreliableCallableCheck | Using `hasattr(x, '__call__')` to test if x is callable is unreliable. Use `callable(x)` for consistent results. | |
| B005 | StripWithMultiCharacters | Using `.strip()` with multi-character strings is misleading the reader. | |
| B006 | MutableArgumentDefault | Do not use mutable data structures for argument defaults. | |
| B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body. | 🛠 |
| B008 | FunctionCallArgumentDefault | Do not perform function calls in argument defaults. | |
| B009 | GetAttrWithConstant | Do not call `getattr` with a constant attribute value, it is not any safer than normal property access. | |
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
| B013 | RedundantTupleInExceptionHandler | A length-one tuple literal is redundant. Write `except ValueError:` instead of `except (ValueError,):`. | |
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | 🛠 |
| B015 | UselessComparison | Pointless comparison. This comparison does nothing but waste CPU instructions. Either prepend `assert` or remove it. | |
| B016 | CannotRaiseLiteral | Cannot raise a literal. Did you intend to return it or raise an Exception? | |
| B017 | NoAssertRaisesException | `assertRaises(Exception):` should be considered evil. | |
| B018 | UselessExpression | Found useless expression. Either assign it to a variable or remove it. | |
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` | |
| B026 | StarArgUnpackingAfterKeywordArg | Star-arg unpacking after a keyword argument is strongly discouraged. | |
### flake8-builtins
For more, see [flake8-builtins](https://pypi.org/project/flake8-builtins/2.0.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| A001 | BuiltinVariableShadowing | Variable `...` is shadowing a python builtin | |
@@ -479,6 +524,8 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
### flake8-print
For more, see [flake8-print](https://pypi.org/project/flake8-print/5.0.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| T201 | PrintFound | `print` found | 🛠 |
@@ -486,6 +533,8 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
### flake8-quotes
For more, see [flake8-quotes](https://pypi.org/project/flake8-quotes/3.3.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| Q000 | BadQuotesInlineString | Single quotes found but double quotes preferred | |
@@ -493,14 +542,46 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| Q002 | BadQuotesDocstring | Single quote docstring found but double quotes preferred | |
| Q003 | AvoidQuoteEscape | Change outer quotes to avoid escaping inner quotes | |
### flake8-annotations
For more, see [flake8-annotations](https://pypi.org/project/flake8-annotations/2.9.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ANN001 | MissingTypeFunctionArgument | Missing type annotation for function argument `...` | |
| ANN002 | MissingTypeArgs | Missing type annotation for `*...` | |
| ANN003 | MissingTypeKwargs | Missing type annotation for `**...` | |
| ANN101 | MissingTypeSelf | Missing type annotation for `...` in method | |
| ANN102 | MissingTypeCls | Missing type annotation for `...` in classmethod | |
| ANN201 | MissingReturnTypePublicFunction | Missing return type annotation for public function `...` | |
| ANN202 | MissingReturnTypePrivateFunction | Missing return type annotation for private function `...` | |
| ANN204 | MissingReturnTypeMagicMethod | Missing return type annotation for magic method `...` | |
| ANN205 | MissingReturnTypeStaticMethod | Missing return type annotation for staticmethod `...` | |
| ANN206 | MissingReturnTypeClassMethod | Missing return type annotation for classmethod `...` | |
| ANN401 | DynamicallyTypedExpression | Dynamically typed expressions (typing.Any) are disallowed in `...` | |
### Ruff-specific rules
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| RUF001 | AmbiguousUnicodeCharacterString | String contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
| RUF002 | AmbiguousUnicodeCharacterDocstring | Docstring contains ambiguous unicode character '𝐁' (did you mean 'B'?) | 🛠 |
| RUF003 | AmbiguousUnicodeCharacterComment | Comment contains ambiguous unicode character '𝐁' (did you mean 'B'?) | |
### Meta rules
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| M001 | UnusedNOQA | Unused `noqa` directive | 🛠 |
<!-- End auto-generated sections. -->
## Editor Integrations
### VS Code (Official)
Download the [Ruff VS Code extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff).
### PyCharm
Ruff can be installed as an [External Tool](https://www.jetbrains.com/help/pycharm/configuring-third-party-tools.html)
@@ -513,6 +594,19 @@ Ruff should then appear as a runnable action:
![Ruff as a runnable action](https://user-images.githubusercontent.com/1309177/193156026-732b0aaf-3dd9-4549-9b4d-2de6d2168a33.png)
### Vim & Neovim (Unofficial)
Ruff is available as part of the [coc-pyright](https://github.com/fannheyward/coc-pyright) extension
for coc.nvim.
Ruff can also be integrated via [efm](https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#efm)
in just a [few lines](https://github.com/JafarAbdi/myconfigs/blob/6f0b6b2450e92ec8fc50422928cd22005b919110/efm-langserver/config.yaml#L14-L20).
### Language Server Protocol (Unofficial)
[`ruffd`](https://github.com/Seamooo/ruffd) is a Rust-based language server for Ruff that implements
the Language Server Protocol (LSP).
### GitHub Actions
GitHub Actions has everything you need to run Ruff out-of-the-box:
@@ -549,6 +643,9 @@ stylistic lint rules that are obviated by autoformatting.
### How does Ruff compare to Flake8?
(Coming from Flake8? Try [`flake8-to-ruff`](https://pypi.org/project/flake8-to-ruff/) to
automatically convert your existing configuration.)
Ruff can be used as a (near) drop-in replacement for Flake8 when used (1) without or with a small
number of plugins, (2) alongside Black, and (3) on Python 3 code.
@@ -566,9 +663,10 @@ including:
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (10/32)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (10/34)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (19/32)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/34)
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
Beyond rule-set parity, Ruff suffers from the following limitations vis-à-vis Flake8:
@@ -588,11 +686,12 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (10/32)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (19/32)
Ruff also implements the functionality that you get from [`yesqa`](https://github.com/asottile/yesqa),
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (10/34).
Ruff can also replace [`isort`](https://pypi.org/project/isort/), [`yesqa`](https://github.com/asottile/yesqa),
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/34).
If you're looking to use Ruff, but rely on an unsupported Flake8 plugin, free to file an Issue.
@@ -612,6 +711,31 @@ on Rust at all.
Ruff does not yet support third-party plugins, though a plugin system is within-scope for the
project. See [#283](https://github.com/charliermarsh/ruff/issues/283) for more.
### How does Ruff's import sorting compare to [`isort`](https://pypi.org/project/isort/)?
Ruff's import sorting is intended to be equivalent to `isort` when used `profile = "black"` and
`combine_as_imports = true`. Like `isort`, Ruff's import sorting is compatible with Black.
Ruff is less configurable than `isort`, but supports the `known-first-party`, `known-third-party`,
`extra-standard-library`, and `src` settings, like so:
```toml
[tool.ruff]
select = [
# Pyflakes
"F",
# Pycodestyle
"E",
"W",
# isort
"I"
]
src = ["src", "tests"]
[tool.ruff.isort]
known-first-party = ["my_module1", "my_module2"]
```
### Does Ruff support NumPy- or Google-style docstrings?
Yes! To enable a specific docstring convention, start by enabling all `pydocstyle` error codes, and
@@ -684,7 +808,7 @@ extend-ignore = [
## Development
Ruff is written in Rust (1.64.0). You'll need to install the [Rust toolchain](https://www.rust-lang.org/tools/install)
Ruff is written in Rust (1.65.0). You'll need to install the [Rust toolchain](https://www.rust-lang.org/tools/install)
for development.
Assuming you have `cargo` installed, you can run:

View File

@@ -1,13 +1,16 @@
use std::path::Path;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use ropey::Rope;
use ruff::fs;
use ruff::source_code_locator::compute_offsets;
fn criterion_benchmark(c: &mut Criterion) {
let contents = fs::read_file(Path::new("resources/test/fixtures/D.py")).unwrap();
c.bench_function("compute_offsets", |b| {
b.iter(|| compute_offsets(black_box(&contents)))
c.bench_function("rope", |b| {
b.iter(|| {
let rope = Rope::from_str(black_box(&contents));
rope.line_to_char(black_box(4));
})
});
}

View File

@@ -1,28 +0,0 @@
//! Generate a Markdown-compatible table of supported lint rules.
use ruff::checks::{CheckCategory, CheckCode};
use strum::IntoEnumIterator;
fn main() {
for check_category in CheckCategory::iter() {
println!("### {}", check_category.title());
println!();
println!("| Code | Name | Message | Fix |");
println!("| ---- | ---- | ------- | --- |");
for check_code in CheckCode::iter() {
if check_code.category() == check_category {
let check_kind = check_code.kind();
let fix_token = if check_kind.fixable() { "🛠" } else { "" };
println!(
"| {} | {} | {} | {} |",
check_kind.code().as_ref(),
check_kind.as_ref(),
check_kind.summary().replace("|", r"\|"),
fix_token
);
}
}
println!();
}
}

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.98"
version = "0.0.110"
dependencies = [
"anyhow",
"clap",
@@ -1265,7 +1265,7 @@ checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
[[package]]
name = "libcst"
version = "0.1.0"
source = "git+https://github.com/charliermarsh/LibCST?rev=32a044c127668df44582f85699358e67803b0d73#32a044c127668df44582f85699358e67803b0d73"
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
dependencies = [
"chic",
"itertools",
@@ -1280,7 +1280,7 @@ dependencies = [
[[package]]
name = "libcst_derive"
version = "0.1.0"
source = "git+https://github.com/charliermarsh/LibCST?rev=32a044c127668df44582f85699358e67803b0d73#32a044c127668df44582f85699358e67803b0d73"
source = "git+https://github.com/charliermarsh/LibCST?rev=a13ec97dd4eb925bde4d426c6e422582793b260c#a13ec97dd4eb925bde4d426c6e422582793b260c"
dependencies = [
"quote",
"syn",
@@ -1505,7 +1505,7 @@ dependencies = [
[[package]]
name = "num_cpus"
version = "1.13.1"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.98"
version = "0.0.110"
dependencies = [
"anyhow",
"bincode",
@@ -2028,7 +2028,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.1.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
source = "git+https://github.com/RustPython/RustPython.git?rev=27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb#27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb"
dependencies = [
"num-bigint",
"rustpython-common",
@@ -2038,7 +2038,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.0.0"
source = "git+https://github.com/RustPython/RustPython.git?rev=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
source = "git+https://github.com/RustPython/RustPython.git?rev=27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb#27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb"
dependencies = [
"ascii",
"cfg-if 1.0.0",
@@ -2061,7 +2061,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
source = "git+https://github.com/RustPython/RustPython.git?rev=27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb#27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb"
dependencies = [
"bincode",
"bitflags",
@@ -2078,7 +2078,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.1.2"
source = "git+https://github.com/RustPython/RustPython.git?rev=77b821a1941019fe34f73ce17cea013ae1b98fd0#77b821a1941019fe34f73ce17cea013ae1b98fd0"
source = "git+https://github.com/RustPython/RustPython.git?rev=27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb#27bf82a2251d7e6ac6cd75e6ad51be12a53d84bb"
dependencies = [
"ahash",
"anyhow",

View File

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

View File

@@ -17,7 +17,7 @@ pip install flake8-to-ruff
### Usage
To run Ruff, try any of the following:
To run `flake8-to-ruff`:
```shell
flake8-to-ruff path/to/setup.cfg
@@ -25,6 +25,60 @@ flake8-to-ruff path/to/tox.ini
flake8-to-ruff path/to/.flake8
```
`flake8-to-ruff` will print the relevant `pyproject.toml` sections to standard output, like so:
```toml
[tool.ruff]
exclude = [
'.svn',
'CVS',
'.bzr',
'.hg',
'.git',
'__pycache__',
'.tox',
'.idea',
'.mypy_cache',
'.venv',
'node_modules',
'_state_machine.py',
'test_fstring.py',
'bad_coding2.py',
'badsyntax_*.py',
]
select = [
'A',
'E',
'F',
'Q',
]
ignore = []
[tool.ruff.flake8-quotes]
inline-quotes = 'single'
[tool.ruff.pep8-naming]
ignore-names = [
'foo',
'bar',
]
```
### Plugins
`flake8-to-ruff` will attempt to infer any activated plugins based on the settings provided in your
configuration file.
For example, if your `.flake8` file includes a `docstring-convention` property, `flake8-to-ruff`
will enable the appropriate [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
checks.
Alternatively, you can manually specify plugins on the command-line:
```shell
flake8-to-ruff path/to/.flake8 --plugin flake8-builtins --plugin flake8-quotes
```
## Limitations
1. Ruff only supports a subset of the Flake configuration options. `flake8-to-ruff` will warn on and
@@ -33,9 +87,6 @@ flake8-to-ruff path/to/.flake8
2. Ruff will omit any error codes that are unimplemented or unsupported by Ruff, including error
codes from unsupported plugins. (See the [Ruff README](https://github.com/charliermarsh/ruff#user-content-how-does-ruff-compare-to-flake8)
for the complete list of supported plugins.)
3. `flake8-to-ruff` does not auto-detect your Flake8 plugins, so any reliance on Flake8 plugins that
implicitly enable third-party checks will be ignored. Instead, add those error codes to your
`select` or `extend-select` fields, so that `flake8-to-ruff` can pick them up.
## License

View File

@@ -0,0 +1,36 @@
[flake8]
min_python_version = 3.7.0
max-line-length = 88
ban-relative-imports = true
# flake8-use-fstring: https://github.com/MichaelKim0407/flake8-use-fstring#--percent-greedy-and---format-greedy
format-greedy = 1
inline-quotes = double
enable-extensions = TC, TC1
type-checking-strict = true
eradicate-whitelist-extend = ^-.*;
extend-ignore =
# E203: Whitespace before ':' (pycqa/pycodestyle#373)
E203,
# SIM106: Handle error-cases first
SIM106,
# ANN101: Missing type annotation for self in method
ANN101,
# ANN102: Missing type annotation for cls in classmethod
ANN102,
# PIE781: assign-and-return
PIE781,
# PIE798 no-unnecessary-class: Consider using a module for namespacing instead
PIE798,
per-file-ignores =
# TC002: Move third-party import '...' into a type-checking block
__init__.py:TC002,
# ANN201: Missing return type annotation for public function
tests/test_*:ANN201
tests/**/test_*:ANN201
extend-exclude =
# Frozen and not subject to change in this repo:
get-poetry.py,
install-poetry.py,
# External to the project's coding standards:
tests/fixtures/*,
tests/**/fixtures/*,

View File

@@ -0,0 +1,19 @@
[flake8]
max-line-length=120
docstring-convention=all
import-order-style=pycharm
application_import_names=bot,tests
exclude=.cache,.venv,.git,constants.py
extend-ignore=
B311,W503,E226,S311,T000,E731
# Missing Docstrings
D100,D104,D105,D107,
# Docstring Whitespace
D203,D212,D214,D215,
# Docstring Quotes
D301,D302,
# Docstring Content
D400,D401,D402,D404,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414,D416,D417
# Type Annotations
ANN002,ANN003,ANN101,ANN102,ANN204,ANN206,ANN401
per-file-ignores=tests/*:D,ANN

View File

@@ -0,0 +1,6 @@
[flake8]
ignore = E203, E501, W503
per-file-ignores =
requests/__init__.py:E402, F401
requests/compat.py:E402, F401
tests/compat.py:F401

View File

@@ -1,21 +1,74 @@
use std::collections::HashMap;
use std::collections::{BTreeSet, HashMap};
use anyhow::Result;
use ruff::checks_gen::CheckCodePrefix;
use ruff::flake8_quotes::settings::Quote;
use ruff::settings::options::Options;
use ruff::settings::pyproject::Pyproject;
use ruff::{flake8_quotes, pep8_naming};
use ruff::{flake8_annotations, flake8_quotes, pep8_naming};
use crate::parser;
use crate::plugin::Plugin;
use crate::{parser, plugin};
pub fn convert(config: HashMap<String, HashMap<String, Option<String>>>) -> Result<Pyproject> {
// Extract the Flake8 section.
let flake8 = config
.get("flake8")
.expect("Unable to find flake8 section in INI file.");
pub fn convert(
flake8: &HashMap<String, Option<String>>,
plugins: Option<Vec<Plugin>>,
) -> Result<Pyproject> {
// Extract all referenced check code prefixes, to power plugin inference.
let mut referenced_codes: BTreeSet<CheckCodePrefix> = Default::default();
for (key, value) in flake8 {
if let Some(value) = value {
match key.as_str() {
"select" | "ignore" | "extend-select" | "extend_select" | "extend-ignore"
| "extend_ignore" => {
referenced_codes.extend(parser::parse_prefix_codes(value.as_ref()));
}
"per-file-ignores" | "per_file_ignores" => {
if let Ok(per_file_ignores) =
parser::parse_files_to_codes_mapping(value.as_ref())
{
for (_, codes) in parser::collect_per_file_ignores(per_file_ignores) {
referenced_codes.extend(codes);
}
}
}
_ => {}
}
}
}
// Check if the user has specified a `select`. If not, we'll add our own
// default `select`, and populate it based on user plugins.
let mut select = flake8
.get("select")
.and_then(|value| {
value
.as_ref()
.map(|value| BTreeSet::from_iter(parser::parse_prefix_codes(value)))
})
.unwrap_or_else(|| {
plugin::resolve_select(
flake8,
&plugins.unwrap_or_else(|| {
plugin::infer_plugins_from_options(flake8)
.into_iter()
.chain(plugin::infer_plugins_from_codes(&referenced_codes))
.collect()
}),
)
});
let mut ignore = flake8
.get("ignore")
.and_then(|value| {
value
.as_ref()
.map(|value| BTreeSet::from_iter(parser::parse_prefix_codes(value)))
})
.unwrap_or_default();
// Parse each supported option.
let mut options: Options = Default::default();
let mut flake8_annotations: flake8_annotations::settings::Options = Default::default();
let mut flake8_quotes: flake8_quotes::settings::Options = Default::default();
let mut pep8_naming: pep8_naming::settings::Options = Default::default();
for (key, value) in flake8 {
@@ -27,16 +80,19 @@ pub fn convert(config: HashMap<String, HashMap<String, Option<String>>>) -> Resu
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
},
"select" => {
options.select = Some(parser::parse_prefix_codes(value.as_ref()));
}
"extend-select" | "extend_select" => {
options.extend_select = Some(parser::parse_prefix_codes(value.as_ref()));
// No-op (handled above).
select.extend(parser::parse_prefix_codes(value.as_ref()));
}
"ignore" => {
options.ignore = Some(parser::parse_prefix_codes(value.as_ref()));
// No-op (handled above).
}
"extend-select" | "extend_select" => {
// Unlike Flake8, use a single explicit `select`.
select.extend(parser::parse_prefix_codes(value.as_ref()));
}
"extend-ignore" | "extend_ignore" => {
options.extend_ignore = Some(parser::parse_prefix_codes(value.as_ref()));
// Unlike Flake8, use a single explicit `ignore`.
ignore.extend(parser::parse_prefix_codes(value.as_ref()));
}
"exclude" => {
options.exclude = Some(parser::parse_strings(value.as_ref()));
@@ -53,6 +109,31 @@ pub fn convert(config: HashMap<String, HashMap<String, Option<String>>>) -> Resu
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}
// flake8-annotations
"suppress-none-returning" | "suppress_none_returning" => {
match parser::parse_bool(value.as_ref()) {
Ok(bool) => flake8_annotations.suppress_none_returning = Some(bool),
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}
"suppress-dummy-args" | "suppress_dummy_args" => {
match parser::parse_bool(value.as_ref()) {
Ok(bool) => flake8_annotations.suppress_dummy_args = Some(bool),
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}
"mypy-init-return" | "mypy_init_return" => {
match parser::parse_bool(value.as_ref()) {
Ok(bool) => flake8_annotations.mypy_init_return = Some(bool),
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}
"allow-star-arg-any" | "allow_star_arg_any" => {
match parser::parse_bool(value.as_ref()) {
Ok(bool) => flake8_annotations.allow_star_arg_any = Some(bool),
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
}
}
// flake8-quotes
"quotes" | "inline-quotes" | "inline_quotes" => match value.trim() {
"'" | "single" => flake8_quotes.inline_quotes = Some(Quote::Single),
@@ -69,10 +150,9 @@ pub fn convert(config: HashMap<String, HashMap<String, Option<String>>>) -> Resu
"\"" | "double" => flake8_quotes.docstring_quotes = Some(Quote::Single),
_ => eprintln!("Unexpected '{key}' value: {value}"),
},
"avoid-escape" | "avoid_escape" => match value.trim() {
"true" => flake8_quotes.avoid_escape = Some(true),
"false" => flake8_quotes.avoid_escape = Some(false),
_ => eprintln!("Unexpected '{key}' value: {value}"),
"avoid-escape" | "avoid_escape" => match parser::parse_bool(value.as_ref()) {
Ok(bool) => flake8_quotes.avoid_escape = Some(bool),
Err(e) => eprintln!("Unable to parse '{key}' property: {e}"),
},
// pep8-naming
"ignore-names" | "ignore_names" => {
@@ -86,12 +166,19 @@ pub fn convert(config: HashMap<String, HashMap<String, Option<String>>>) -> Resu
pep8_naming.staticmethod_decorators =
Some(parser::parse_strings(value.as_ref()));
}
// flake8-docstrings
"docstring-convention" => {
// No-op (handled above).
}
// Unknown
_ => eprintln!("Skipping unsupported property: {key}"),
}
}
}
// Deduplicate and sort.
options.select = Some(Vec::from_iter(select));
options.ignore = Some(Vec::from_iter(ignore));
if flake8_quotes != Default::default() {
options.flake8_quotes = Some(flake8_quotes);
}
@@ -108,27 +195,37 @@ mod tests {
use std::collections::HashMap;
use anyhow::Result;
use ruff::checks_gen::CheckCodePrefix;
use ruff::flake8_quotes;
use ruff::settings::options::Options;
use ruff::settings::pyproject::Pyproject;
use crate::converter::convert;
use crate::plugin::Plugin;
#[test]
fn it_converts_empty() -> Result<()> {
let actual = convert(HashMap::from([("flake8".to_string(), HashMap::from([]))]))?;
let actual = convert(&HashMap::from([]), None)?;
let expected = Pyproject::new(Options {
line_length: None,
src: None,
fix: None,
exclude: None,
extend_exclude: None,
select: None,
select: Some(vec![
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::W,
]),
extend_select: None,
ignore: None,
ignore: Some(vec![]),
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_annotations: None,
flake8_quotes: None,
isort: None,
pep8_naming: None,
});
assert_eq!(actual, expected);
@@ -138,22 +235,30 @@ mod tests {
#[test]
fn it_converts_dashes() -> Result<()> {
let actual = convert(HashMap::from([(
"flake8".to_string(),
HashMap::from([("max-line-length".to_string(), Some("100".to_string()))]),
)]))?;
let actual = convert(
&HashMap::from([("max-line-length".to_string(), Some("100".to_string()))]),
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
line_length: Some(100),
src: None,
fix: None,
exclude: None,
extend_exclude: None,
select: None,
select: Some(vec![
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::W,
]),
extend_select: None,
ignore: None,
ignore: Some(vec![]),
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_annotations: None,
flake8_quotes: None,
isort: None,
pep8_naming: None,
});
assert_eq!(actual, expected);
@@ -163,22 +268,30 @@ mod tests {
#[test]
fn it_converts_underscores() -> Result<()> {
let actual = convert(HashMap::from([(
"flake8".to_string(),
HashMap::from([("max_line_length".to_string(), Some("100".to_string()))]),
)]))?;
let actual = convert(
&HashMap::from([("max_line_length".to_string(), Some("100".to_string()))]),
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
line_length: Some(100),
src: None,
fix: None,
exclude: None,
extend_exclude: None,
select: None,
select: Some(vec![
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::W,
]),
extend_select: None,
ignore: None,
ignore: Some(vec![]),
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_annotations: None,
flake8_quotes: None,
isort: None,
pep8_naming: None,
});
assert_eq!(actual, expected);
@@ -188,22 +301,30 @@ mod tests {
#[test]
fn it_ignores_parse_errors() -> Result<()> {
let actual = convert(HashMap::from([(
"flake8".to_string(),
HashMap::from([("max_line_length".to_string(), Some("abc".to_string()))]),
)]))?;
let actual = convert(
&HashMap::from([("max_line_length".to_string(), Some("abc".to_string()))]),
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
line_length: None,
src: None,
fix: None,
exclude: None,
extend_exclude: None,
select: None,
select: Some(vec![
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::W,
]),
extend_select: None,
ignore: None,
ignore: Some(vec![]),
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_annotations: None,
flake8_quotes: None,
isort: None,
pep8_naming: None,
});
assert_eq!(actual, expected);
@@ -212,28 +333,146 @@ mod tests {
}
#[test]
fn it_converts_extensions() -> Result<()> {
let actual = convert(HashMap::from([(
"flake8".to_string(),
HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
)]))?;
fn it_converts_plugin_options() -> Result<()> {
let actual = convert(
&HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
Some(vec![]),
)?;
let expected = Pyproject::new(Options {
line_length: None,
src: None,
fix: None,
exclude: None,
extend_exclude: None,
select: None,
select: Some(vec![
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::W,
]),
extend_select: None,
ignore: None,
ignore: Some(vec![]),
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_annotations: None,
flake8_quotes: Some(flake8_quotes::settings::Options {
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
multiline_quotes: None,
docstring_quotes: None,
avoid_escape: None,
}),
isort: None,
pep8_naming: None,
});
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn it_converts_docstring_conventions() -> Result<()> {
let actual = convert(
&HashMap::from([(
"docstring-convention".to_string(),
Some("numpy".to_string()),
)]),
Some(vec![Plugin::Flake8Docstrings]),
)?;
let expected = Pyproject::new(Options {
line_length: None,
src: None,
fix: None,
exclude: None,
extend_exclude: None,
select: Some(vec![
CheckCodePrefix::D100,
CheckCodePrefix::D101,
CheckCodePrefix::D102,
CheckCodePrefix::D103,
CheckCodePrefix::D104,
CheckCodePrefix::D105,
CheckCodePrefix::D106,
CheckCodePrefix::D200,
CheckCodePrefix::D201,
CheckCodePrefix::D202,
CheckCodePrefix::D204,
CheckCodePrefix::D205,
CheckCodePrefix::D206,
CheckCodePrefix::D207,
CheckCodePrefix::D208,
CheckCodePrefix::D209,
CheckCodePrefix::D210,
CheckCodePrefix::D211,
CheckCodePrefix::D214,
CheckCodePrefix::D215,
CheckCodePrefix::D300,
CheckCodePrefix::D400,
CheckCodePrefix::D403,
CheckCodePrefix::D404,
CheckCodePrefix::D405,
CheckCodePrefix::D406,
CheckCodePrefix::D407,
CheckCodePrefix::D408,
CheckCodePrefix::D409,
CheckCodePrefix::D410,
CheckCodePrefix::D411,
CheckCodePrefix::D412,
CheckCodePrefix::D414,
CheckCodePrefix::D418,
CheckCodePrefix::D419,
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::W,
]),
extend_select: None,
ignore: Some(vec![]),
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_annotations: None,
flake8_quotes: None,
isort: None,
pep8_naming: None,
});
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn it_infers_plugins_if_omitted() -> Result<()> {
let actual = convert(
&HashMap::from([("inline-quotes".to_string(), Some("single".to_string()))]),
None,
)?;
let expected = Pyproject::new(Options {
line_length: None,
src: None,
fix: None,
exclude: None,
extend_exclude: None,
select: Some(vec![
CheckCodePrefix::E,
CheckCodePrefix::F,
CheckCodePrefix::Q,
CheckCodePrefix::W,
]),
extend_select: None,
ignore: Some(vec![]),
extend_ignore: None,
per_file_ignores: None,
dummy_variable_rgx: None,
target_version: None,
flake8_annotations: None,
flake8_quotes: Some(flake8_quotes::settings::Options {
inline_quotes: Some(flake8_quotes::settings::Quote::Single),
multiline_quotes: None,
docstring_quotes: None,
avoid_escape: None,
}),
isort: None,
pep8_naming: None,
});
assert_eq!(actual, expected);

View File

@@ -2,3 +2,4 @@
pub mod converter;
mod parser;
pub mod plugin;

View File

@@ -6,6 +6,7 @@ use anyhow::Result;
use clap::Parser;
use configparser::ini::Ini;
use flake8_to_ruff::converter;
use flake8_to_ruff::plugin::Plugin;
#[derive(Parser)]
#[command(
@@ -17,6 +18,9 @@ struct Cli {
/// '.flake8').
#[arg(required = true)]
file: PathBuf,
/// List of plugins to enable.
#[arg(long, value_delimiter = ',')]
plugin: Option<Vec<Plugin>>,
}
fn main() -> Result<()> {
@@ -27,8 +31,13 @@ fn main() -> Result<()> {
ini.set_multiline(true);
let config = ini.load(cli.file).map_err(|msg| anyhow::anyhow!(msg))?;
// Extract the Flake8 section.
let flake8 = config
.get("flake8")
.expect("Unable to find flake8 section in INI file.");
// Create the pyproject.toml.
let pyproject = converter::convert(config)?;
let pyproject = converter::convert(flake8, cli.plugin)?;
println!("{}", toml::to_string_pretty(&pyproject)?);
Ok(())

View File

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

View File

@@ -0,0 +1,396 @@
use std::collections::{BTreeSet, HashMap};
use std::str::FromStr;
use anyhow::anyhow;
use ruff::checks_gen::CheckCodePrefix;
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum Plugin {
Flake8Bugbear,
Flake8Builtins,
Flake8Comprehensions,
Flake8Docstrings,
Flake8Print,
Flake8Quotes,
Flake8Annotations,
PEP8Naming,
Pyupgrade,
}
impl FromStr for Plugin {
type Err = anyhow::Error;
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
"flake8-bugbear" => Ok(Plugin::Flake8Bugbear),
"flake8-builtins" => Ok(Plugin::Flake8Builtins),
"flake8-comprehensions" => Ok(Plugin::Flake8Comprehensions),
"flake8-docstrings" => Ok(Plugin::Flake8Docstrings),
"flake8-print" => Ok(Plugin::Flake8Print),
"flake8-quotes" => Ok(Plugin::Flake8Quotes),
"flake8-annotations" => Ok(Plugin::Flake8Annotations),
"pep8-naming" => Ok(Plugin::PEP8Naming),
"pyupgrade" => Ok(Plugin::Pyupgrade),
_ => Err(anyhow!("Unknown plugin: {}", string)),
}
}
}
impl Plugin {
pub fn default(&self) -> CheckCodePrefix {
match self {
Plugin::Flake8Bugbear => CheckCodePrefix::B,
Plugin::Flake8Builtins => CheckCodePrefix::A,
Plugin::Flake8Comprehensions => CheckCodePrefix::C,
Plugin::Flake8Docstrings => CheckCodePrefix::D,
Plugin::Flake8Print => CheckCodePrefix::T,
Plugin::Flake8Quotes => CheckCodePrefix::Q,
Plugin::Flake8Annotations => CheckCodePrefix::ANN,
Plugin::PEP8Naming => CheckCodePrefix::N,
Plugin::Pyupgrade => CheckCodePrefix::U,
}
}
pub fn select(&self, flake8: &HashMap<String, Option<String>>) -> Vec<CheckCodePrefix> {
match self {
Plugin::Flake8Bugbear => vec![CheckCodePrefix::B],
Plugin::Flake8Builtins => vec![CheckCodePrefix::A],
Plugin::Flake8Comprehensions => vec![CheckCodePrefix::C],
Plugin::Flake8Docstrings => {
// Use the user-provided docstring.
for key in ["docstring-convention", "docstring_convention"] {
if let Some(Some(value)) = flake8.get(key) {
match DocstringConvention::from_str(value) {
Ok(convention) => return convention.select(),
Err(e) => {
eprintln!("Unexpected '{key}' value: {value} ({e}");
return vec![];
}
}
}
}
// Default to PEP8.
DocstringConvention::PEP8.select()
}
Plugin::Flake8Print => vec![CheckCodePrefix::T],
Plugin::Flake8Quotes => vec![CheckCodePrefix::Q],
Plugin::Flake8Annotations => vec![CheckCodePrefix::ANN],
Plugin::PEP8Naming => vec![CheckCodePrefix::N],
Plugin::Pyupgrade => vec![CheckCodePrefix::U],
}
}
}
pub enum DocstringConvention {
All,
PEP8,
NumPy,
Google,
}
impl FromStr for DocstringConvention {
type Err = anyhow::Error;
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
"all" => Ok(DocstringConvention::All),
"pep8" => Ok(DocstringConvention::PEP8),
"numpy" => Ok(DocstringConvention::NumPy),
"google" => Ok(DocstringConvention::Google),
_ => Err(anyhow!("Unknown docstring convention: {}", string)),
}
}
}
impl DocstringConvention {
fn select(&self) -> Vec<CheckCodePrefix> {
match self {
DocstringConvention::All => vec![CheckCodePrefix::D],
DocstringConvention::PEP8 => vec![
// All errors except D203, D212, D213, D214, D215, D404, D405, D406, D407, D408,
// D409, D410, D411, D413, D415, D416 and D417.
CheckCodePrefix::D100,
CheckCodePrefix::D101,
CheckCodePrefix::D102,
CheckCodePrefix::D103,
CheckCodePrefix::D104,
CheckCodePrefix::D105,
CheckCodePrefix::D106,
CheckCodePrefix::D107,
CheckCodePrefix::D200,
CheckCodePrefix::D201,
CheckCodePrefix::D202,
// CheckCodePrefix::D203,
CheckCodePrefix::D204,
CheckCodePrefix::D205,
CheckCodePrefix::D206,
CheckCodePrefix::D207,
CheckCodePrefix::D208,
CheckCodePrefix::D209,
CheckCodePrefix::D210,
CheckCodePrefix::D211,
// CheckCodePrefix::D212,
// CheckCodePrefix::D213,
// CheckCodePrefix::D214,
// CheckCodePrefix::D215,
CheckCodePrefix::D300,
CheckCodePrefix::D400,
CheckCodePrefix::D402,
CheckCodePrefix::D403,
// CheckCodePrefix::D404,
// CheckCodePrefix::D405,
// CheckCodePrefix::D406,
// CheckCodePrefix::D407,
// CheckCodePrefix::D408,
// CheckCodePrefix::D409,
// CheckCodePrefix::D410,
// CheckCodePrefix::D411,
CheckCodePrefix::D412,
// CheckCodePrefix::D413,
CheckCodePrefix::D414,
// CheckCodePrefix::D415,
// CheckCodePrefix::D416,
// CheckCodePrefix::D417,
CheckCodePrefix::D418,
CheckCodePrefix::D419,
],
DocstringConvention::NumPy => vec![
// All errors except D107, D203, D212, D213, D402, D413, D415, D416, and D417.
CheckCodePrefix::D100,
CheckCodePrefix::D101,
CheckCodePrefix::D102,
CheckCodePrefix::D103,
CheckCodePrefix::D104,
CheckCodePrefix::D105,
CheckCodePrefix::D106,
// CheckCodePrefix::D107,
CheckCodePrefix::D200,
CheckCodePrefix::D201,
CheckCodePrefix::D202,
// CheckCodePrefix::D203,
CheckCodePrefix::D204,
CheckCodePrefix::D205,
CheckCodePrefix::D206,
CheckCodePrefix::D207,
CheckCodePrefix::D208,
CheckCodePrefix::D209,
CheckCodePrefix::D210,
CheckCodePrefix::D211,
// CheckCodePrefix::D212,
// CheckCodePrefix::D213,
CheckCodePrefix::D214,
CheckCodePrefix::D215,
CheckCodePrefix::D300,
CheckCodePrefix::D400,
// CheckCodePrefix::D402,
CheckCodePrefix::D403,
CheckCodePrefix::D404,
CheckCodePrefix::D405,
CheckCodePrefix::D406,
CheckCodePrefix::D407,
CheckCodePrefix::D408,
CheckCodePrefix::D409,
CheckCodePrefix::D410,
CheckCodePrefix::D411,
CheckCodePrefix::D412,
// CheckCodePrefix::D413,
CheckCodePrefix::D414,
// CheckCodePrefix::D415,
// CheckCodePrefix::D416,
// CheckCodePrefix::D417,
CheckCodePrefix::D418,
CheckCodePrefix::D419,
],
DocstringConvention::Google => vec![
// All errors except D203, D204, D213, D215, D400, D401, D404, D406, D407, D408,
// D409 and D413.
CheckCodePrefix::D100,
CheckCodePrefix::D101,
CheckCodePrefix::D102,
CheckCodePrefix::D103,
CheckCodePrefix::D104,
CheckCodePrefix::D105,
CheckCodePrefix::D106,
CheckCodePrefix::D107,
CheckCodePrefix::D200,
CheckCodePrefix::D201,
CheckCodePrefix::D202,
// CheckCodePrefix::D203,
// CheckCodePrefix::D204,
CheckCodePrefix::D205,
CheckCodePrefix::D206,
CheckCodePrefix::D207,
CheckCodePrefix::D208,
CheckCodePrefix::D209,
CheckCodePrefix::D210,
CheckCodePrefix::D211,
CheckCodePrefix::D212,
// CheckCodePrefix::D213,
CheckCodePrefix::D214,
// CheckCodePrefix::D215,
CheckCodePrefix::D300,
// CheckCodePrefix::D400,
CheckCodePrefix::D402,
CheckCodePrefix::D403,
// CheckCodePrefix::D404,
CheckCodePrefix::D405,
// CheckCodePrefix::D406,
// CheckCodePrefix::D407,
// CheckCodePrefix::D408,
// CheckCodePrefix::D409,
CheckCodePrefix::D410,
CheckCodePrefix::D411,
CheckCodePrefix::D412,
// CheckCodePrefix::D413,
CheckCodePrefix::D414,
CheckCodePrefix::D415,
CheckCodePrefix::D416,
CheckCodePrefix::D417,
CheckCodePrefix::D418,
CheckCodePrefix::D419,
],
}
}
}
/// Infer the enabled plugins based on user-provided options.
///
/// For example, if the user specified a `mypy-init-return` setting, we should
/// infer that `flake8-annotations` is active.
pub fn infer_plugins_from_options(flake8: &HashMap<String, Option<String>>) -> Vec<Plugin> {
let mut plugins = BTreeSet::new();
for key in flake8.keys() {
match key.as_str() {
// flake8-docstrings
"docstring-convention" | "docstring_convention" => {
plugins.insert(Plugin::Flake8Docstrings);
}
// flake8-builtins
"builtins-ignorelist" | "builtins_ignorelist" => {
plugins.insert(Plugin::Flake8Builtins);
}
// flake8-quotes
"quotes" | "inline-quotes" | "inline_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"multiline-quotes" | "multiline_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"docstring-quotes" | "docstring_quotes" => {
plugins.insert(Plugin::Flake8Quotes);
}
"avoid-escape" | "avoid_escape" => {
plugins.insert(Plugin::Flake8Quotes);
}
// flake8-annotations
"suppress-none-returning" | "suppress_none_returning" => {
plugins.insert(Plugin::Flake8Annotations);
}
"suppress-dummy-args" | "suppress_dummy_args" => {
plugins.insert(Plugin::Flake8Annotations);
}
"allow-untyped-defs" | "allow_untyped_defs" => {
plugins.insert(Plugin::Flake8Annotations);
}
"allow-untyped-nested" | "allow_untyped_nested" => {
plugins.insert(Plugin::Flake8Annotations);
}
"mypy-init-return" | "mypy_init_return" => {
plugins.insert(Plugin::Flake8Annotations);
}
"dispatch-decorators" | "dispatch_decorators" => {
plugins.insert(Plugin::Flake8Annotations);
}
"overload-decorators" | "overload_decorators" => {
plugins.insert(Plugin::Flake8Annotations);
}
"allow-star-arg-any" | "allow_star_arg_any" => {
plugins.insert(Plugin::Flake8Annotations);
}
// pep8-naming
"ignore-names" | "ignore_names" => {
plugins.insert(Plugin::PEP8Naming);
}
"classmethod-decorators" | "classmethod_decorators" => {
plugins.insert(Plugin::PEP8Naming);
}
"staticmethod-decorators" | "staticmethod_decorators" => {
plugins.insert(Plugin::PEP8Naming);
}
_ => {}
}
}
Vec::from_iter(plugins)
}
/// Infer the enabled plugins based on the referenced prefixes.
///
/// For example, if the user ignores `ANN101`, we should infer that
/// `flake8-annotations` is active.
pub fn infer_plugins_from_codes(codes: &BTreeSet<CheckCodePrefix>) -> Vec<Plugin> {
[
Plugin::Flake8Bugbear,
Plugin::Flake8Builtins,
Plugin::Flake8Comprehensions,
Plugin::Flake8Docstrings,
Plugin::Flake8Print,
Plugin::Flake8Quotes,
Plugin::Flake8Annotations,
Plugin::PEP8Naming,
Plugin::Pyupgrade,
]
.into_iter()
.filter(|plugin| {
for prefix in codes {
if prefix
.codes()
.iter()
.any(|code| plugin.default().codes().contains(code))
{
return true;
}
}
false
})
.collect()
}
/// Resolve the set of enabled `CheckCodePrefix` values for the given plugins.
pub fn resolve_select(
flake8: &HashMap<String, Option<String>>,
plugins: &[Plugin],
) -> BTreeSet<CheckCodePrefix> {
// Include default Pyflakes and pycodestyle checks.
let mut select = BTreeSet::from([CheckCodePrefix::E, CheckCodePrefix::F, CheckCodePrefix::W]);
// Add prefix codes for every plugin.
for plugin in plugins {
select.extend(plugin.select(flake8));
}
select
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use crate::plugin::{infer_plugins_from_options, Plugin};
#[test]
fn it_infers_plugins() {
let actual = infer_plugins_from_options(&HashMap::from([(
"inline-quotes".to_string(),
Some("single".to_string()),
)]));
let expected = vec![Plugin::Flake8Quotes];
assert_eq!(actual, expected);
let actual = infer_plugins_from_options(&HashMap::from([(
"staticmethod-decorators".to_string(),
Some("[]".to_string()),
)]));
let expected = vec![Plugin::PEP8Naming];
assert_eq!(actual, expected);
}
}

View File

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

18
resources/test/fixtures/B003.py vendored Normal file
View File

@@ -0,0 +1,18 @@
"""
Should emit:
B003 - on line 9
"""
import os
from os import environ
os.environ = {}
environ = {} # that's fine, assigning a new meaning to the module-level name
class Object:
os = None
o = Object()
o.os.environ = {}

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

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

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

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

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

@@ -0,0 +1,47 @@
"""
Should emit:
B009 - Line 17, 18, 19, 44
B010 - Line 28, 29, 30
"""
# Valid getattr usage
getattr(foo, bar)
getattr(foo, "bar", None)
getattr(foo, "bar{foo}".format(foo="a"), None)
getattr(foo, "bar{foo}".format(foo="a"))
getattr(foo, bar, None)
getattr(foo, "123abc")
getattr(foo, "except")
# Invalid usage
getattr(foo, "bar")
getattr(foo, "_123abc")
getattr(foo, "abc123")
# Valid setattr usage
setattr(foo, bar, None)
setattr(foo, "bar{foo}".format(foo="a"), None)
setattr(foo, "123abc", None)
setattr(foo, "except", None)
# Invalid usage
setattr(foo, "bar", None)
setattr(foo, "_123abc", None)
setattr(foo, "abc123", None)
# Allow use of setattr within lambda expression
# since assignment is not valid in this context.
c = lambda x: setattr(x, "some_attr", 1)
class FakeCookieStore:
def __init__(self, has_setter):
self.cookie_filter = None
if has_setter:
self.setCookieFilter = lambda func: setattr(self, "cookie_filter", func)
# getattr is still flagged within lambda though
c = lambda x: getattr(x, "some_attr")
# should be replaced with
c = lambda x: x.some_attr

27
resources/test/fixtures/B015.py vendored Normal file
View File

@@ -0,0 +1,27 @@
assert 1 == 1
1 == 1
assert 1 in (1, 2)
1 in (1, 2)
if 1 == 2:
pass
def test():
assert 1 in (1, 2)
1 in (1, 2)
data = [x for x in [1, 2, 3] if x in (1, 2)]
class TestClass:
1 == 1
print(1 == 1)

11
resources/test/fixtures/B016.py vendored Normal file
View File

@@ -0,0 +1,11 @@
"""
Should emit:
B016 - on lines 6, 7, and 8
"""
raise False
raise 1
raise "string"
raise Exception(False)
raise Exception(1)
raise Exception("string")

59
resources/test/fixtures/B018.py vendored Normal file
View File

@@ -0,0 +1,59 @@
class Foo1:
"""abc"""
class Foo2:
"""abc"""
a = 2
"str" # Str (no raise)
f"{int}" # JoinedStr (no raise)
1j # Number (complex)
1 # Number (int)
1.0 # Number (float)
b"foo" # Binary
True # NameConstant (True)
False # NameConstant (False)
None # NameConstant (None)
[1, 2] # list
{1, 2} # set
{"foo": "bar"} # dict
class Foo3:
123
a = 2
"str"
1
def foo1():
"""my docstring"""
def foo2():
"""my docstring"""
a = 2
"str" # Str (no raise)
f"{int}" # JoinedStr (no raise)
1j # Number (complex)
1 # Number (int)
1.0 # Number (float)
b"foo" # Binary
True # NameConstant (True)
False # NameConstant (False)
None # NameConstant (None)
[1, 2] # list
{1, 2} # set
{"foo": "bar"} # dict
def foo3():
123
a = 2
"str"
3
def foo4():
...

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

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

View File

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

View File

@@ -14,3 +14,38 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor i
_ = "---------------------------------------------------------------------------AAAAAAA"
_ = "---------------------------------------------------------------------------亜亜亜亜亜亜亜"
def caller(string: str) -> None:
assert string is not None
caller(
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
""",
)
caller(
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
""", # noqa: E501
)
multiple_strings_per_line = {
# OK
"Lorem ipsum dolor": "sit amet",
# E501 Line too long
"Lorem ipsum dolor": "sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
# E501 Line too long
"Lorem ipsum dolor": """
sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
""",
# OK
"Lorem ipsum dolor": "sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", # noqa: E501
# OK
"Lorem ipsum dolor": """
sit amet consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
""", # noqa: E501
}

View File

@@ -3,3 +3,6 @@ if x is "abc":
if 123 is not y:
pass
if "123" is x < 3:
pass

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

@@ -0,0 +1,14 @@
"""Test (edge case): accesses of object named `dict`."""
# OK
dict = {"parameters": {}}
dict["parameters"]["userId"] = "userId"
dict["parameters"]["itemId"] = "itemId"
del dict["parameters"]["itemId"]
# F821 Undefined name `key`
# F821 Undefined name `value`
x: dict["key", "value"]
# OK
x: dict[str, str]

View File

@@ -1,7 +1,7 @@
def func(a, A):
return a, A
def func(_, a, A):
return _, a, A
class Class:
def method(self, a, A):
return a, A
def method(self, _, a, A):
return _, a, A

View File

@@ -1,19 +1,43 @@
from abc import ABCMeta
class Class:
@classmethod
def bad_class_method(this):
def bad_method(this):
pass
if False:
def extra_bad_method(this):
pass
def good_method(self):
pass
@classmethod
def good_class_method(cls):
pass
def method(self):
def class_method(cls):
pass
@staticmethod
def static_method(x):
return x
def __init__(self):
...
def __new__(cls, *args, **kwargs):
...
def __init_subclass__(self, default_name, **kwargs):
...
class MetaClass(ABCMeta):
def bad_method(self):
pass
def good_method(cls):
pass
def func(x):
return x

View File

@@ -1,11 +1,11 @@
import random
from abc import ABCMeta
class Class:
def bad_method(this):
pass
if random.random(0, 2) == 0:
if False:
def extra_bad_method(this):
pass
@@ -21,6 +21,23 @@ class Class:
def static_method(x):
return x
def __init__(self):
...
def __new__(cls, *args, **kwargs):
...
def __init_subclass__(self, default_name, **kwargs):
...
class MetaClass(ABCMeta):
def bad_method(self):
pass
def good_method(cls):
pass
def func(x):
return x

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,4 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
print('Hello world')

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

@@ -0,0 +1,5 @@
#!/usr/bin/python
# A coding comment is only valid in the first two lines, so this one is not checked.
# -*- coding: utf-8 -*-
print('Hello world')

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

@@ -0,0 +1,4 @@
#!/usr/bin/python
# -*- coding: something-else -*-
print('Hello world')

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

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

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

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

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

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

35
resources/test/fixtures/W605_1.py vendored Normal file
View File

@@ -0,0 +1,35 @@
#: W605:1:10
regex = '\.png$'
#: W605:2:1
regex = '''
\.png$
'''
#: W605:2:6
f(
'\_'
)
#: W605:4:6
"""
multi-line
literal
with \_ somewhere
in the middle
"""
#: Okay
regex = r'\.png$'
regex = '\\.png$'
regex = r'''
\.png$
'''
regex = r'''
\\.png$
'''
s = '\\'
regex = '\w' # noqa
regex = '''
\w
''' # noqa

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

17
ruff_dev/Cargo.toml Normal file
View File

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

View File

@@ -1,22 +1,39 @@
//! Generate the CheckCodePrefix enum.
//! Generate the `CheckCodePrefix` enum.
use std::collections::{BTreeMap, BTreeSet};
use std::fs::OpenOptions;
use std::io::Write;
use anyhow::Result;
use clap::Parser;
use codegen::{Scope, Type, Variant};
use itertools::Itertools;
use ruff::checks::CheckCode;
use strum::IntoEnumIterator;
fn main() {
const FILE: &str = "src/checks_gen.rs";
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
/// Write the generated source code to stdout (rather than to
/// `src/checks_gen.rs`).
#[arg(long)]
dry_run: bool,
}
pub fn main(cli: &Cli) -> Result<()> {
// Build up a map from prefix to matching CheckCodes.
let mut prefix_to_codes: BTreeMap<String, BTreeSet<CheckCode>> = Default::default();
for check_code in CheckCode::iter() {
let as_ref = check_code.as_ref().to_string();
for i in 1..=as_ref.len() {
let as_ref: String = check_code.as_ref().to_string();
let prefix_len = as_ref
.chars()
.take_while(|char| char.is_alphabetic())
.count();
for i in prefix_len..=as_ref.len() {
let prefix = as_ref[..i].to_string();
let entry = prefix_to_codes
.entry(prefix)
.or_insert_with(|| Default::default());
let entry = prefix_to_codes.entry(prefix).or_default();
entry.insert(check_code.clone());
}
}
@@ -31,10 +48,12 @@ fn main() {
.derive("Debug")
.derive("PartialEq")
.derive("Eq")
.derive("PartialOrd")
.derive("Ord")
.derive("Clone")
.derive("Serialize")
.derive("Deserialize");
for (prefix, _) in &prefix_to_codes {
for prefix in prefix_to_codes.keys() {
gen = gen.push_variant(Variant::new(prefix.to_string()));
}
@@ -78,12 +97,13 @@ fn main() {
.ret(Type::new("PrefixSpecificity"))
.vis("pub")
.line("match self {");
for (prefix, _) in &prefix_to_codes {
let specificity = match prefix.len() {
4 => "Explicit",
3 => "Tens",
2 => "Hundreds",
1 => "Category",
for prefix in prefix_to_codes.keys() {
let num_numeric = prefix.chars().filter(|char| char.is_numeric()).count();
let specificity = match num_numeric {
3 => "Explicit",
2 => "Tens",
1 => "Hundreds",
0 => "Category",
_ => panic!("Invalid prefix: {}", prefix),
};
gen = gen.line(format!(
@@ -93,12 +113,28 @@ fn main() {
}
gen.line("}");
println!("//! File automatically generated by examples/generate_check_code_prefix.rs.");
println!();
println!("use serde::{{Serialize, Deserialize}};");
println!("use strum_macros::EnumString;");
println!();
println!("use crate::checks::CheckCode;");
println!();
println!("{}", scope.to_string());
// Construct the output contents.
let mut output = String::new();
output.push_str("//! File automatically generated by examples/generate_check_code_prefix.rs.");
output.push('\n');
output.push('\n');
output.push_str("use serde::{{Serialize, Deserialize}};");
output.push('\n');
output.push_str("use strum_macros::EnumString;");
output.push('\n');
output.push('\n');
output.push_str("use crate::checks::CheckCode;");
output.push('\n');
output.push('\n');
output.push_str(&scope.to_string());
// Write the output to `src/checks_gen.rs` (or stdout).
if cli.dry_run {
println!("{}", output);
} else {
let mut f = OpenOptions::new().write(true).truncate(true).open(FILE)?;
write!(f, "{}", output)?;
}
Ok(())
}

View File

@@ -0,0 +1,93 @@
//! Generate a Markdown-compatible table of supported lint rules.
use std::fs;
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use ruff::checks::{CheckCategory, CheckCode};
use strum::IntoEnumIterator;
const BEGIN_PRAGMA: &str = "<!-- Begin auto-generated sections. -->";
const END_PRAGMA: &str = "<!-- End auto-generated sections. -->";
#[derive(Args)]
pub struct Cli {
/// Write the generated table to stdout (rather than to `README.md`).
#[arg(long)]
dry_run: bool,
}
pub fn main(cli: &Cli) -> Result<()> {
// Generate the table string.
let mut output = String::new();
for check_category in CheckCategory::iter() {
output.push_str(&format!("### {}", check_category.title()));
output.push('\n');
output.push('\n');
if let Some(url) = check_category.url() {
output.push_str(&format!(
"For more, see [{}]({}) on PyPI.",
check_category.title(),
url
));
output.push('\n');
output.push('\n');
}
output.push_str("| Code | Name | Message | Fix |");
output.push('\n');
output.push_str("| ---- | ---- | ------- | --- |");
output.push('\n');
for check_code in CheckCode::iter() {
if check_code.category() == check_category {
let check_kind = check_code.kind();
let fix_token = if check_kind.fixable() { "🛠" } else { "" };
output.push_str(&format!(
"| {} | {} | {} | {} |",
check_kind.code().as_ref(),
check_kind.as_ref(),
check_kind.summary().replace('|', r"\|"),
fix_token
));
output.push('\n');
}
}
output.push('\n');
}
if cli.dry_run {
print!("{}", output);
} else {
// Read the existing file.
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("Failed to find root directory")
.join("README.md");
let existing = fs::read_to_string(&file)?;
// Extract the prefix.
let index = existing
.find(BEGIN_PRAGMA)
.expect("Unable to find begin pragma.");
let prefix = &existing[..index + BEGIN_PRAGMA.len()];
// Extract the suffix.
let index = existing
.find(END_PRAGMA)
.expect("Unable to find end pragma.");
let suffix = &existing[index..];
// Write the prefix, new contents, and suffix.
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
write!(f, "{}\n\n", prefix)?;
write!(f, "{}", output)?;
write!(f, "{}", suffix)?;
}
Ok(())
}

View File

@@ -1,25 +1,25 @@
//! Run round-trip source code generation on a given Python file.
use std::fs;
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use clap::Args;
use ruff::code_gen::SourceGenerator;
use ruff::fs;
use rustpython_parser::parser;
#[derive(Debug, Parser)]
struct Cli {
#[derive(Args)]
pub struct Cli {
/// Python file to round-trip.
#[arg(required = true)]
file: PathBuf,
}
fn main() -> Result<()> {
let cli = Cli::parse();
let contents = fs::read_file(&cli.file)?;
pub fn main(cli: &Cli) -> Result<()> {
let contents = fs::read_to_string(&cli.file)?;
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
let mut generator = SourceGenerator::new();
generator.unparse_suite(&python_ast)?;
println!("{}", generator.generate()?);
Ok(())
}

6
ruff_dev/src/lib.rs Normal file
View File

@@ -0,0 +1,6 @@
pub mod generate_check_code_prefix;
pub mod generate_rules_table;
pub mod generate_source_code;
pub mod print_ast;
pub mod print_cst;
pub mod print_tokens;

43
ruff_dev/src/main.rs Normal file
View File

@@ -0,0 +1,43 @@
use anyhow::Result;
use clap::{Parser, Subcommand};
use ruff_dev::{
generate_check_code_prefix, generate_rules_table, generate_source_code, print_ast, print_cst,
print_tokens,
};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
#[command(propagate_version = true)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Generate the `CheckCodePrefix` enum.
GenerateCheckCodePrefix(generate_check_code_prefix::Cli),
/// Generate a Markdown-compatible table of supported lint rules.
GenerateRulesTable(generate_rules_table::Cli),
/// Run round-trip source code generation on a given Python file.
GenerateSourceCode(generate_source_code::Cli),
/// Print the AST for a given Python file.
PrintAST(print_ast::Cli),
/// Print the LibCST CST for a given Python file.
PrintCST(print_cst::Cli),
/// Print the token stream for a given Python file.
PrintTokens(print_tokens::Cli),
}
fn main() -> Result<()> {
let cli = Cli::parse();
match &cli.command {
Commands::GenerateCheckCodePrefix(args) => generate_check_code_prefix::main(args)?,
Commands::GenerateRulesTable(args) => generate_rules_table::main(args)?,
Commands::GenerateSourceCode(args) => generate_source_code::main(args)?,
Commands::PrintAST(args) => print_ast::main(args)?,
Commands::PrintCST(args) => print_cst::main(args)?,
Commands::PrintTokens(args) => print_tokens::main(args)?,
}
Ok(())
}

View File

@@ -1,25 +1,22 @@
//! Print the AST for a given Python file.
use std::fs;
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use ruff::fs;
use clap::Args;
use rustpython_parser::parser;
#[derive(Debug, Parser)]
struct Cli {
#[derive(Args)]
pub struct Cli {
/// Python file for which to generate the AST.
#[arg(required = true)]
file: PathBuf,
}
fn main() -> Result<()> {
let cli = Cli::parse();
let contents = fs::read_file(&cli.file)?;
pub fn main(cli: &Cli) -> Result<()> {
let contents = fs::read_to_string(&cli.file)?;
let python_ast = parser::parse_program(&contents, &cli.file.to_string_lossy())?;
println!("{:#?}", python_ast);
Ok(())
}

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

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

View File

@@ -1,25 +1,23 @@
//! Print the token stream for a given Python file.
use std::fs;
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use ruff::fs;
use clap::Args;
use rustpython_parser::lexer;
#[derive(Debug, Parser)]
struct Cli {
#[derive(Args)]
pub struct Cli {
/// Python file for which to generate the AST.
#[arg(required = true)]
file: PathBuf,
}
fn main() -> Result<()> {
let cli = Cli::parse();
let contents = fs::read_file(&cli.file)?;
pub fn main(cli: &Cli) -> Result<()> {
let contents = fs::read_to_string(&cli.file)?;
for (_, tok, _) in lexer::make_tokenizer(&contents).flatten() {
println!("{:#?}", tok);
}
Ok(())
}

View File

@@ -1 +1 @@
1.64.0
1.65.0

View File

@@ -1,6 +1,7 @@
use std::collections::BTreeMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use rustpython_ast::{Expr, Keyword};
use rustpython_parser::ast::{Located, Location};
fn id() -> usize {
@@ -30,24 +31,33 @@ pub struct FunctionScope {
pub uses_locals: bool,
}
#[derive(Clone, Debug)]
pub enum ScopeKind {
Class,
Function(FunctionScope),
Generator,
Module,
#[derive(Clone, Debug, Default)]
pub struct ClassScope<'a> {
pub name: &'a str,
pub bases: &'a [Expr],
pub keywords: &'a [Keyword],
pub decorator_list: &'a [Expr],
}
#[derive(Clone, Debug)]
pub struct Scope {
pub enum ScopeKind<'a> {
Class(ClassScope<'a>),
Function(FunctionScope),
Generator,
Module,
Arg,
}
#[derive(Clone, Debug)]
pub struct Scope<'a> {
pub id: usize,
pub kind: ScopeKind,
pub kind: ScopeKind<'a>,
pub import_starred: bool,
pub values: BTreeMap<String, Binding>,
}
impl Scope {
pub fn new(kind: ScopeKind) -> Self {
impl<'a> Scope<'a> {
pub fn new(kind: ScopeKind<'a>) -> Self {
Scope {
id: id(),
kind,
@@ -90,10 +100,6 @@ pub struct Binding {
pub used: Option<(usize, Range)>,
}
pub trait CheckLocator {
fn locate_check(&self, default: Range) -> Range;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ImportKind {
Import,

View File

@@ -1,10 +1,14 @@
use std::borrow::Cow;
use std::collections::BTreeSet;
use itertools::Itertools;
use ropey::RopeBuilder;
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::autofix::{Fix, Patch};
use crate::checks::Check;
use crate::source_code_locator::SourceCodeLocator;
// TODO(charlie): The model here is awkward because `Apply` is only relevant at
// higher levels in the execution flow.
@@ -36,24 +40,28 @@ impl From<bool> for Mode {
}
/// Auto-fix errors in a file, and write the fixed source code to disk.
pub fn fix_file(checks: &mut [Check], contents: &str) -> Option<String> {
pub fn fix_file<'a>(
checks: &'a mut [Check],
locator: &'a SourceCodeLocator<'a>,
) -> Option<Cow<'a, str>> {
if checks.iter().all(|check| check.fix.is_none()) {
return None;
}
Some(apply_fixes(
checks.iter_mut().filter_map(|check| check.fix.as_mut()),
contents,
locator,
))
}
/// Apply a series of fixes.
fn apply_fixes<'a>(fixes: impl Iterator<Item = &'a mut Fix>, contents: &str) -> String {
let lines: Vec<&str> = contents.lines().collect();
let mut output: String = Default::default();
let mut last_pos: Location = Default::default();
let mut applied: BTreeSet<&Patch> = Default::default();
fn apply_fixes<'a>(
fixes: impl Iterator<Item = &'a mut Fix>,
locator: &'a SourceCodeLocator<'a>,
) -> Cow<'a, str> {
let mut output = RopeBuilder::new();
let mut last_pos: Location = Location::new(1, 0);
let mut applied: BTreeSet<&Patch> = BTreeSet::new();
for fix in fixes.sorted_by_key(|fix| fix.patch.location) {
// If we already applied an identical fix as part of another correction, skip
@@ -69,44 +77,27 @@ fn apply_fixes<'a>(fixes: impl Iterator<Item = &'a mut Fix>, contents: &str) ->
continue;
}
if fix.patch.location.row() > last_pos.row() {
if last_pos.row() > 0 || last_pos.column() > 0 {
output.push_str(&lines[last_pos.row() - 1][last_pos.column()..]);
output.push('\n');
}
for line in &lines[last_pos.row()..fix.patch.location.row() - 1] {
output.push_str(line);
output.push('\n');
}
output.push_str(&lines[fix.patch.location.row() - 1][..fix.patch.location.column()]);
output.push_str(&fix.patch.content);
} else {
output.push_str(
&lines[last_pos.row() - 1][last_pos.column()..fix.patch.location.column()],
);
output.push_str(&fix.patch.content);
}
last_pos = fix.patch.end_location;
// Add all contents from `last_pos` to `fix.patch.location`.
let slice = locator.slice_source_code_range(&Range {
location: last_pos,
end_location: fix.patch.location,
});
output.append(&slice);
// Add the patch itself.
output.append(&fix.patch.content);
// Track that the fix was applied.
last_pos = fix.patch.end_location;
applied.insert(&fix.patch);
fix.applied = true;
}
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()..]);
output.push('\n');
}
if last_pos.row() < lines.len() {
for line in &lines[last_pos.row()..] {
output.push_str(line);
output.push('\n');
}
}
// Add the remaining content.
let slice = locator.slice_source_code_at(&last_pos);
output.append(&slice);
output
Cow::from(output.finish())
}
#[cfg(test)]
@@ -116,11 +107,13 @@ mod tests {
use crate::autofix::fixer::apply_fixes;
use crate::autofix::{Fix, Patch};
use crate::SourceCodeLocator;
#[test]
fn empty_file() -> Result<()> {
let mut fixes = vec![];
let actual = apply_fixes(fixes.iter_mut(), "");
let locator = SourceCodeLocator::new("");
let actual = apply_fixes(fixes.iter_mut(), &locator);
let expected = "";
assert_eq!(actual, expected);
@@ -138,12 +131,12 @@ mod tests {
},
applied: false,
}];
let actual = apply_fixes(
fixes.iter_mut(),
let locator = SourceCodeLocator::new(
"class A(object):
...
",
);
let actual = apply_fixes(fixes.iter_mut(), &locator);
let expected = "class A(Bar):
...
@@ -164,12 +157,12 @@ mod tests {
},
applied: false,
}];
let actual = apply_fixes(
fixes.iter_mut(),
let locator = SourceCodeLocator::new(
"class A(object):
...
",
);
let actual = apply_fixes(fixes.iter_mut(), &locator);
let expected = "class A:
...
@@ -200,12 +193,12 @@ mod tests {
applied: false,
},
];
let actual = apply_fixes(
fixes.iter_mut(),
let locator = SourceCodeLocator::new(
"class A(object, object):
...
",
);
let actual = apply_fixes(fixes.iter_mut(), &locator);
let expected = "class A:
...
@@ -236,12 +229,12 @@ mod tests {
applied: false,
},
];
let actual = apply_fixes(
fixes.iter_mut(),
let locator = SourceCodeLocator::new(
"class A(object):
...
",
);
let actual = apply_fixes(fixes.iter_mut(), &locator);
let expected = "class A:
...

File diff suppressed because it is too large Load Diff

41
src/check_imports.rs Normal file
View File

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

View File

@@ -2,6 +2,8 @@
use std::collections::BTreeMap;
use once_cell::sync::Lazy;
use regex::Regex;
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
@@ -11,6 +13,10 @@ use crate::noqa;
use crate::noqa::Directive;
use crate::settings::Settings;
// Regex from PEP263
static CODING_COMMENT_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^[ \t\f]*#.*?coding[:=][ \t]*utf-?8").expect("Invalid regex"));
/// Whether the given line is too long and should be reported.
fn should_enforce_line_length(line: &str, length: usize, limit: usize) -> bool {
if length > limit {
@@ -34,6 +40,7 @@ pub fn check_lines(
settings: &Settings,
autofix: &fixer::Mode,
) {
let enforce_unnecessary_coding_comment = settings.enabled.contains(&CheckCode::U009);
let enforce_line_too_long = settings.enabled.contains(&CheckCode::E501);
let enforce_noqa = settings.enabled.contains(&CheckCode::M001);
@@ -52,6 +59,30 @@ pub fn check_lines(
.map(|lineno| lineno - 1)
.unwrap_or(lineno);
// Enforce unnecessary coding comments (U009).
if enforce_unnecessary_coding_comment {
if lineno < 2 {
// PEP3120 makes utf-8 the default encoding.
if CODING_COMMENT_REGEX.is_match(line) {
let line_length = line.len();
let mut check = Check::new(
CheckKind::PEP3120UnnecessaryCodingComment,
Range {
location: Location::new(lineno + 1, 0),
end_location: Location::new(lineno + 1, line_length + 1),
},
);
if autofix.patch() {
check.amend(Fix::deletion(
Location::new(lineno + 1, 0),
Location::new(lineno + 1, line_length + 1),
));
}
line_checks.push(check);
}
}
}
if enforce_noqa {
noqa_directives
.entry(noqa_lineno)
@@ -82,7 +113,7 @@ pub fn check_lines(
}
}
// Enforce line length.
// Enforce line length violations (E501).
if enforce_line_too_long {
let line_length = line.chars().count();
if should_enforce_line_length(line, line_length, settings.line_length) {

View File

@@ -2,37 +2,64 @@
use rustpython_parser::lexer::{LexResult, Tok};
use crate::autofix::fixer;
use crate::checks::{Check, CheckCode};
use crate::flake8_quotes::docstring_detection::StateMachine;
use crate::lex::docstring_detection::StateMachine;
use crate::rules::checks::Context;
use crate::source_code_locator::SourceCodeLocator;
use crate::{flake8_quotes, pycodestyle, Settings};
use crate::{flake8_quotes, pycodestyle, rules, Settings};
pub fn check_tokens(
checks: &mut Vec<Check>,
locator: &SourceCodeLocator,
tokens: &[LexResult],
settings: &Settings,
autofix: &fixer::Mode,
) {
let enforce_invalid_escape_sequence = settings.enabled.contains(&CheckCode::W605);
let enforce_ambiguous_unicode_character = settings.enabled.contains(&CheckCode::RUF001)
|| settings.enabled.contains(&CheckCode::RUF002)
|| settings.enabled.contains(&CheckCode::RUF003);
let enforce_quotes = settings.enabled.contains(&CheckCode::Q000)
| settings.enabled.contains(&CheckCode::Q001)
| settings.enabled.contains(&CheckCode::Q002)
| settings.enabled.contains(&CheckCode::Q003);
|| settings.enabled.contains(&CheckCode::Q001)
|| settings.enabled.contains(&CheckCode::Q002)
|| settings.enabled.contains(&CheckCode::Q003);
let enforce_invalid_escape_sequence = settings.enabled.contains(&CheckCode::W605);
let mut state_machine: StateMachine = Default::default();
for (start, tok, end) in tokens.iter().flatten() {
// W605
if enforce_invalid_escape_sequence {
if matches!(tok, Tok::String { .. }) {
checks.extend(pycodestyle::checks::invalid_escape_sequence(
locator, start, end,
));
let is_docstring = if enforce_ambiguous_unicode_character || enforce_quotes {
state_machine.consume(tok)
} else {
false
};
// RUF001, RUF002, RUF003
if enforce_ambiguous_unicode_character {
if matches!(tok, Tok::String { .. } | Tok::Comment) {
for check in rules::checks::ambiguous_unicode_character(
locator,
start,
end,
if matches!(tok, Tok::String { .. }) {
if is_docstring {
Context::Docstring
} else {
Context::String
}
} else {
Context::Comment
},
autofix.patch(),
) {
if settings.enabled.contains(check.kind.code()) {
checks.push(check);
}
}
}
}
// flake8-quotes
if enforce_quotes {
let is_docstring = state_machine.consume(tok);
if matches!(tok, Tok::String { .. }) {
if let Some(check) = flake8_quotes::checks::quotes(
locator,
@@ -47,5 +74,14 @@ pub fn check_tokens(
}
}
}
// W605
if enforce_invalid_escape_sequence {
if matches!(tok, Tok::String { .. }) {
checks.extend(pycodestyle::checks::invalid_escape_sequence(
locator, start, end,
));
}
}
}
}

View File

@@ -78,13 +78,22 @@ pub enum CheckCode {
A003,
// flake8-bugbear
B002,
B003,
B004,
B005,
B006,
B007,
B008,
B009,
B011,
B013,
B014,
B015,
B016,
B017,
B018,
B025,
B026,
// flake8-comprehensions
C400,
C401,
@@ -110,6 +119,18 @@ pub enum CheckCode {
Q001,
Q002,
Q003,
// flake8-annotations
ANN001,
ANN002,
ANN003,
ANN101,
ANN102,
ANN201,
ANN202,
ANN204,
ANN205,
ANN206,
ANN401,
// pyupgrade
U001,
U002,
@@ -119,6 +140,9 @@ pub enum CheckCode {
U006,
U007,
U008,
U009,
U010,
U011,
// pydocstyle
D100,
D101,
@@ -180,6 +204,12 @@ pub enum CheckCode {
N816,
N817,
N818,
// isort
I001,
// Ruff
RUF001,
RUF002,
RUF003,
// Meta
M001,
}
@@ -187,8 +217,8 @@ pub enum CheckCode {
#[derive(EnumIter, Debug, PartialEq, Eq)]
pub enum CheckCategory {
Pyflakes,
PycodestyleError,
PycodestyleWarning,
Pycodestyle,
Isort,
Pydocstyle,
Pyupgrade,
PEP8Naming,
@@ -197,26 +227,57 @@ pub enum CheckCategory {
Flake8Builtins,
Flake8Print,
Flake8Quotes,
Flake8Annotations,
Ruff,
Meta,
}
impl CheckCategory {
pub fn title(&self) -> &'static str {
match self {
CheckCategory::PycodestyleError => "pycodestyle (error)",
CheckCategory::PycodestyleWarning => "pycodestyle (warning)",
CheckCategory::Pycodestyle => "pycodestyle",
CheckCategory::Pyflakes => "Pyflakes",
CheckCategory::Isort => "isort",
CheckCategory::Flake8Builtins => "flake8-builtins",
CheckCategory::Flake8Bugbear => "flake8-bugbear",
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
CheckCategory::Flake8Print => "flake8-print",
CheckCategory::Flake8Quotes => "flake8-quotes",
CheckCategory::Flake8Annotations => "flake8-annotations",
CheckCategory::Pyupgrade => "pyupgrade",
CheckCategory::Pydocstyle => "pydocstyle",
CheckCategory::PEP8Naming => "pep8-naming",
CheckCategory::Ruff => "Ruff-specific rules",
CheckCategory::Meta => "Meta rules",
}
}
pub fn url(&self) -> Option<&'static str> {
match self {
CheckCategory::Pycodestyle => Some("https://pypi.org/project/pycodestyle/2.9.1/"),
CheckCategory::Pyflakes => Some("https://pypi.org/project/pyflakes/2.5.0/"),
CheckCategory::Isort => Some("https://pypi.org/project/isort/5.10.1/"),
CheckCategory::Flake8Builtins => {
Some("https://pypi.org/project/flake8-builtins/2.0.1/")
}
CheckCategory::Flake8Bugbear => {
Some("https://pypi.org/project/flake8-bugbear/22.10.27/")
}
CheckCategory::Flake8Comprehensions => {
Some("https://pypi.org/project/flake8-comprehensions/3.10.1/")
}
CheckCategory::Flake8Print => Some("https://pypi.org/project/flake8-print/5.0.0/"),
CheckCategory::Flake8Quotes => Some("https://pypi.org/project/flake8-quotes/3.3.1/"),
CheckCategory::Flake8Annotations => {
Some("https://pypi.org/project/flake8-annotations/2.9.1/")
}
CheckCategory::Pyupgrade => Some("https://pypi.org/project/pyupgrade/3.2.0/"),
CheckCategory::Pydocstyle => Some("https://pypi.org/project/pydocstyle/6.1.1/"),
CheckCategory::PEP8Naming => Some("https://pypi.org/project/pep8-naming/0.13.2/"),
CheckCategory::Ruff => None,
CheckCategory::Meta => None,
}
}
}
#[allow(clippy::upper_case_acronyms)]
@@ -225,6 +286,7 @@ pub enum LintSource {
FileSystem,
Lines,
Tokens,
Imports,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -288,13 +350,22 @@ pub enum CheckKind {
BuiltinAttributeShadowing(String),
// flake8-bugbear
UnaryPrefixIncrement,
AssignmentToOsEnviron,
UnreliableCallableCheck,
StripWithMultiCharacters,
MutableArgumentDefault,
UnusedLoopControlVariable(String),
FunctionCallArgumentDefault,
GetAttrWithConstant,
DoNotAssertFalse,
RedundantTupleInExceptionHandler(String),
DuplicateHandlerException(Vec<String>),
UselessComparison,
CannotRaiseLiteral,
NoAssertRaisesException,
UselessExpression,
DuplicateTryBlockException(String),
StarArgUnpackingAfterKeywordArg,
// flake8-comprehensions
UnnecessaryGeneratorList,
UnnecessaryGeneratorSet,
@@ -320,6 +391,18 @@ pub enum CheckKind {
BadQuotesMultilineString(Quote),
BadQuotesDocstring(Quote),
AvoidQuoteEscape,
// flake8-annotations
MissingTypeFunctionArgument(String),
MissingTypeArgs(String),
MissingTypeKwargs(String),
MissingTypeSelf(String),
MissingTypeCls(String),
MissingReturnTypePublicFunction(String),
MissingReturnTypePrivateFunction(String),
MissingReturnTypeMagicMethod(String),
MissingReturnTypeStaticMethod(String),
MissingReturnTypeClassMethod(String),
DynamicallyTypedExpression(String),
// pyupgrade
TypeOfPrimitive(Primitive),
UnnecessaryAbspath,
@@ -329,6 +412,9 @@ pub enum CheckKind {
UsePEP585Annotation(String),
UsePEP604Annotation,
SuperCallWithParameters,
PEP3120UnnecessaryCodingComment,
UnnecessaryFutureImport(String),
UnnecessaryLRUCacheParams,
// pydocstyle
BlankLineAfterLastSection(String),
BlankLineAfterSection(String),
@@ -390,6 +476,12 @@ pub enum CheckKind {
MixedCaseVariableInGlobalScope(String),
CamelcaseImportedAsAcronym(String, String),
ErrorSuffixOnExceptionName(String),
// isort
UnsortedImports,
// Ruff
AmbiguousUnicodeCharacterString(char, char),
AmbiguousUnicodeCharacterDocstring(char, char),
AmbiguousUnicodeCharacterComment(char, char),
// Meta
UnusedNOQA(Option<Vec<String>>),
}
@@ -399,13 +491,19 @@ impl CheckCode {
/// physical lines).
pub fn lint_source(&self) -> &'static LintSource {
match self {
CheckCode::E501 | CheckCode::W292 | CheckCode::M001 => &LintSource::Lines,
CheckCode::W605
| CheckCode::Q000
CheckCode::E501 | CheckCode::W292 | CheckCode::M001 | CheckCode::U009 => {
&LintSource::Lines
}
CheckCode::Q000
| CheckCode::Q001
| CheckCode::Q002
| CheckCode::Q003 => &LintSource::Tokens,
| CheckCode::Q003
| CheckCode::W605
| CheckCode::RUF001
| CheckCode::RUF002
| CheckCode::RUF003 => &LintSource::Tokens,
CheckCode::E902 => &LintSource::FileSystem,
CheckCode::I001 => &LintSource::Imports,
_ => &LintSource::AST,
}
}
@@ -468,15 +566,24 @@ impl CheckCode {
CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()),
// flake8-bugbear
CheckCode::B002 => CheckKind::UnaryPrefixIncrement,
CheckCode::B003 => CheckKind::AssignmentToOsEnviron,
CheckCode::B004 => CheckKind::UnreliableCallableCheck,
CheckCode::B005 => CheckKind::StripWithMultiCharacters,
CheckCode::B006 => CheckKind::MutableArgumentDefault,
CheckCode::B007 => CheckKind::UnusedLoopControlVariable("i".to_string()),
CheckCode::B008 => CheckKind::FunctionCallArgumentDefault,
CheckCode::B009 => CheckKind::GetAttrWithConstant,
CheckCode::B011 => CheckKind::DoNotAssertFalse,
CheckCode::B013 => {
CheckKind::RedundantTupleInExceptionHandler("ValueError".to_string())
}
CheckCode::B014 => CheckKind::DuplicateHandlerException(vec!["ValueError".to_string()]),
CheckCode::B015 => CheckKind::UselessComparison,
CheckCode::B016 => CheckKind::CannotRaiseLiteral,
CheckCode::B017 => CheckKind::NoAssertRaisesException,
CheckCode::B018 => CheckKind::UselessExpression,
CheckCode::B025 => CheckKind::DuplicateTryBlockException("Exception".to_string()),
CheckCode::B026 => CheckKind::StarArgUnpackingAfterKeywordArg,
// flake8-comprehensions
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
CheckCode::C401 => CheckKind::UnnecessaryGeneratorSet,
@@ -515,6 +622,18 @@ impl CheckCode {
CheckCode::Q001 => CheckKind::BadQuotesMultilineString(Quote::Double),
CheckCode::Q002 => CheckKind::BadQuotesDocstring(Quote::Double),
CheckCode::Q003 => CheckKind::AvoidQuoteEscape,
// flake8-annotations
CheckCode::ANN001 => CheckKind::MissingTypeFunctionArgument("...".to_string()),
CheckCode::ANN002 => CheckKind::MissingTypeArgs("...".to_string()),
CheckCode::ANN003 => CheckKind::MissingTypeKwargs("...".to_string()),
CheckCode::ANN101 => CheckKind::MissingTypeSelf("...".to_string()),
CheckCode::ANN102 => CheckKind::MissingTypeCls("...".to_string()),
CheckCode::ANN201 => CheckKind::MissingReturnTypePublicFunction("...".to_string()),
CheckCode::ANN202 => CheckKind::MissingReturnTypePrivateFunction("...".to_string()),
CheckCode::ANN204 => CheckKind::MissingReturnTypeMagicMethod("...".to_string()),
CheckCode::ANN205 => CheckKind::MissingReturnTypeStaticMethod("...".to_string()),
CheckCode::ANN206 => CheckKind::MissingReturnTypeClassMethod("...".to_string()),
CheckCode::ANN401 => CheckKind::DynamicallyTypedExpression("...".to_string()),
// pyupgrade
CheckCode::U001 => CheckKind::UselessMetaclassType,
CheckCode::U002 => CheckKind::UnnecessaryAbspath,
@@ -527,6 +646,9 @@ impl CheckCode {
CheckCode::U006 => CheckKind::UsePEP585Annotation("List".to_string()),
CheckCode::U007 => CheckKind::UsePEP604Annotation,
CheckCode::U008 => CheckKind::SuperCallWithParameters,
CheckCode::U009 => CheckKind::PEP3120UnnecessaryCodingComment,
CheckCode::U010 => CheckKind::UnnecessaryFutureImport("...".to_string()),
CheckCode::U011 => CheckKind::UnnecessaryLRUCacheParams,
// pydocstyle
CheckCode::D100 => CheckKind::PublicModule,
CheckCode::D101 => CheckKind::PublicClass,
@@ -604,6 +726,12 @@ impl CheckCode {
CheckKind::CamelcaseImportedAsAcronym("...".to_string(), "...".to_string())
}
CheckCode::N818 => CheckKind::ErrorSuffixOnExceptionName("...".to_string()),
// isort
CheckCode::I001 => CheckKind::UnsortedImports,
// Ruff
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
CheckCode::RUF003 => CheckKind::AmbiguousUnicodeCharacterComment('𝐁', 'B'),
// Meta
CheckCode::M001 => CheckKind::UnusedNOQA(None),
}
@@ -611,22 +739,22 @@ impl CheckCode {
pub fn category(&self) -> CheckCategory {
match self {
CheckCode::E402 => CheckCategory::PycodestyleError,
CheckCode::E501 => CheckCategory::PycodestyleError,
CheckCode::E711 => CheckCategory::PycodestyleError,
CheckCode::E712 => CheckCategory::PycodestyleError,
CheckCode::E713 => CheckCategory::PycodestyleError,
CheckCode::E714 => CheckCategory::PycodestyleError,
CheckCode::E721 => CheckCategory::PycodestyleError,
CheckCode::E722 => CheckCategory::PycodestyleError,
CheckCode::E731 => CheckCategory::PycodestyleError,
CheckCode::E741 => CheckCategory::PycodestyleError,
CheckCode::E742 => CheckCategory::PycodestyleError,
CheckCode::E743 => CheckCategory::PycodestyleError,
CheckCode::E902 => CheckCategory::PycodestyleError,
CheckCode::E999 => CheckCategory::PycodestyleError,
CheckCode::W292 => CheckCategory::PycodestyleWarning,
CheckCode::W605 => CheckCategory::PycodestyleWarning,
CheckCode::E402 => CheckCategory::Pycodestyle,
CheckCode::E501 => CheckCategory::Pycodestyle,
CheckCode::E711 => CheckCategory::Pycodestyle,
CheckCode::E712 => CheckCategory::Pycodestyle,
CheckCode::E713 => CheckCategory::Pycodestyle,
CheckCode::E714 => CheckCategory::Pycodestyle,
CheckCode::E721 => CheckCategory::Pycodestyle,
CheckCode::E722 => CheckCategory::Pycodestyle,
CheckCode::E731 => CheckCategory::Pycodestyle,
CheckCode::E741 => CheckCategory::Pycodestyle,
CheckCode::E742 => CheckCategory::Pycodestyle,
CheckCode::E743 => CheckCategory::Pycodestyle,
CheckCode::E902 => CheckCategory::Pycodestyle,
CheckCode::E999 => CheckCategory::Pycodestyle,
CheckCode::W292 => CheckCategory::Pycodestyle,
CheckCode::W605 => CheckCategory::Pycodestyle,
CheckCode::F401 => CheckCategory::Pyflakes,
CheckCode::F402 => CheckCategory::Pyflakes,
CheckCode::F403 => CheckCategory::Pyflakes,
@@ -659,13 +787,22 @@ impl CheckCode {
CheckCode::A002 => CheckCategory::Flake8Builtins,
CheckCode::A003 => CheckCategory::Flake8Builtins,
CheckCode::B002 => CheckCategory::Flake8Bugbear,
CheckCode::B003 => CheckCategory::Flake8Bugbear,
CheckCode::B004 => CheckCategory::Flake8Bugbear,
CheckCode::B005 => CheckCategory::Flake8Bugbear,
CheckCode::B006 => CheckCategory::Flake8Bugbear,
CheckCode::B007 => CheckCategory::Flake8Bugbear,
CheckCode::B008 => CheckCategory::Flake8Bugbear,
CheckCode::B009 => CheckCategory::Flake8Bugbear,
CheckCode::B011 => CheckCategory::Flake8Bugbear,
CheckCode::B013 => CheckCategory::Flake8Bugbear,
CheckCode::B014 => CheckCategory::Flake8Bugbear,
CheckCode::B015 => CheckCategory::Flake8Bugbear,
CheckCode::B016 => CheckCategory::Flake8Bugbear,
CheckCode::B017 => CheckCategory::Flake8Bugbear,
CheckCode::B018 => CheckCategory::Flake8Bugbear,
CheckCode::B025 => CheckCategory::Flake8Bugbear,
CheckCode::B026 => CheckCategory::Flake8Bugbear,
CheckCode::C400 => CheckCategory::Flake8Comprehensions,
CheckCode::C401 => CheckCategory::Flake8Comprehensions,
CheckCode::C402 => CheckCategory::Flake8Comprehensions,
@@ -688,6 +825,17 @@ impl CheckCode {
CheckCode::Q001 => CheckCategory::Flake8Quotes,
CheckCode::Q002 => CheckCategory::Flake8Quotes,
CheckCode::Q003 => CheckCategory::Flake8Quotes,
CheckCode::ANN001 => CheckCategory::Flake8Annotations,
CheckCode::ANN002 => CheckCategory::Flake8Annotations,
CheckCode::ANN003 => CheckCategory::Flake8Annotations,
CheckCode::ANN101 => CheckCategory::Flake8Annotations,
CheckCode::ANN102 => CheckCategory::Flake8Annotations,
CheckCode::ANN201 => CheckCategory::Flake8Annotations,
CheckCode::ANN202 => CheckCategory::Flake8Annotations,
CheckCode::ANN204 => CheckCategory::Flake8Annotations,
CheckCode::ANN205 => CheckCategory::Flake8Annotations,
CheckCode::ANN206 => CheckCategory::Flake8Annotations,
CheckCode::ANN401 => CheckCategory::Flake8Annotations,
CheckCode::U001 => CheckCategory::Pyupgrade,
CheckCode::U002 => CheckCategory::Pyupgrade,
CheckCode::U003 => CheckCategory::Pyupgrade,
@@ -696,6 +844,9 @@ impl CheckCode {
CheckCode::U006 => CheckCategory::Pyupgrade,
CheckCode::U007 => CheckCategory::Pyupgrade,
CheckCode::U008 => CheckCategory::Pyupgrade,
CheckCode::U009 => CheckCategory::Pyupgrade,
CheckCode::U010 => CheckCategory::Pyupgrade,
CheckCode::U011 => CheckCategory::Pyupgrade,
CheckCode::D100 => CheckCategory::Pydocstyle,
CheckCode::D101 => CheckCategory::Pydocstyle,
CheckCode::D102 => CheckCategory::Pydocstyle,
@@ -755,6 +906,10 @@ impl CheckCode {
CheckCode::N816 => CheckCategory::PEP8Naming,
CheckCode::N817 => CheckCategory::PEP8Naming,
CheckCode::N818 => CheckCategory::PEP8Naming,
CheckCode::I001 => CheckCategory::Isort,
CheckCode::RUF001 => CheckCategory::Ruff,
CheckCode::RUF002 => CheckCategory::Ruff,
CheckCode::RUF003 => CheckCategory::Ruff,
CheckCode::M001 => CheckCategory::Meta,
}
}
@@ -816,13 +971,22 @@ impl CheckKind {
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
// flake8-bugbear
CheckKind::UnaryPrefixIncrement => &CheckCode::B002,
CheckKind::AssignmentToOsEnviron => &CheckCode::B003,
CheckKind::UnreliableCallableCheck => &CheckCode::B004,
CheckKind::StripWithMultiCharacters => &CheckCode::B005,
CheckKind::MutableArgumentDefault => &CheckCode::B006,
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
CheckKind::FunctionCallArgumentDefault => &CheckCode::B008,
CheckKind::GetAttrWithConstant => &CheckCode::B009,
CheckKind::DoNotAssertFalse => &CheckCode::B011,
CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013,
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
CheckKind::UselessComparison => &CheckCode::B015,
CheckKind::CannotRaiseLiteral => &CheckCode::B016,
CheckKind::NoAssertRaisesException => &CheckCode::B017,
CheckKind::UselessExpression => &CheckCode::B018,
CheckKind::DuplicateTryBlockException(_) => &CheckCode::B025,
CheckKind::StarArgUnpackingAfterKeywordArg => &CheckCode::B026,
// flake8-comprehensions
CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
CheckKind::UnnecessaryGeneratorSet => &CheckCode::C401,
@@ -848,6 +1012,18 @@ impl CheckKind {
CheckKind::BadQuotesMultilineString(_) => &CheckCode::Q001,
CheckKind::BadQuotesDocstring(_) => &CheckCode::Q002,
CheckKind::AvoidQuoteEscape => &CheckCode::Q003,
// flake8-annotations
CheckKind::MissingTypeFunctionArgument(_) => &CheckCode::ANN001,
CheckKind::MissingTypeArgs(_) => &CheckCode::ANN002,
CheckKind::MissingTypeKwargs(_) => &CheckCode::ANN003,
CheckKind::MissingTypeSelf(_) => &CheckCode::ANN101,
CheckKind::MissingTypeCls(_) => &CheckCode::ANN102,
CheckKind::MissingReturnTypePublicFunction(_) => &CheckCode::ANN201,
CheckKind::MissingReturnTypePrivateFunction(_) => &CheckCode::ANN202,
CheckKind::MissingReturnTypeMagicMethod(_) => &CheckCode::ANN204,
CheckKind::MissingReturnTypeStaticMethod(_) => &CheckCode::ANN205,
CheckKind::MissingReturnTypeClassMethod(_) => &CheckCode::ANN206,
CheckKind::DynamicallyTypedExpression(_) => &CheckCode::ANN401,
// pyupgrade
CheckKind::TypeOfPrimitive(_) => &CheckCode::U003,
CheckKind::UnnecessaryAbspath => &CheckCode::U002,
@@ -857,6 +1033,9 @@ impl CheckKind {
CheckKind::UsePEP604Annotation => &CheckCode::U007,
CheckKind::UselessObjectInheritance(_) => &CheckCode::U004,
CheckKind::SuperCallWithParameters => &CheckCode::U008,
CheckKind::PEP3120UnnecessaryCodingComment => &CheckCode::U009,
CheckKind::UnnecessaryFutureImport(_) => &CheckCode::U010,
CheckKind::UnnecessaryLRUCacheParams => &CheckCode::U011,
// pydocstyle
CheckKind::BlankLineAfterLastSection(_) => &CheckCode::D413,
CheckKind::BlankLineAfterSection(_) => &CheckCode::D410,
@@ -918,6 +1097,12 @@ impl CheckKind {
CheckKind::MixedCaseVariableInGlobalScope(..) => &CheckCode::N816,
CheckKind::CamelcaseImportedAsAcronym(..) => &CheckCode::N817,
CheckKind::ErrorSuffixOnExceptionName(..) => &CheckCode::N818,
// isort
CheckKind::UnsortedImports => &CheckCode::I001,
// Ruff
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
CheckKind::AmbiguousUnicodeCharacterComment(..) => &CheckCode::RUF003,
// Meta
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
}
@@ -1078,6 +1263,16 @@ impl CheckKind {
increment. Writing `++n` is equivalent to \
`+(+(n))`, which equals `n`. You meant `n += 1`."
.to_string(),
CheckKind::AssignmentToOsEnviron => {
"Assigning to `os.environ` doesn't clear the environment.".to_string()
}
CheckKind::UnreliableCallableCheck => " Using `hasattr(x, '__call__')` to test if x \
is callable is unreliable. Use `callable(x)` \
for consistent results."
.to_string(),
CheckKind::StripWithMultiCharacters => "Using `.strip()` with multi-character strings \
is misleading the reader."
.to_string(),
CheckKind::MutableArgumentDefault => {
"Do not use mutable data structures for argument defaults.".to_string()
}
@@ -1085,6 +1280,13 @@ impl CheckKind {
"Loop control variable `{name}` not used within the loop body. If this is \
intended, start the name with an underscore."
),
CheckKind::FunctionCallArgumentDefault => {
"Do not perform function calls in argument defaults.".to_string()
}
CheckKind::GetAttrWithConstant => "Do not call `getattr` with a constant attribute \
value, it is not any safer than normal property \
access."
.to_string(),
CheckKind::DoNotAssertFalse => "Do not `assert False` (`python -O` removes these \
calls), raise `AssertionError()`"
.to_string(),
@@ -1094,6 +1296,13 @@ impl CheckKind {
`except ({name},):`."
)
}
CheckKind::UselessComparison => "Pointless comparison. This comparison does nothing \
but waste CPU instructions. Either prepend `assert` \
or remove it."
.to_string(),
CheckKind::CannotRaiseLiteral => "Cannot raise a literal. Did you intend to return it \
or raise an Exception?"
.to_string(),
CheckKind::DuplicateHandlerException(names) => {
if names.len() == 1 {
let name = &names[0];
@@ -1110,9 +1319,19 @@ impl CheckKind {
`assertRaisesRegex`, or use the context manager form of `assertRaises`."
.to_string()
}
CheckKind::UselessExpression => {
"Found useless expression. Either assign it to a variable or remove it.".to_string()
}
CheckKind::DuplicateTryBlockException(name) => {
format!("try-except block with duplicate exception `{name}`")
}
CheckKind::StarArgUnpackingAfterKeywordArg => {
"Star-arg unpacking after a keyword argument is strongly discouraged, because it \
only works when the keyword parameter is declared after all parameters supplied \
by the unpacked sequence, and this change of ordering can surprise and mislead \
readers."
.to_string()
}
// flake8-comprehensions
CheckKind::UnnecessaryGeneratorList => {
"Unnecessary generator (rewrite as a `list` comprehension)".to_string()
@@ -1213,6 +1432,38 @@ impl CheckKind {
CheckKind::AvoidQuoteEscape => {
"Change outer quotes to avoid escaping inner quotes".to_string()
}
// flake8-annotations
CheckKind::MissingTypeFunctionArgument(name) => {
format!("Missing type annotation for function argument `{name}`")
}
CheckKind::MissingTypeArgs(name) => format!("Missing type annotation for `*{name}`"),
CheckKind::MissingTypeKwargs(name) => {
format!("Missing type annotation for `**{name}`")
}
CheckKind::MissingTypeSelf(name) => {
format!("Missing type annotation for `{name}` in method")
}
CheckKind::MissingTypeCls(name) => {
format!("Missing type annotation for `{name}` in classmethod")
}
CheckKind::MissingReturnTypePublicFunction(name) => {
format!("Missing return type annotation for public function `{name}`")
}
CheckKind::MissingReturnTypePrivateFunction(name) => {
format!("Missing return type annotation for private function `{name}`")
}
CheckKind::MissingReturnTypeMagicMethod(name) => {
format!("Missing return type annotation for magic method `{name}`")
}
CheckKind::MissingReturnTypeStaticMethod(name) => {
format!("Missing return type annotation for staticmethod `{name}`")
}
CheckKind::MissingReturnTypeClassMethod(name) => {
format!("Missing return type annotation for classmethod `{name}`")
}
CheckKind::DynamicallyTypedExpression(name) => {
format!("Dynamically typed expressions (typing.Any) are disallowed in `{name}`")
}
// pyupgrade
CheckKind::TypeOfPrimitive(primitive) => {
format!("Use `{}` instead of `type(...)`", primitive.builtin())
@@ -1238,6 +1489,12 @@ impl CheckKind {
CheckKind::SuperCallWithParameters => {
"Use `super()` instead of `super(__class__, self)`".to_string()
}
CheckKind::UnnecessaryFutureImport(name) => {
format!("Unnessary __future__ import `{name}` for target Python version")
}
CheckKind::UnnecessaryLRUCacheParams => {
"Unnessary parameters to functools.lru_cache".to_string()
}
// pydocstyle
CheckKind::FitsOnOneLine => "One-line docstring should fit on one line".to_string(),
CheckKind::BlankLineAfterSummary => {
@@ -1398,6 +1655,30 @@ impl CheckKind {
CheckKind::ErrorSuffixOnExceptionName(name) => {
format!("Exception name `{name}` should be named with an Error suffix")
}
CheckKind::PEP3120UnnecessaryCodingComment => {
"utf-8 encoding declaration is unnecessary".to_string()
}
// isort
CheckKind::UnsortedImports => "Import block is un-sorted or un-formatted".to_string(),
// Ruff
CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) => {
format!(
"String contains ambiguous unicode character '{confusable}' (did you mean \
'{representant}'?)"
)
}
CheckKind::AmbiguousUnicodeCharacterDocstring(confusable, representant) => {
format!(
"Docstring contains ambiguous unicode character '{confusable}' (did you mean \
'{representant}'?)"
)
}
CheckKind::AmbiguousUnicodeCharacterComment(confusable, representant) => {
format!(
"Comment contains ambiguous unicode character '{confusable}' (did you mean \
'{representant}'?)"
)
}
// Meta
CheckKind::UnusedNOQA(codes) => match codes {
None => "Unused `noqa` directive".to_string(),
@@ -1431,6 +1712,9 @@ impl CheckKind {
CheckKind::NoAssertRaisesException => {
"`assertRaises(Exception):` should be considered evil.".to_string()
}
CheckKind::StarArgUnpackingAfterKeywordArg => {
"Star-arg unpacking after a keyword argument is strongly discouraged.".to_string()
}
_ => self.body(),
}
}
@@ -1439,7 +1723,9 @@ impl CheckKind {
pub fn fixable(&self) -> bool {
matches!(
self,
CheckKind::BlankLineAfterLastSection(_)
CheckKind::AmbiguousUnicodeCharacterString(_, _)
| CheckKind::AmbiguousUnicodeCharacterDocstring(_, _)
| CheckKind::BlankLineAfterLastSection(_)
| CheckKind::BlankLineAfterSection(_)
| CheckKind::BlankLineAfterSummary
| CheckKind::BlankLineBeforeSection(_)
@@ -1448,6 +1734,7 @@ impl CheckKind {
| CheckKind::DeprecatedUnittestAlias(_, _)
| CheckKind::DoNotAssertFalse
| CheckKind::DuplicateHandlerException(_)
| CheckKind::IsLiteral
| CheckKind::NewLineAfterLastParagraph
| CheckKind::NewLineAfterSectionName(_)
| CheckKind::NoBlankLineAfterFunction(_)
@@ -1459,8 +1746,10 @@ impl CheckKind {
| CheckKind::NoUnderIndentation
| CheckKind::OneBlankLineAfterClass(_)
| CheckKind::OneBlankLineBeforeClass(_)
| CheckKind::PEP3120UnnecessaryCodingComment
| CheckKind::PPrintFound
| CheckKind::PrintFound
| CheckKind::RaiseNotImplemented
| CheckKind::SectionNameEndsInColon(_)
| CheckKind::SectionNotOverIndented(_)
| CheckKind::SectionUnderlineAfterName(_)
@@ -1470,14 +1759,19 @@ impl CheckKind {
| CheckKind::TypeOfPrimitive(_)
| CheckKind::UnnecessaryAbspath
| CheckKind::UnnecessaryCollectionCall(_)
| CheckKind::UnnecessaryComprehension(_)
| CheckKind::UnnecessaryGeneratorDict
| CheckKind::UnnecessaryGeneratorList
| CheckKind::UnnecessaryGeneratorSet
| CheckKind::UnnecessaryLRUCacheParams
| CheckKind::UnnecessaryListCall
| CheckKind::UnnecessaryListComprehensionDict
| CheckKind::UnnecessaryListComprehensionSet
| CheckKind::UnnecessaryLiteralDict(_)
| CheckKind::UnnecessaryLiteralSet(_)
| CheckKind::UnnecessaryLiteralWithinListCall(_)
| CheckKind::UnnecessaryLiteralWithinTupleCall(_)
| CheckKind::UnsortedImports
| CheckKind::UnusedImport(_, false)
| CheckKind::UnusedLoopControlVariable(_)
| CheckKind::UnusedNOQA(_)

View File

@@ -1,11 +1,11 @@
//! File automatically generated by examples/generate_check_code_prefix.rs.
use serde::{Deserialize, Serialize};
use strum_macros::{AsRefStr, EnumString};
use strum_macros::EnumString;
use crate::checks::CheckCode;
#[derive(AsRefStr, EnumString, Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[derive(EnumString, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
pub enum CheckCodePrefix {
A,
A0,
@@ -13,19 +13,48 @@ pub enum CheckCodePrefix {
A001,
A002,
A003,
ANN,
ANN0,
ANN00,
ANN001,
ANN002,
ANN003,
ANN1,
ANN10,
ANN101,
ANN102,
ANN2,
ANN20,
ANN201,
ANN202,
ANN204,
ANN205,
ANN206,
ANN4,
ANN40,
ANN401,
B,
B0,
B00,
B002,
B003,
B004,
B005,
B006,
B007,
B008,
B009,
B01,
B011,
B013,
B014,
B015,
B016,
B017,
B018,
B02,
B025,
B026,
C,
C4,
C40,
@@ -174,6 +203,10 @@ pub enum CheckCodePrefix {
F9,
F90,
F901,
I,
I0,
I00,
I001,
M,
M0,
M00,
@@ -204,6 +237,12 @@ pub enum CheckCodePrefix {
Q001,
Q002,
Q003,
RUF,
RUF0,
RUF00,
RUF001,
RUF002,
RUF003,
T,
T2,
T20,
@@ -220,6 +259,10 @@ pub enum CheckCodePrefix {
U006,
U007,
U008,
U009,
U01,
U010,
U011,
W,
W2,
W29,
@@ -246,44 +289,125 @@ impl CheckCodePrefix {
CheckCodePrefix::A001 => vec![CheckCode::A001],
CheckCodePrefix::A002 => vec![CheckCode::A002],
CheckCodePrefix::A003 => vec![CheckCode::A003],
CheckCodePrefix::ANN => vec![
CheckCode::ANN001,
CheckCode::ANN002,
CheckCode::ANN003,
CheckCode::ANN101,
CheckCode::ANN102,
CheckCode::ANN201,
CheckCode::ANN202,
CheckCode::ANN204,
CheckCode::ANN205,
CheckCode::ANN206,
CheckCode::ANN401,
],
CheckCodePrefix::ANN0 => vec![CheckCode::ANN001, CheckCode::ANN002, CheckCode::ANN003],
CheckCodePrefix::ANN00 => vec![CheckCode::ANN001, CheckCode::ANN002, CheckCode::ANN003],
CheckCodePrefix::ANN001 => vec![CheckCode::ANN001],
CheckCodePrefix::ANN002 => vec![CheckCode::ANN002],
CheckCodePrefix::ANN003 => vec![CheckCode::ANN003],
CheckCodePrefix::ANN1 => vec![CheckCode::ANN101, CheckCode::ANN102],
CheckCodePrefix::ANN10 => vec![CheckCode::ANN101, CheckCode::ANN102],
CheckCodePrefix::ANN101 => vec![CheckCode::ANN101],
CheckCodePrefix::ANN102 => vec![CheckCode::ANN102],
CheckCodePrefix::ANN2 => vec![
CheckCode::ANN201,
CheckCode::ANN202,
CheckCode::ANN204,
CheckCode::ANN205,
CheckCode::ANN206,
],
CheckCodePrefix::ANN20 => vec![
CheckCode::ANN201,
CheckCode::ANN202,
CheckCode::ANN204,
CheckCode::ANN205,
CheckCode::ANN206,
],
CheckCodePrefix::ANN201 => vec![CheckCode::ANN201],
CheckCodePrefix::ANN202 => vec![CheckCode::ANN202],
CheckCodePrefix::ANN204 => vec![CheckCode::ANN204],
CheckCodePrefix::ANN205 => vec![CheckCode::ANN205],
CheckCodePrefix::ANN206 => vec![CheckCode::ANN206],
CheckCodePrefix::ANN4 => vec![CheckCode::ANN401],
CheckCodePrefix::ANN40 => vec![CheckCode::ANN401],
CheckCodePrefix::ANN401 => vec![CheckCode::ANN401],
CheckCodePrefix::B => vec![
CheckCode::B002,
CheckCode::B003,
CheckCode::B004,
CheckCode::B005,
CheckCode::B006,
CheckCode::B007,
CheckCode::B008,
CheckCode::B009,
CheckCode::B011,
CheckCode::B013,
CheckCode::B014,
CheckCode::B015,
CheckCode::B016,
CheckCode::B017,
CheckCode::B018,
CheckCode::B025,
CheckCode::B026,
],
CheckCodePrefix::B0 => vec![
CheckCode::B002,
CheckCode::B003,
CheckCode::B004,
CheckCode::B005,
CheckCode::B006,
CheckCode::B007,
CheckCode::B008,
CheckCode::B009,
CheckCode::B011,
CheckCode::B013,
CheckCode::B014,
CheckCode::B015,
CheckCode::B016,
CheckCode::B017,
CheckCode::B018,
CheckCode::B025,
CheckCode::B026,
],
CheckCodePrefix::B00 => vec![
CheckCode::B002,
CheckCode::B003,
CheckCode::B004,
CheckCode::B005,
CheckCode::B006,
CheckCode::B007,
CheckCode::B008,
CheckCode::B009,
],
CheckCodePrefix::B00 => vec![CheckCode::B002, CheckCode::B006, CheckCode::B007],
CheckCodePrefix::B002 => vec![CheckCode::B002],
CheckCodePrefix::B003 => vec![CheckCode::B003],
CheckCodePrefix::B004 => vec![CheckCode::B004],
CheckCodePrefix::B005 => vec![CheckCode::B005],
CheckCodePrefix::B006 => vec![CheckCode::B006],
CheckCodePrefix::B007 => vec![CheckCode::B007],
CheckCodePrefix::B01 => {
vec![
CheckCode::B011,
CheckCode::B013,
CheckCode::B014,
CheckCode::B017,
]
}
CheckCodePrefix::B008 => vec![CheckCode::B008],
CheckCodePrefix::B009 => vec![CheckCode::B009],
CheckCodePrefix::B01 => vec![
CheckCode::B011,
CheckCode::B013,
CheckCode::B014,
CheckCode::B015,
CheckCode::B016,
CheckCode::B017,
CheckCode::B018,
],
CheckCodePrefix::B011 => vec![CheckCode::B011],
CheckCodePrefix::B013 => vec![CheckCode::B013],
CheckCodePrefix::B014 => vec![CheckCode::B014],
CheckCodePrefix::B015 => vec![CheckCode::B015],
CheckCodePrefix::B016 => vec![CheckCode::B016],
CheckCodePrefix::B017 => vec![CheckCode::B017],
CheckCodePrefix::B02 => vec![CheckCode::B025],
CheckCodePrefix::B018 => vec![CheckCode::B018],
CheckCodePrefix::B02 => vec![CheckCode::B025, CheckCode::B026],
CheckCodePrefix::B025 => vec![CheckCode::B025],
CheckCodePrefix::B026 => vec![CheckCode::B026],
CheckCodePrefix::C => vec![
CheckCode::C400,
CheckCode::C401,
@@ -584,14 +708,12 @@ impl CheckCodePrefix {
CheckCode::E742,
CheckCode::E743,
],
CheckCodePrefix::E71 => {
vec![
CheckCode::E711,
CheckCode::E712,
CheckCode::E713,
CheckCode::E714,
]
}
CheckCodePrefix::E71 => vec![
CheckCode::E711,
CheckCode::E712,
CheckCode::E713,
CheckCode::E714,
],
CheckCodePrefix::E711 => vec![CheckCode::E711],
CheckCodePrefix::E712 => vec![CheckCode::E712],
CheckCodePrefix::E713 => vec![CheckCode::E713],
@@ -684,14 +806,12 @@ impl CheckCodePrefix {
CheckCodePrefix::F62 => vec![CheckCode::F621, CheckCode::F622],
CheckCodePrefix::F621 => vec![CheckCode::F621],
CheckCodePrefix::F622 => vec![CheckCode::F622],
CheckCodePrefix::F63 => {
vec![
CheckCode::F631,
CheckCode::F632,
CheckCode::F633,
CheckCode::F634,
]
}
CheckCodePrefix::F63 => vec![
CheckCode::F631,
CheckCode::F632,
CheckCode::F633,
CheckCode::F634,
],
CheckCodePrefix::F631 => vec![CheckCode::F631],
CheckCodePrefix::F632 => vec![CheckCode::F632],
CheckCodePrefix::F633 => vec![CheckCode::F633],
@@ -736,6 +856,10 @@ impl CheckCodePrefix {
CheckCodePrefix::F9 => vec![CheckCode::F901],
CheckCodePrefix::F90 => vec![CheckCode::F901],
CheckCodePrefix::F901 => vec![CheckCode::F901],
CheckCodePrefix::I => vec![CheckCode::I001],
CheckCodePrefix::I0 => vec![CheckCode::I001],
CheckCodePrefix::I00 => vec![CheckCode::I001],
CheckCodePrefix::I001 => vec![CheckCode::I001],
CheckCodePrefix::M => vec![CheckCode::M001],
CheckCodePrefix::M0 => vec![CheckCode::M001],
CheckCodePrefix::M00 => vec![CheckCode::M001],
@@ -808,34 +932,34 @@ impl CheckCodePrefix {
CheckCodePrefix::N816 => vec![CheckCode::N816],
CheckCodePrefix::N817 => vec![CheckCode::N817],
CheckCodePrefix::N818 => vec![CheckCode::N818],
CheckCodePrefix::Q => {
vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
]
}
CheckCodePrefix::Q0 => {
vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
]
}
CheckCodePrefix::Q00 => {
vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
]
}
CheckCodePrefix::Q => vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
],
CheckCodePrefix::Q0 => vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
],
CheckCodePrefix::Q00 => vec![
CheckCode::Q000,
CheckCode::Q001,
CheckCode::Q002,
CheckCode::Q003,
],
CheckCodePrefix::Q000 => vec![CheckCode::Q000],
CheckCodePrefix::Q001 => vec![CheckCode::Q001],
CheckCodePrefix::Q002 => vec![CheckCode::Q002],
CheckCodePrefix::Q003 => vec![CheckCode::Q003],
CheckCodePrefix::RUF => vec![CheckCode::RUF001, CheckCode::RUF002, CheckCode::RUF003],
CheckCodePrefix::RUF0 => vec![CheckCode::RUF001, CheckCode::RUF002, CheckCode::RUF003],
CheckCodePrefix::RUF00 => vec![CheckCode::RUF001, CheckCode::RUF002, CheckCode::RUF003],
CheckCodePrefix::RUF001 => vec![CheckCode::RUF001],
CheckCodePrefix::RUF002 => vec![CheckCode::RUF002],
CheckCodePrefix::RUF003 => vec![CheckCode::RUF003],
CheckCodePrefix::T => vec![CheckCode::T201, CheckCode::T203],
CheckCodePrefix::T2 => vec![CheckCode::T201, CheckCode::T203],
CheckCodePrefix::T20 => vec![CheckCode::T201, CheckCode::T203],
@@ -850,6 +974,9 @@ impl CheckCodePrefix {
CheckCode::U006,
CheckCode::U007,
CheckCode::U008,
CheckCode::U009,
CheckCode::U010,
CheckCode::U011,
],
CheckCodePrefix::U0 => vec![
CheckCode::U001,
@@ -860,6 +987,9 @@ impl CheckCodePrefix {
CheckCode::U006,
CheckCode::U007,
CheckCode::U008,
CheckCode::U009,
CheckCode::U010,
CheckCode::U011,
],
CheckCodePrefix::U00 => vec![
CheckCode::U001,
@@ -870,6 +1000,7 @@ impl CheckCodePrefix {
CheckCode::U006,
CheckCode::U007,
CheckCode::U008,
CheckCode::U009,
],
CheckCodePrefix::U001 => vec![CheckCode::U001],
CheckCodePrefix::U002 => vec![CheckCode::U002],
@@ -879,6 +1010,10 @@ impl CheckCodePrefix {
CheckCodePrefix::U006 => vec![CheckCode::U006],
CheckCodePrefix::U007 => vec![CheckCode::U007],
CheckCodePrefix::U008 => vec![CheckCode::U008],
CheckCodePrefix::U009 => vec![CheckCode::U009],
CheckCodePrefix::U01 => vec![CheckCode::U010, CheckCode::U011],
CheckCodePrefix::U010 => vec![CheckCode::U010],
CheckCodePrefix::U011 => vec![CheckCode::U011],
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
CheckCodePrefix::W2 => vec![CheckCode::W292],
CheckCodePrefix::W29 => vec![CheckCode::W292],
@@ -899,19 +1034,48 @@ impl CheckCodePrefix {
CheckCodePrefix::A001 => PrefixSpecificity::Explicit,
CheckCodePrefix::A002 => PrefixSpecificity::Explicit,
CheckCodePrefix::A003 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN => PrefixSpecificity::Category,
CheckCodePrefix::ANN0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::ANN00 => PrefixSpecificity::Tens,
CheckCodePrefix::ANN001 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN002 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN003 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN1 => PrefixSpecificity::Hundreds,
CheckCodePrefix::ANN10 => PrefixSpecificity::Tens,
CheckCodePrefix::ANN101 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN102 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN2 => PrefixSpecificity::Hundreds,
CheckCodePrefix::ANN20 => PrefixSpecificity::Tens,
CheckCodePrefix::ANN201 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN202 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN204 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN205 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN206 => PrefixSpecificity::Explicit,
CheckCodePrefix::ANN4 => PrefixSpecificity::Hundreds,
CheckCodePrefix::ANN40 => PrefixSpecificity::Tens,
CheckCodePrefix::ANN401 => PrefixSpecificity::Explicit,
CheckCodePrefix::B => PrefixSpecificity::Category,
CheckCodePrefix::B0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::B00 => PrefixSpecificity::Tens,
CheckCodePrefix::B002 => PrefixSpecificity::Explicit,
CheckCodePrefix::B003 => PrefixSpecificity::Explicit,
CheckCodePrefix::B004 => PrefixSpecificity::Explicit,
CheckCodePrefix::B005 => PrefixSpecificity::Explicit,
CheckCodePrefix::B006 => PrefixSpecificity::Explicit,
CheckCodePrefix::B007 => PrefixSpecificity::Explicit,
CheckCodePrefix::B008 => PrefixSpecificity::Explicit,
CheckCodePrefix::B009 => PrefixSpecificity::Explicit,
CheckCodePrefix::B01 => PrefixSpecificity::Tens,
CheckCodePrefix::B011 => PrefixSpecificity::Explicit,
CheckCodePrefix::B013 => PrefixSpecificity::Explicit,
CheckCodePrefix::B014 => PrefixSpecificity::Explicit,
CheckCodePrefix::B015 => PrefixSpecificity::Explicit,
CheckCodePrefix::B016 => PrefixSpecificity::Explicit,
CheckCodePrefix::B017 => PrefixSpecificity::Explicit,
CheckCodePrefix::B018 => PrefixSpecificity::Explicit,
CheckCodePrefix::B02 => PrefixSpecificity::Tens,
CheckCodePrefix::B025 => PrefixSpecificity::Explicit,
CheckCodePrefix::B026 => PrefixSpecificity::Explicit,
CheckCodePrefix::C => PrefixSpecificity::Category,
CheckCodePrefix::C4 => PrefixSpecificity::Hundreds,
CheckCodePrefix::C40 => PrefixSpecificity::Tens,
@@ -1060,6 +1224,10 @@ impl CheckCodePrefix {
CheckCodePrefix::F9 => PrefixSpecificity::Hundreds,
CheckCodePrefix::F90 => PrefixSpecificity::Tens,
CheckCodePrefix::F901 => PrefixSpecificity::Explicit,
CheckCodePrefix::I => PrefixSpecificity::Category,
CheckCodePrefix::I0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::I00 => PrefixSpecificity::Tens,
CheckCodePrefix::I001 => PrefixSpecificity::Explicit,
CheckCodePrefix::M => PrefixSpecificity::Category,
CheckCodePrefix::M0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::M00 => PrefixSpecificity::Tens,
@@ -1090,6 +1258,12 @@ impl CheckCodePrefix {
CheckCodePrefix::Q001 => PrefixSpecificity::Explicit,
CheckCodePrefix::Q002 => PrefixSpecificity::Explicit,
CheckCodePrefix::Q003 => PrefixSpecificity::Explicit,
CheckCodePrefix::RUF => PrefixSpecificity::Category,
CheckCodePrefix::RUF0 => PrefixSpecificity::Hundreds,
CheckCodePrefix::RUF00 => PrefixSpecificity::Tens,
CheckCodePrefix::RUF001 => PrefixSpecificity::Explicit,
CheckCodePrefix::RUF002 => PrefixSpecificity::Explicit,
CheckCodePrefix::RUF003 => PrefixSpecificity::Explicit,
CheckCodePrefix::T => PrefixSpecificity::Category,
CheckCodePrefix::T2 => PrefixSpecificity::Hundreds,
CheckCodePrefix::T20 => PrefixSpecificity::Tens,
@@ -1106,6 +1280,10 @@ impl CheckCodePrefix {
CheckCodePrefix::U006 => PrefixSpecificity::Explicit,
CheckCodePrefix::U007 => PrefixSpecificity::Explicit,
CheckCodePrefix::U008 => PrefixSpecificity::Explicit,
CheckCodePrefix::U009 => PrefixSpecificity::Explicit,
CheckCodePrefix::U01 => PrefixSpecificity::Tens,
CheckCodePrefix::U010 => PrefixSpecificity::Explicit,
CheckCodePrefix::U011 => PrefixSpecificity::Explicit,
CheckCodePrefix::W => PrefixSpecificity::Category,
CheckCodePrefix::W2 => PrefixSpecificity::Hundreds,
CheckCodePrefix::W29 => PrefixSpecificity::Tens,

View File

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

View File

@@ -1,19 +1,21 @@
use anyhow::Result;
use libcst_native::Module;
use rustpython_ast::Located;
use libcst_native::{Expr, Module, SmallStatement, Statement};
use crate::ast::types::Range;
use crate::source_code_locator::SourceCodeLocator;
pub fn match_tree<'a, T>(
locator: &'a SourceCodeLocator,
located: &'a Located<T>,
) -> Result<Module<'a>> {
match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(located)),
None,
) {
pub fn match_module(module_text: &str) -> Result<Module> {
match libcst_native::parse_module(module_text, None) {
Ok(module) => Ok(module),
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
Err(_) => Err(anyhow::anyhow!("Failed to extract CST from source.")),
}
}
pub fn match_expr<'a, 'b>(module: &'a mut Module<'b>) -> Result<&'a mut Expr<'b>> {
if let Some(Statement::Simple(expr)) = module.body.first_mut() {
if let Some(SmallStatement::Expr(expr)) = expr.body.first_mut() {
Ok(expr)
} else {
Err(anyhow::anyhow!("Expected node to be: SmallStatement::Expr"))
}
} else {
Err(anyhow::anyhow!("Expected node to be: Statement::Simple"))
}
}

View File

@@ -1,4 +1,4 @@
use rustpython_ast::{Expr, Location};
use rustpython_ast::{Located, Location};
use crate::ast::types::Range;
use crate::check_ast::Checker;
@@ -24,13 +24,16 @@ pub fn leading_space(line: &str) -> String {
.collect()
}
/// Extract the leading indentation from a docstring.
pub fn indentation<'a>(checker: &'a Checker, docstring: &Expr) -> &'a str {
let range = Range::from_located(docstring);
checker.locator.slice_source_code_range(&Range {
location: Location::new(range.location.row(), 0),
end_location: Location::new(range.location.row(), range.location.column()),
})
/// Extract the leading indentation from a line.
pub fn indentation<'a, T>(checker: &'a Checker, located: &Located<T>) -> String {
let range = Range::from_located(located);
checker
.locator
.slice_source_code_range(&Range {
location: Location::new(range.location.row(), 0),
end_location: Location::new(range.location.row(), range.location.column()),
})
.to_string()
}
/// Replace any non-whitespace characters from an indentation string.

View File

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

View File

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

View File

@@ -0,0 +1,40 @@
//! Settings for the `flake-annotations` plugin.
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
/// Allow omission of a return type hint for `__init__` if at least one
/// argument is annotated.
pub mypy_init_return: Option<bool>,
/// Suppress ANN000-level errors for dummy arguments, like `_`.
pub suppress_dummy_args: Option<bool>,
/// Suppress ANN200-level errors for functions that meet one of the
/// following criteria:
/// - Contain no `return` statement
/// - Explicit `return` statement(s) all return `None` (explicitly or
/// implicitly).
pub suppress_none_returning: Option<bool>,
/// Suppress ANN401 for dynamically typed *args and **kwargs.
pub allow_star_arg_any: Option<bool>,
}
#[derive(Debug, Hash, Default)]
pub struct Settings {
pub mypy_init_return: bool,
pub suppress_dummy_args: bool,
pub suppress_none_returning: bool,
pub allow_star_arg_any: bool,
}
impl Settings {
pub fn from_options(options: Options) -> Self {
Self {
mypy_init_return: options.mypy_init_return.unwrap_or_default(),
suppress_dummy_args: options.suppress_dummy_args.unwrap_or_default(),
suppress_none_returning: options.suppress_none_returning.unwrap_or_default(),
allow_star_arg_any: options.allow_star_arg_any.unwrap_or_default(),
}
}
}

View File

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

View File

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

View File

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

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