Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10b250ee57 | ||
|
|
30b1b1e15a | ||
|
|
aafe7c0c39 | ||
|
|
f060248656 | ||
|
|
bbe0220c72 | ||
|
|
129e2b6ad3 | ||
|
|
73e744b1d0 | ||
|
|
50a3fc5a67 | ||
|
|
de499f0258 | ||
|
|
e1abe37c6a | ||
|
|
7fe5945541 | ||
|
|
806f3fd4f6 | ||
|
|
2bba643dd2 | ||
|
|
54090bd7ac | ||
|
|
c62727db42 | ||
|
|
d0e1612507 | ||
|
|
5ccd907398 | ||
|
|
346610c2e3 | ||
|
|
307fa26515 | ||
|
|
136d412edd | ||
|
|
d9edec0ac9 | ||
|
|
473675fffb | ||
|
|
95dfc61315 | ||
|
|
dd496c7b52 | ||
|
|
78aafb4b34 | ||
|
|
99c66d513a | ||
|
|
04ade6a2f3 | ||
|
|
4cf2682cda | ||
|
|
e3a7357187 | ||
|
|
b60768debb | ||
|
|
25e476639f | ||
|
|
4645788205 | ||
|
|
0b9eda8836 | ||
|
|
ad23d6acee | ||
|
|
e3d1d01a1f | ||
|
|
6dfdd21a7c | ||
|
|
f17d3b3c44 | ||
|
|
da6b913317 | ||
|
|
bd34850f98 | ||
|
|
d5b33cdb40 | ||
|
|
3fb4cf7009 | ||
|
|
6e19539e28 | ||
|
|
4eac7a07f5 | ||
|
|
5141285c8e | ||
|
|
82cc139d2d | ||
|
|
df438ba051 | ||
|
|
a70624cd47 | ||
|
|
b307afc00c | ||
|
|
3d5bc1f51f | ||
|
|
aba01745f5 | ||
|
|
1eeeffab66 |
@@ -14,6 +14,12 @@ your proposed change.
|
||||
ruff is written in Rust (1.63.0). You'll need to install the
|
||||
[Rust toolchain](https://www.rust-lang.org/tools/install) for development.
|
||||
|
||||
You'll also need [Insta](https://insta.rs/docs/) to update snapshot tests:
|
||||
|
||||
```shell
|
||||
cargo install cargo-insta
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
After cloning the repository, run ruff locally with:
|
||||
@@ -58,7 +64,7 @@ similar rules are implemented.
|
||||
To add a test fixture, create a file under `resources/test/fixtures`, named to match the `CheckCode`
|
||||
you defined earlier (e.g., `E402.py`). This file should contain a variety of violations and
|
||||
non-violations designed to evaluate and demonstrate the behavior of your lint rule. Run ruff locally
|
||||
with (e.g.) `cargo run resources/test/fixtures/E402.py`. Once you're satisified with the output,
|
||||
with (e.g.) `cargo run resources/test/fixtures/E402.py`. Once you're satisfied with the output,
|
||||
codify the behavior as a snapshot test by adding a new function to the `mod tests` section of
|
||||
`src/linter.rs`, like so:
|
||||
|
||||
|
||||
35
Cargo.lock
generated
35
Cargo.lock
generated
@@ -1907,7 +1907,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.55"
|
||||
version = "0.0.64"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@@ -1926,6 +1926,7 @@ dependencies = [
|
||||
"libcst",
|
||||
"log",
|
||||
"notify",
|
||||
"num-bigint",
|
||||
"once_cell",
|
||||
"path-absolutize",
|
||||
"rayon",
|
||||
@@ -1935,6 +1936,8 @@ dependencies = [
|
||||
"rustpython-parser",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"toml",
|
||||
"update-informer",
|
||||
"walkdir",
|
||||
@@ -1955,7 +1958,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=4f457893efc381ad5c432576b24bcc7e4a08c641#4f457893efc381ad5c432576b24bcc7e4a08c641"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f#778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-common",
|
||||
@@ -1965,7 +1968,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-common"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=4f457893efc381ad5c432576b24bcc7e4a08c641#4f457893efc381ad5c432576b24bcc7e4a08c641"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f#778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f"
|
||||
dependencies = [
|
||||
"ascii",
|
||||
"cfg-if 1.0.0",
|
||||
@@ -1988,7 +1991,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=4f457893efc381ad5c432576b24bcc7e4a08c641#4f457893efc381ad5c432576b24bcc7e4a08c641"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f#778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -2005,7 +2008,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=4f457893efc381ad5c432576b24bcc7e4a08c641#4f457893efc381ad5c432576b24bcc7e4a08c641"
|
||||
source = "git+https://github.com/charliermarsh/RustPython.git?rev=778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f#778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
@@ -2252,6 +2255,28 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.24.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.101"
|
||||
|
||||
11
Cargo.toml
11
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.55"
|
||||
version = "0.0.64"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -27,14 +27,17 @@ once_cell = { version = "1.13.1" }
|
||||
path-absolutize = { version = "3.0.13", features = ["once_cell_cache"] }
|
||||
rayon = { version = "1.5.3" }
|
||||
regex = { version = "1.6.0" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/charliermarsh/RustPython.git", rev = "4f457893efc381ad5c432576b24bcc7e4a08c641" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "4f457893efc381ad5c432576b24bcc7e4a08c641" }
|
||||
rustpython-common = { git = "https://github.com/charliermarsh/RustPython.git", rev = "4f457893efc381ad5c432576b24bcc7e4a08c641" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/charliermarsh/RustPython.git", rev = "778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f" }
|
||||
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f" }
|
||||
rustpython-common = { git = "https://github.com/charliermarsh/RustPython.git", rev = "778ae2aeb521d0438d2a91bd11238bb5c2bf9d4f" }
|
||||
serde = { version = "1.0.143", features = ["derive"] }
|
||||
serde_json = { version = "1.0.83" }
|
||||
toml = { version = "0.5.9" }
|
||||
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
|
||||
walkdir = { version = "2.3.2" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
strum_macros = "0.24.3"
|
||||
num-bigint = "0.4.3"
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { version = "1.19.1", features = ["yaml"] }
|
||||
|
||||
25
README.md
25
README.md
@@ -130,6 +130,8 @@ Options:
|
||||
Enable automatic additions of noqa directives to failing lines
|
||||
--dummy-variable-rgx <DUMMY_VARIABLE_RGX>
|
||||
Regular expression matching the name of dummy variables
|
||||
--target-version <TARGET_VERSION>
|
||||
The minimum Python version that should be supported
|
||||
-h, --help
|
||||
Print help information
|
||||
-V, --version
|
||||
@@ -213,6 +215,7 @@ ruff also implements some of the most popular Flake8 plugins natively, including
|
||||
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
|
||||
- [`flake8-super`](https://pypi.org/project/flake8-super/)
|
||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) (partial)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (partial)
|
||||
|
||||
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
|
||||
@@ -243,6 +246,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| E743 | AmbiguousFunctionName | Ambiguous function name: `...` | ✅ | |
|
||||
| E902 | IOError | IOError: `...` | ✅ | |
|
||||
| E999 | SyntaxError | SyntaxError: `...` | ✅ | |
|
||||
| W292 | NoNewLineAtEndOfFile | No newline at end of file | ✅ | |
|
||||
| F401 | UnusedImport | `...` imported but unused | ✅ | 🛠 |
|
||||
| F402 | ImportShadowedByLoopVar | Import `...` from line 1 shadowed by loop variable | ✅ | |
|
||||
| F403 | ImportStarUsed | `from ... import *` used; unable to detect undefined names | ✅ | |
|
||||
@@ -274,12 +278,25 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| A001 | BuiltinVariableShadowing | Variable `...` is shadowing a python builtin | | |
|
||||
| A002 | BuiltinArgumentShadowing | Argument `...` is shadowing a python builtin | | |
|
||||
| A003 | BuiltinAttributeShadowing | Class attribute `...` is shadowing a python builtin | | |
|
||||
| C400 | UnnecessaryGeneratorList | Unnecessary generator - rewrite as a list comprehension | | |
|
||||
| C401 | UnnecessaryGeneratorSet | Unnecessary generator - rewrite as a set comprehension | | |
|
||||
| C402 | UnnecessaryGeneratorDict | Unnecessary generator - rewrite as a dict comprehension | | |
|
||||
| C403 | UnnecessaryListComprehensionSet | Unnecessary list comprehension - rewrite as a set comprehension | | |
|
||||
| C404 | UnnecessaryListComprehensionDict | Unnecessary list comprehension - rewrite as a dict comprehension | | |
|
||||
| C405 | UnnecessaryLiteralSet | Unnecessary <list/tuple> literal - rewrite as a set literal | | |
|
||||
| C406 | UnnecessaryLiteralDict | Unnecessary <list/tuple> literal - rewrite as a dict literal | | |
|
||||
| C408 | UnnecessaryCollectionCall | Unnecessary <dict/list/tuple> call - rewrite as a literal | | |
|
||||
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within <reversed/set/sorted>() | | |
|
||||
| SPR001 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | | 🛠 |
|
||||
| T201 | PrintFound | `print` found | | 🛠 |
|
||||
| T203 | PPrintFound | `pprint` found | | 🛠 |
|
||||
| U001 | UselessMetaclassType | `__metaclass__ = type` is implied | | 🛠 |
|
||||
| R001 | UselessObjectInheritance | Class `...` inherits from object | | 🛠 |
|
||||
| R002 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead | | 🛠 |
|
||||
| U002 | UnnecessaryAbspath | `abspath(__file__)` is unnecessary in Python 3.9 and later | | 🛠 |
|
||||
| U003 | TypeOfPrimitive | Use `str` instead of `type(...)` | | 🛠 |
|
||||
| U004 | UselessObjectInheritance | Class `...` inherits from object | | 🛠 |
|
||||
| U005 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead | | 🛠 |
|
||||
| U006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | | 🛠 |
|
||||
| U007 | UsePEP604Annotation | Use `X \| Y` for type annotations | | 🛠 |
|
||||
| M001 | UnusedNOQA | Unused `noqa` directive | | 🛠 |
|
||||
|
||||
## Integrations
|
||||
@@ -307,9 +324,9 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
/// Generate a Markdown-compatible table of supported lint rules.
|
||||
use ruff::checks::{CheckCode, ALL_CHECK_CODES, DEFAULT_CHECK_CODES};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use ruff::checks::{CheckCode, DEFAULT_CHECK_CODES};
|
||||
|
||||
fn main() {
|
||||
let mut check_codes: Vec<CheckCode> = ALL_CHECK_CODES.to_vec();
|
||||
check_codes.sort();
|
||||
|
||||
println!("| Code | Name | Message | | |");
|
||||
println!("| ---- | ---- | ------- | --- | --- |");
|
||||
for check_code in check_codes {
|
||||
for check_code in CheckCode::iter() {
|
||||
let check_kind = check_code.kind();
|
||||
let default_token = if DEFAULT_CHECK_CODES.contains(&check_code) {
|
||||
"✅"
|
||||
@@ -17,9 +16,9 @@ fn main() {
|
||||
let fix_token = if check_kind.fixable() { "🛠" } else { "" };
|
||||
println!(
|
||||
"| {} | {} | {} | {} | {} |",
|
||||
check_kind.code().as_str(),
|
||||
check_kind.name(),
|
||||
check_kind.body(),
|
||||
check_kind.code().as_ref(),
|
||||
check_kind.as_ref(),
|
||||
check_kind.body().replace("|", r"\|"),
|
||||
default_token,
|
||||
fix_token
|
||||
);
|
||||
|
||||
37
output.py
37
output.py
@@ -1,37 +0,0 @@
|
||||
from collections import Counter
|
||||
|
||||
|
||||
def f() -> None:
|
||||
"""Docstring goes here."""
|
||||
for x in range(5):
|
||||
print(x)
|
||||
else:
|
||||
print("Nope!")
|
||||
|
||||
|
||||
a = {
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
}
|
||||
|
||||
cls(title=title, before_text=before_text, after_text=after_text, before_description=before_description, after_description=after_description, transform_type=transform_type)
|
||||
|
||||
1
resources/test/fixtures/C400.py
vendored
Normal file
1
resources/test/fixtures/C400.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
x = list(x for x in range(3))
|
||||
1
resources/test/fixtures/C401.py
vendored
Normal file
1
resources/test/fixtures/C401.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
x = set(x for x in range(3))
|
||||
1
resources/test/fixtures/C402.py
vendored
Normal file
1
resources/test/fixtures/C402.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
d = dict((x, x) for x in range(3))
|
||||
1
resources/test/fixtures/C403.py
vendored
Normal file
1
resources/test/fixtures/C403.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
s = set([x for x in range(3)])
|
||||
1
resources/test/fixtures/C404.py
vendored
Normal file
1
resources/test/fixtures/C404.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
d = dict([(i, i) for i in range(3)])
|
||||
5
resources/test/fixtures/C405.py
vendored
Normal file
5
resources/test/fixtures/C405.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
s1 = set([1, 2])
|
||||
s2 = set((1, 2))
|
||||
s3 = set([])
|
||||
s4 = set(())
|
||||
s5 = set()
|
||||
5
resources/test/fixtures/C406.py
vendored
Normal file
5
resources/test/fixtures/C406.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
d1 = dict([(1, 2)])
|
||||
d2 = dict(((1, 2),))
|
||||
d3 = dict([])
|
||||
d4 = dict(())
|
||||
d5 = dict()
|
||||
5
resources/test/fixtures/C408.py
vendored
Normal file
5
resources/test/fixtures/C408.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
t = tuple()
|
||||
l = list()
|
||||
d1 = dict()
|
||||
d2 = dict(a=1)
|
||||
d3 = dict(**d2)
|
||||
9
resources/test/fixtures/C415.py
vendored
Normal file
9
resources/test/fixtures/C415.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
lst = [2, 1, 3]
|
||||
a = set(lst[::-1])
|
||||
b = reversed(lst[::-1])
|
||||
c = sorted(lst[::-1])
|
||||
d = sorted(lst[::-1], reverse=True)
|
||||
e = set(lst[2:-1])
|
||||
f = set(lst[:1:-1])
|
||||
g = set(lst[::1])
|
||||
h = set(lst[::-2])
|
||||
21
resources/test/fixtures/F401.py
vendored
21
resources/test/fixtures/F401.py
vendored
@@ -65,3 +65,24 @@ b = Union["Nut", None]
|
||||
c = cast("Vegetable", b)
|
||||
|
||||
Field = lambda default=MISSING: field(default=default)
|
||||
|
||||
|
||||
# Test: access a sub-importation via an alias.
|
||||
import pyarrow as pa
|
||||
import pyarrow.csv
|
||||
|
||||
print(pa.csv.read_csv("test.csv"))
|
||||
|
||||
|
||||
# Test: referencing an import via TypeAlias.
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
from typing import TypeAlias
|
||||
else:
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
|
||||
CustomInt: TypeAlias = "np.int8 | np.int16"
|
||||
|
||||
33
resources/test/fixtures/F821.py
vendored
33
resources/test/fixtures/F821.py
vendored
@@ -89,3 +89,36 @@ A = (
|
||||
f'B'
|
||||
f'{B}'
|
||||
)
|
||||
|
||||
|
||||
from typing import Annotated, Literal
|
||||
|
||||
|
||||
def arbitrary_callable() -> None:
|
||||
...
|
||||
|
||||
|
||||
class PEP593Test:
|
||||
field: Annotated[
|
||||
int,
|
||||
"base64",
|
||||
arbitrary_callable(),
|
||||
123,
|
||||
(1, 2, 3),
|
||||
]
|
||||
field_with_stringified_type: Annotated[
|
||||
"PEP593Test",
|
||||
123,
|
||||
]
|
||||
field_with_undefined_stringified_type: Annotated[
|
||||
"PEP593Test123",
|
||||
123,
|
||||
]
|
||||
field_with_nested_subscript: Annotated[
|
||||
dict[Literal["foo"], str],
|
||||
123,
|
||||
]
|
||||
field_with_undefined_nested_subscript: Annotated[
|
||||
dict["foo", "bar"], # Expected to fail as undefined.
|
||||
123,
|
||||
]
|
||||
|
||||
15
resources/test/fixtures/U002.py
vendored
Normal file
15
resources/test/fixtures/U002.py
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
from os.path import abspath
|
||||
|
||||
x = abspath(__file__)
|
||||
|
||||
|
||||
import os
|
||||
|
||||
|
||||
y = os.path.abspath(__file__)
|
||||
|
||||
|
||||
from os import path
|
||||
|
||||
|
||||
z = path.abspath(__file__)
|
||||
5
resources/test/fixtures/U003.py
vendored
Normal file
5
resources/test/fixtures/U003.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
type('')
|
||||
type(b'')
|
||||
type(0)
|
||||
type(0.)
|
||||
type(0j)
|
||||
12
resources/test/fixtures/U006.py
vendored
Normal file
12
resources/test/fixtures/U006.py
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
from typing import List
|
||||
|
||||
|
||||
def f(x: List[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
|
||||
def f(x: typing.List[str]) -> None:
|
||||
...
|
||||
40
resources/test/fixtures/U007.py
vendored
Normal file
40
resources/test/fixtures/U007.py
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def f(x: Optional[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
|
||||
def f(x: typing.Optional[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
from typing import Union
|
||||
|
||||
|
||||
def f(x: Union[str, int, Union[float, bytes]]) -> None:
|
||||
...
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
|
||||
def f(x: typing.Union[str, int]) -> None:
|
||||
...
|
||||
|
||||
|
||||
from typing import Union
|
||||
|
||||
|
||||
def f(x: "Union[str, int, Union[float, bytes]]") -> None:
|
||||
...
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
|
||||
def f(x: "typing.Union[str, int]") -> None:
|
||||
...
|
||||
2
resources/test/fixtures/W292_0.py
vendored
Normal file
2
resources/test/fixtures/W292_0.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
def fn() -> None:
|
||||
pass
|
||||
2
resources/test/fixtures/W292_1.py
vendored
Normal file
2
resources/test/fixtures/W292_1.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
def fn() -> None:
|
||||
pass # noqa: W292
|
||||
2
resources/test/fixtures/W292_2.py
vendored
Normal file
2
resources/test/fixtures/W292_2.py
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
def fn() -> None:
|
||||
pass
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod checks;
|
||||
pub mod helpers;
|
||||
pub mod operations;
|
||||
pub mod relocate;
|
||||
pub mod types;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use itertools::izip;
|
||||
use num_bigint::BigInt;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::ast::{
|
||||
Arg, ArgData, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind,
|
||||
Keyword, Location, Stmt, StmtKind, Unaryop,
|
||||
KeywordData, Located, Stmt, StmtKind, Unaryop,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ast::operations::SourceCodeLocator;
|
||||
use crate::ast::types::{
|
||||
Binding, BindingKind, CheckLocator, FunctionScope, Range, Scope, ScopeKind,
|
||||
};
|
||||
use crate::autofix::{fixer, fixes};
|
||||
use crate::checks::{Check, CheckKind, Fix, RejectedCmpop};
|
||||
use crate::checks::{Check, CheckKind, RejectedCmpop};
|
||||
use crate::python::builtins::BUILTINS;
|
||||
|
||||
/// Check IfTuple compliance.
|
||||
@@ -136,6 +136,86 @@ pub fn check_useless_metaclass_type(
|
||||
None
|
||||
}
|
||||
|
||||
/// Check UnnecessaryAbspath compliance.
|
||||
pub fn check_unnecessary_abspath(func: &Expr, args: &Vec<Expr>, location: Range) -> Option<Check> {
|
||||
// Validate the arguments.
|
||||
if args.len() == 1 {
|
||||
if let ExprKind::Name { id, .. } = &args[0].node {
|
||||
if id == "__file__" {
|
||||
match &func.node {
|
||||
ExprKind::Attribute { attr: id, .. } | ExprKind::Name { id, .. } => {
|
||||
if id == "abspath" {
|
||||
return Some(Check::new(CheckKind::UnnecessaryAbspath, location));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum Primitive {
|
||||
Bool,
|
||||
Str,
|
||||
Bytes,
|
||||
Int,
|
||||
Float,
|
||||
Complex,
|
||||
}
|
||||
|
||||
impl Primitive {
|
||||
fn from_constant(constant: &Constant) -> Option<Self> {
|
||||
match constant {
|
||||
Constant::Bool(_) => Some(Primitive::Bool),
|
||||
Constant::Str(_) => Some(Primitive::Str),
|
||||
Constant::Bytes(_) => Some(Primitive::Bytes),
|
||||
Constant::Int(_) => Some(Primitive::Int),
|
||||
Constant::Float(_) => Some(Primitive::Float),
|
||||
Constant::Complex { .. } => Some(Primitive::Complex),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builtin(&self) -> String {
|
||||
match self {
|
||||
Primitive::Bool => "bool".to_string(),
|
||||
Primitive::Str => "str".to_string(),
|
||||
Primitive::Bytes => "bytes".to_string(),
|
||||
Primitive::Int => "int".to_string(),
|
||||
Primitive::Float => "float".to_string(),
|
||||
Primitive::Complex => "complex".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check TypeOfPrimitive compliance.
|
||||
pub fn check_type_of_primitive(func: &Expr, args: &Vec<Expr>, location: Range) -> Option<Check> {
|
||||
// Validate the arguments.
|
||||
if args.len() == 1 {
|
||||
match &func.node {
|
||||
ExprKind::Attribute { attr: id, .. } | ExprKind::Name { id, .. } => {
|
||||
if id == "type" {
|
||||
if let ExprKind::Constant { value, .. } = &args[0].node {
|
||||
if let Some(primitive) = Primitive::from_constant(value) {
|
||||
return Some(Check::new(
|
||||
CheckKind::TypeOfPrimitive(primitive),
|
||||
location,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn is_ambiguous_name(name: &str) -> bool {
|
||||
name == "l" || name == "I" || name == "O"
|
||||
}
|
||||
@@ -178,13 +258,9 @@ pub fn check_ambiguous_function_name(name: &str, location: Range) -> Option<Chec
|
||||
|
||||
/// Check UselessObjectInheritance compliance.
|
||||
pub fn check_useless_object_inheritance(
|
||||
stmt: &Stmt,
|
||||
name: &str,
|
||||
bases: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
scope: &Scope,
|
||||
locator: &mut SourceCodeLocator,
|
||||
autofix: &fixer::Mode,
|
||||
) -> Option<Check> {
|
||||
for expr in bases {
|
||||
if let ExprKind::Name { id, .. } = &expr.node {
|
||||
@@ -195,22 +271,10 @@ pub fn check_useless_object_inheritance(
|
||||
kind: BindingKind::Builtin,
|
||||
..
|
||||
}) => {
|
||||
let mut check = Check::new(
|
||||
return Some(Check::new(
|
||||
CheckKind::UselessObjectInheritance(name.to_string()),
|
||||
Range::from_located(expr),
|
||||
);
|
||||
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
if let Some(fix) = fixes::remove_class_def_base(
|
||||
locator,
|
||||
&stmt.location,
|
||||
expr.location,
|
||||
bases,
|
||||
keywords,
|
||||
) {
|
||||
check.amend(fix);
|
||||
}
|
||||
}
|
||||
return Some(check);
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -298,28 +362,15 @@ pub fn check_duplicate_arguments(arguments: &Arguments) -> Vec<Check> {
|
||||
}
|
||||
|
||||
/// Check AssertEquals compliance.
|
||||
pub fn check_assert_equals(expr: &Expr, autofix: &fixer::Mode) -> Option<Check> {
|
||||
pub fn check_assert_equals(expr: &Expr) -> Option<Check> {
|
||||
if let ExprKind::Attribute { value, attr, .. } = &expr.node {
|
||||
if attr == "assertEquals" {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
if id == "self" {
|
||||
let mut check =
|
||||
Check::new(CheckKind::NoAssertEquals, Range::from_located(expr));
|
||||
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
check.amend(Fix {
|
||||
content: "assertEqual".to_string(),
|
||||
location: Location::new(
|
||||
expr.location.row(),
|
||||
expr.location.column() + 1,
|
||||
),
|
||||
end_location: Location::new(
|
||||
expr.location.row(),
|
||||
expr.location.column() + 1 + "assertEquals".len(),
|
||||
),
|
||||
applied: false,
|
||||
});
|
||||
}
|
||||
return Some(check);
|
||||
return Some(Check::new(
|
||||
CheckKind::NoAssertEquals,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -723,6 +774,256 @@ pub fn is_super_call_with_arguments(func: &Expr, args: &Vec<Expr>) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
// flakes8-comprehensions
|
||||
/// Check `list(generator)` compliance.
|
||||
pub fn unnecessary_generator_list(expr: &Expr, func: &Expr, args: &Vec<Expr>) -> Option<Check> {
|
||||
if args.len() == 1 {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "list" {
|
||||
if let ExprKind::GeneratorExp { .. } = &args[0].node {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryGeneratorList,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Check `set(generator)` compliance.
|
||||
pub fn unnecessary_generator_set(expr: &Expr, func: &Expr, args: &Vec<Expr>) -> Option<Check> {
|
||||
if args.len() == 1 {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "set" {
|
||||
if let ExprKind::GeneratorExp { .. } = &args[0].node {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryGeneratorList,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Check `dict((x, y) for x, y in iterable)` compliance.
|
||||
pub fn unnecessary_generator_dict(expr: &Expr, func: &Expr, args: &Vec<Expr>) -> Option<Check> {
|
||||
if args.len() == 1 {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "dict" {
|
||||
if let ExprKind::GeneratorExp { elt, .. } = &args[0].node {
|
||||
match &elt.node {
|
||||
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryListComprehensionDict,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Check `set([...])` compliance.
|
||||
pub fn unnecessary_list_comprehension_set(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &Vec<Expr>,
|
||||
) -> Option<Check> {
|
||||
if args.len() == 1 {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "set" {
|
||||
if let ExprKind::ListComp { .. } = &args[0].node {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryListComprehensionSet,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Check `dict([...])` compliance.
|
||||
pub fn unnecessary_list_comprehension_dict(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &Vec<Expr>,
|
||||
) -> Option<Check> {
|
||||
if args.len() == 1 {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "dict" {
|
||||
if let ExprKind::ListComp { elt, .. } = &args[0].node {
|
||||
match &elt.node {
|
||||
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryListComprehensionDict,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Check `set([1, 2])` compliance.
|
||||
pub fn unnecessary_literal_set(expr: &Expr, func: &Expr, args: &Vec<Expr>) -> Option<Check> {
|
||||
if args.len() == 1 {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "set" {
|
||||
match &args[0].node {
|
||||
ExprKind::List { .. } => {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryLiteralSet("list".to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
ExprKind::Tuple { .. } => {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryLiteralSet("tuple".to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Check `dict([(1, 2)])` compliance.
|
||||
pub fn unnecessary_literal_dict(expr: &Expr, func: &Expr, args: &Vec<Expr>) -> Option<Check> {
|
||||
if args.len() == 1 {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "dict" {
|
||||
match &args[0].node {
|
||||
ExprKind::Tuple { elts, .. } => {
|
||||
if let Some(elt) = elts.first() {
|
||||
match &elt.node {
|
||||
// dict((1, 2), ...))
|
||||
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryLiteralDict("tuple".to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
// dict(())
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryLiteralDict("tuple".to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
ExprKind::List { elts, .. } => {
|
||||
if let Some(elt) = elts.first() {
|
||||
match &elt.node {
|
||||
// dict([(1, 2), ...])
|
||||
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryLiteralDict("list".to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
// dict([])
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryLiteralDict("list".to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn unnecessary_collection_call(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &Vec<Expr>,
|
||||
keywords: &Vec<Located<KeywordData>>,
|
||||
) -> Option<Check> {
|
||||
if args.is_empty() {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "list" || id == "tuple" {
|
||||
// list() or tuple()
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryCollectionCall(id.to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
} else if id == "dict" {
|
||||
// dict() or dict(a=1)
|
||||
if keywords.is_empty() || keywords.iter().all(|kw| kw.node.arg.is_some()) {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryCollectionCall(id.to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn unnecessary_subscript_reversal(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
|
||||
if let Some(first_arg) = args.first() {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "set" || id == "sorted" || id == "reversed" {
|
||||
if let ExprKind::Subscript { slice, .. } = &first_arg.node {
|
||||
if let ExprKind::Slice { lower, upper, step } = &slice.node {
|
||||
if lower.is_none() && upper.is_none() {
|
||||
if let Some(step) = step {
|
||||
if let ExprKind::UnaryOp {
|
||||
op: Unaryop::USub,
|
||||
operand,
|
||||
} = &step.node
|
||||
{
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Int(val),
|
||||
..
|
||||
} = &operand.node
|
||||
{
|
||||
if *val == BigInt::from(1) {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessarySubscriptReversal(
|
||||
id.to_string(),
|
||||
),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// flake8-super
|
||||
/// Check that `super()` has no args
|
||||
pub fn check_super_args(
|
||||
@@ -791,11 +1092,16 @@ pub fn check_super_args(
|
||||
|
||||
// flake8-print
|
||||
/// Check whether a function call is a `print` or `pprint` invocation
|
||||
pub fn check_print_call(expr: &Expr, func: &Expr) -> Option<Check> {
|
||||
pub fn check_print_call(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
check_print: bool,
|
||||
check_pprint: bool,
|
||||
) -> Option<Check> {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "print" {
|
||||
if check_print && id == "print" {
|
||||
return Some(Check::new(CheckKind::PrintFound, Range::from_located(expr)));
|
||||
} else if id == "pprint" {
|
||||
} else if check_pprint && id == "pprint" {
|
||||
return Some(Check::new(
|
||||
CheckKind::PPrintFound,
|
||||
Range::from_located(expr),
|
||||
@@ -805,7 +1111,7 @@ pub fn check_print_call(expr: &Expr, func: &Expr) -> Option<Check> {
|
||||
|
||||
if let ExprKind::Attribute { value, attr, .. } = &func.node {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
if id == "pprint" && attr == "pprint" {
|
||||
if check_pprint && id == "pprint" && attr == "pprint" {
|
||||
return Some(Check::new(
|
||||
CheckKind::PPrintFound,
|
||||
Range::from_located(expr),
|
||||
|
||||
74
src/ast/helpers.rs
Normal file
74
src/ast/helpers.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_ast::{Expr, ExprKind, StmtKind};
|
||||
|
||||
use crate::python::typing;
|
||||
|
||||
static DUNDER_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"__[^\s]+__").unwrap());
|
||||
|
||||
pub enum SubscriptKind {
|
||||
AnnotatedSubscript,
|
||||
PEP593AnnotatedSubscript,
|
||||
}
|
||||
|
||||
pub fn match_annotated_subscript(expr: &Expr) -> Option<SubscriptKind> {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { attr, .. } => {
|
||||
if typing::is_annotated_subscript(attr) {
|
||||
Some(SubscriptKind::AnnotatedSubscript)
|
||||
} else if typing::is_pep593_annotated_subscript(attr) {
|
||||
Some(SubscriptKind::PEP593AnnotatedSubscript)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
ExprKind::Name { id, .. } => {
|
||||
if typing::is_annotated_subscript(id) {
|
||||
Some(SubscriptKind::AnnotatedSubscript)
|
||||
} else if typing::is_pep593_annotated_subscript(id) {
|
||||
Some(SubscriptKind::PEP593AnnotatedSubscript)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_assignment_to_a_dunder(node: &StmtKind) -> bool {
|
||||
// Check whether it's an assignment to a dunder, with or without a type annotation.
|
||||
// This is what pycodestyle (as of 2.9.1) does.
|
||||
match node {
|
||||
StmtKind::Assign {
|
||||
targets,
|
||||
value: _,
|
||||
type_comment: _,
|
||||
} => {
|
||||
if targets.len() != 1 {
|
||||
return false;
|
||||
}
|
||||
match &targets[0].node {
|
||||
ExprKind::Name { id, ctx: _ } => DUNDER_REGEX.is_match(id),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
StmtKind::AnnAssign {
|
||||
target,
|
||||
annotation: _,
|
||||
value: _,
|
||||
simple: _,
|
||||
} => match &target.node {
|
||||
ExprKind::Name { id, ctx: _ } => DUNDER_REGEX.is_match(id),
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { attr, .. } => target == attr,
|
||||
ExprKind::Name { id, .. } => target == id,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -74,9 +74,9 @@ pub enum BindingKind {
|
||||
Export(Vec<String>),
|
||||
FutureImportation,
|
||||
StarImportation,
|
||||
Importation(String, BindingContext),
|
||||
FromImportation(String, BindingContext),
|
||||
SubmoduleImportation(String, BindingContext),
|
||||
Importation(String, String, BindingContext),
|
||||
FromImportation(String, String, BindingContext),
|
||||
SubmoduleImportation(String, String, BindingContext),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::ast::helpers::match_name_or_attr;
|
||||
use rustpython_parser::ast::{
|
||||
Alias, Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, Excepthandler,
|
||||
ExcepthandlerKind, Expr, ExprContext, ExprKind, Keyword, MatchCase, Operator, Pattern,
|
||||
@@ -148,7 +149,11 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
|
||||
} => {
|
||||
visitor.visit_annotation(annotation);
|
||||
if let Some(expr) = value {
|
||||
visitor.visit_expr(expr);
|
||||
if match_name_or_attr(annotation, "TypeAlias") {
|
||||
visitor.visit_annotation(expr);
|
||||
} else {
|
||||
visitor.visit_expr(expr);
|
||||
}
|
||||
}
|
||||
visitor.visit_expr(target);
|
||||
}
|
||||
|
||||
599
src/check_ast.rs
599
src/check_ast.rs
@@ -3,14 +3,14 @@ use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
|
||||
use log::error;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_ast::Location;
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
|
||||
KeywordData, Operator, Stmt, StmtKind, Suite,
|
||||
};
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::ast::helpers::{match_name_or_attr, SubscriptKind};
|
||||
use crate::ast::operations::{extract_all_names, SourceCodeLocator};
|
||||
use crate::ast::relocate::relocate_expr;
|
||||
use crate::ast::types::{
|
||||
@@ -18,30 +18,32 @@ use crate::ast::types::{
|
||||
ScopeKind,
|
||||
};
|
||||
use crate::ast::visitor::{walk_excepthandler, Visitor};
|
||||
use crate::ast::{checks, operations, visitor};
|
||||
use crate::ast::{checks, helpers, operations, visitor};
|
||||
use crate::autofix::{fixer, fixes};
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::plugins;
|
||||
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use crate::python::future::ALL_FEATURE_NAMES;
|
||||
use crate::python::typing;
|
||||
use crate::settings::Settings;
|
||||
use crate::settings::{PythonVersion, Settings};
|
||||
|
||||
pub const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
|
||||
static DUNDER_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"__[^\s]+__").unwrap());
|
||||
|
||||
struct Checker<'a> {
|
||||
pub struct Checker<'a> {
|
||||
// Input data.
|
||||
locator: SourceCodeLocator<'a>,
|
||||
settings: &'a Settings,
|
||||
autofix: &'a fixer::Mode,
|
||||
path: &'a Path,
|
||||
// TODO(charlie): Separate immutable from mutable state. (None of these should ever change.)
|
||||
pub(crate) locator: SourceCodeLocator<'a>,
|
||||
pub(crate) settings: &'a Settings,
|
||||
pub(crate) autofix: &'a fixer::Mode,
|
||||
// Computed checks.
|
||||
checks: Vec<Check>,
|
||||
// Edit tracking.
|
||||
// TODO(charlie): Instead of exposing deletions, wrap in a public API.
|
||||
pub(crate) deletions: BTreeSet<usize>,
|
||||
// Retain all scopes and parent nodes, along with a stack of indexes to track which are active
|
||||
// at various points in time.
|
||||
parents: Vec<&'a Stmt>,
|
||||
parent_stack: Vec<usize>,
|
||||
pub(crate) parents: Vec<&'a Stmt>,
|
||||
pub(crate) parent_stack: Vec<usize>,
|
||||
scopes: Vec<Scope>,
|
||||
scope_stack: Vec<usize>,
|
||||
dead_scopes: Vec<usize>,
|
||||
@@ -50,7 +52,7 @@ struct Checker<'a> {
|
||||
deferred_functions: Vec<(&'a Stmt, Vec<usize>, Vec<usize>)>,
|
||||
deferred_lambdas: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
|
||||
deferred_assignments: Vec<usize>,
|
||||
// Derivative state.
|
||||
// Internal, derivative state.
|
||||
in_f_string: Option<Range>,
|
||||
in_annotation: bool,
|
||||
in_literal: bool,
|
||||
@@ -58,8 +60,6 @@ struct Checker<'a> {
|
||||
seen_docstring: bool,
|
||||
futures_allowed: bool,
|
||||
annotations_future_enabled: bool,
|
||||
// Edit tracking.
|
||||
deletions: BTreeSet<usize>,
|
||||
}
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
@@ -97,52 +97,6 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { attr, .. } => target == attr,
|
||||
ExprKind::Name { id, .. } => target == id,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_annotated_subscript(expr: &Expr) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { attr, .. } => typing::is_annotated_subscript(attr),
|
||||
ExprKind::Name { id, .. } => typing::is_annotated_subscript(id),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_assignment_to_a_dunder(node: &StmtKind) -> bool {
|
||||
// Check whether it's an assignment to a dunder, with or without a type annotation.
|
||||
// This is what pycodestyle (as of 2.9.1) does.
|
||||
match node {
|
||||
StmtKind::Assign {
|
||||
targets,
|
||||
value: _,
|
||||
type_comment: _,
|
||||
} => {
|
||||
if targets.len() != 1 {
|
||||
return false;
|
||||
}
|
||||
match &targets[0].node {
|
||||
ExprKind::Name { id, ctx: _ } => DUNDER_REGEX.is_match(id),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
StmtKind::AnnAssign {
|
||||
target,
|
||||
annotation: _,
|
||||
value: _,
|
||||
simple: _,
|
||||
} => match &target.node {
|
||||
ExprKind::Name { id, ctx: _ } => DUNDER_REGEX.is_match(id),
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Visitor<'b> for Checker<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
@@ -203,7 +157,7 @@ where
|
||||
self.futures_allowed = false;
|
||||
|
||||
if !self.seen_non_import
|
||||
&& !is_assignment_to_a_dunder(node)
|
||||
&& !helpers::is_assignment_to_a_dunder(node)
|
||||
&& !operations::in_nested_block(&self.parent_stack, &self.parents)
|
||||
{
|
||||
self.seen_non_import = true;
|
||||
@@ -216,8 +170,7 @@ where
|
||||
StmtKind::Global { names } | StmtKind::Nonlocal { names } => {
|
||||
let global_scope_id = self.scopes[GLOBAL_SCOPE_INDEX].id;
|
||||
|
||||
let current_scope =
|
||||
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
let current_scope = self.current_scope();
|
||||
let current_scope_id = current_scope.id;
|
||||
if current_scope_id != global_scope_id {
|
||||
for name in names {
|
||||
@@ -365,20 +318,8 @@ where
|
||||
decorator_list,
|
||||
..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::R001) {
|
||||
let scope =
|
||||
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
if let Some(check) = checks::check_useless_object_inheritance(
|
||||
stmt,
|
||||
name,
|
||||
bases,
|
||||
keywords,
|
||||
scope,
|
||||
&mut self.locator,
|
||||
self.autofix,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::U004) {
|
||||
plugins::useless_object_inheritance(self, stmt, name, bases, keywords);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::E742) {
|
||||
@@ -423,13 +364,16 @@ where
|
||||
|
||||
for alias in names {
|
||||
if alias.node.name.contains('.') && alias.node.asname.is_none() {
|
||||
// TODO(charlie): Multiple submodule imports with the same parent module
|
||||
// will be merged into a single binding.
|
||||
// Given `import foo.bar`, `name` would be "foo", and `full_name` would be
|
||||
// "foo.bar".
|
||||
let name = alias.node.name.split('.').next().unwrap();
|
||||
let full_name = &alias.node.name;
|
||||
self.add_binding(
|
||||
alias.node.name.split('.').next().unwrap().to_string(),
|
||||
name.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::SubmoduleImportation(
|
||||
alias.node.name.to_string(),
|
||||
name.to_string(),
|
||||
full_name.to_string(),
|
||||
self.binding_context(),
|
||||
),
|
||||
used: None,
|
||||
@@ -441,19 +385,17 @@ where
|
||||
self.check_builtin_shadowing(asname, Range::from_located(stmt), false);
|
||||
}
|
||||
|
||||
// Given `import foo`, `name` and `full_name` would both be `foo`.
|
||||
// Given `import foo as bar`, `name` would be `bar` and `full_name` would
|
||||
// be `foo`.
|
||||
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
|
||||
let full_name = &alias.node.name;
|
||||
self.add_binding(
|
||||
alias
|
||||
.node
|
||||
.asname
|
||||
.clone()
|
||||
.unwrap_or_else(|| alias.node.name.clone()),
|
||||
name.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::Importation(
|
||||
alias
|
||||
.node
|
||||
.asname
|
||||
.clone()
|
||||
.unwrap_or_else(|| alias.node.name.clone()),
|
||||
name.to_string(),
|
||||
full_name.to_string(),
|
||||
self.binding_context(),
|
||||
),
|
||||
used: None,
|
||||
@@ -482,14 +424,10 @@ where
|
||||
}
|
||||
|
||||
for alias in names {
|
||||
let name = alias
|
||||
.node
|
||||
.asname
|
||||
.clone()
|
||||
.unwrap_or_else(|| alias.node.name.clone());
|
||||
if let Some("__future__") = module.as_deref() {
|
||||
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
|
||||
self.add_binding(
|
||||
name,
|
||||
name.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::FutureImportation,
|
||||
used: Some((
|
||||
@@ -568,18 +506,26 @@ where
|
||||
self.check_builtin_shadowing(asname, Range::from_located(stmt), false);
|
||||
}
|
||||
|
||||
let binding = Binding {
|
||||
kind: BindingKind::FromImportation(
|
||||
match module {
|
||||
None => name.clone(),
|
||||
Some(parent) => format!("{}.{}", parent, name),
|
||||
},
|
||||
self.binding_context(),
|
||||
),
|
||||
used: None,
|
||||
range: Range::from_located(stmt),
|
||||
// Given `from foo import bar`, `name` would be "bar" and `full_name` would
|
||||
// be "foo.bar". Given `from foo import bar as baz`, `name` would be "baz"
|
||||
// and `full_name` would be "foo.bar".
|
||||
let name = alias.node.asname.as_ref().unwrap_or(&alias.node.name);
|
||||
let full_name = match module {
|
||||
None => alias.node.name.to_string(),
|
||||
Some(parent) => format!("{}.{}", parent, alias.node.name),
|
||||
};
|
||||
self.add_binding(name, binding)
|
||||
self.add_binding(
|
||||
name.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::FromImportation(
|
||||
name.to_string(),
|
||||
full_name,
|
||||
self.binding_context(),
|
||||
),
|
||||
used: None,
|
||||
range: Range::from_located(stmt),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -597,25 +543,12 @@ where
|
||||
}
|
||||
StmtKind::If { test, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::F634) {
|
||||
if let Some(check) =
|
||||
checks::check_if_tuple(test, self.locate_check(Range::from_located(stmt)))
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
plugins::if_tuple(self, stmt, test);
|
||||
}
|
||||
}
|
||||
StmtKind::Assert { test, .. } => {
|
||||
if self
|
||||
.settings
|
||||
.enabled
|
||||
.contains(CheckKind::AssertTuple.code())
|
||||
{
|
||||
if let Some(check) = checks::check_assert_tuple(
|
||||
test,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::F631) {
|
||||
plugins::assert_tuple(self, stmt, test);
|
||||
}
|
||||
}
|
||||
StmtKind::Try { handlers, .. } => {
|
||||
@@ -635,35 +568,7 @@ where
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::U001) {
|
||||
if let Some(mut check) = checks::check_useless_metaclass_type(
|
||||
targets,
|
||||
value,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
) {
|
||||
if matches!(self.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
let context = self.binding_context();
|
||||
let deleted: Vec<&Stmt> = self
|
||||
.deletions
|
||||
.iter()
|
||||
.map(|index| self.parents[*index])
|
||||
.collect();
|
||||
|
||||
match fixes::remove_stmt(
|
||||
self.parents[context.defined_by],
|
||||
context.defined_in.map(|index| self.parents[index]),
|
||||
&deleted,
|
||||
) {
|
||||
Ok(fix) => {
|
||||
if fix.content.is_empty() || fix.content == "pass" {
|
||||
self.deletions.insert(context.defined_by);
|
||||
}
|
||||
check.amend(fix)
|
||||
}
|
||||
Err(e) => error!("Failed to fix unused imports: {}", e),
|
||||
}
|
||||
}
|
||||
self.checks.push(check);
|
||||
}
|
||||
plugins::useless_metaclass_type(self, stmt, value, targets);
|
||||
}
|
||||
}
|
||||
StmtKind::AnnAssign { value, .. } => {
|
||||
@@ -739,7 +644,14 @@ where
|
||||
|
||||
// Pre-visit.
|
||||
match &expr.node {
|
||||
ExprKind::Subscript { value, .. } => {
|
||||
ExprKind::Subscript { value, slice, .. } => {
|
||||
// Ex) typing.List[...]
|
||||
if self.settings.enabled.contains(&CheckCode::U007)
|
||||
&& self.settings.target_version >= PythonVersion::Py39
|
||||
{
|
||||
plugins::use_pep604_annotation(self, expr, value, slice);
|
||||
}
|
||||
|
||||
if match_name_or_attr(value, "Literal") {
|
||||
self.in_literal = true;
|
||||
}
|
||||
@@ -761,7 +673,16 @@ where
|
||||
}
|
||||
}
|
||||
ExprKind::Name { id, ctx } => match ctx {
|
||||
ExprContext::Load => self.handle_node_load(expr),
|
||||
ExprContext::Load => {
|
||||
// Ex) List[...]
|
||||
if self.settings.enabled.contains(&CheckCode::U006)
|
||||
&& self.settings.target_version >= PythonVersion::Py39
|
||||
{
|
||||
plugins::use_pep585_annotation(self, expr, id);
|
||||
}
|
||||
|
||||
self.handle_node_load(expr);
|
||||
}
|
||||
ExprContext::Store => {
|
||||
if self.settings.enabled.contains(&CheckCode::E741) {
|
||||
if let Some(check) = checks::check_ambiguous_variable_name(
|
||||
@@ -774,81 +695,114 @@ where
|
||||
|
||||
self.check_builtin_shadowing(id, Range::from_located(expr), true);
|
||||
|
||||
let parent =
|
||||
self.parents[*(self.parent_stack.last().expect("No parent found."))];
|
||||
self.handle_node_store(expr, parent);
|
||||
self.handle_node_store(expr, self.current_parent());
|
||||
}
|
||||
ExprContext::Del => self.handle_node_delete(expr),
|
||||
},
|
||||
ExprKind::Call { func, args, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::R002) {
|
||||
if let Some(check) = checks::check_assert_equals(func, self.autofix) {
|
||||
self.checks.push(check)
|
||||
ExprKind::Attribute { value, attr, .. } => {
|
||||
// Ex) typing.List[...]
|
||||
if self.settings.enabled.contains(&CheckCode::U006)
|
||||
&& self.settings.target_version >= PythonVersion::Py39
|
||||
{
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
if id == "typing" {
|
||||
plugins::use_pep585_annotation(self, expr, attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Call {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::U005) {
|
||||
plugins::assert_equals(self, func);
|
||||
}
|
||||
|
||||
// flake8-super
|
||||
if self.settings.enabled.contains(&CheckCode::SPR001) {
|
||||
// Only bother going through the super check at all if we're in a `super` call.
|
||||
// (We check this in `check_super_args` too, so this is just an optimization.)
|
||||
if checks::is_super_call_with_arguments(func, args) {
|
||||
let scope = &mut self.scopes
|
||||
[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
let parents: Vec<&Stmt> = self
|
||||
.parent_stack
|
||||
.iter()
|
||||
.map(|index| self.parents[*index])
|
||||
.collect();
|
||||
if let Some(mut check) =
|
||||
checks::check_super_args(scope, &parents, expr, func, args)
|
||||
{
|
||||
if matches!(self.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
if let Some(fix) =
|
||||
fixes::remove_super_arguments(&mut self.locator, expr)
|
||||
{
|
||||
check.amend(fix);
|
||||
}
|
||||
}
|
||||
self.checks.push(check)
|
||||
}
|
||||
}
|
||||
plugins::super_call_with_parameters(self, expr, func, args);
|
||||
}
|
||||
|
||||
// flake8-print
|
||||
if self.settings.enabled.contains(&CheckCode::T201)
|
||||
|| self.settings.enabled.contains(&CheckCode::T203)
|
||||
{
|
||||
if let Some(mut check) = checks::check_print_call(expr, func) {
|
||||
if matches!(self.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
let context = self.binding_context();
|
||||
if matches!(
|
||||
self.parents[context.defined_by].node,
|
||||
StmtKind::Expr { .. }
|
||||
) {
|
||||
let deleted: Vec<&Stmt> = self
|
||||
.deletions
|
||||
.iter()
|
||||
.map(|index| self.parents[*index])
|
||||
.collect();
|
||||
plugins::print_call(self, expr, func);
|
||||
}
|
||||
|
||||
match fixes::remove_stmt(
|
||||
self.parents[context.defined_by],
|
||||
context.defined_in.map(|index| self.parents[index]),
|
||||
&deleted,
|
||||
) {
|
||||
Ok(fix) => {
|
||||
if fix.content.is_empty() || fix.content == "pass" {
|
||||
self.deletions.insert(context.defined_by);
|
||||
}
|
||||
check.amend(fix)
|
||||
}
|
||||
Err(e) => error!("Failed to fix unused imports: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
// flake8-comprehensions
|
||||
if self.settings.enabled.contains(&CheckCode::C400) {
|
||||
if let Some(check) = checks::unnecessary_generator_list(expr, func, args) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
self.checks.push(check)
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::C401) {
|
||||
if let Some(check) = checks::unnecessary_generator_set(expr, func, args) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C402) {
|
||||
if let Some(check) = checks::unnecessary_generator_dict(expr, func, args) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C403) {
|
||||
if let Some(check) =
|
||||
checks::unnecessary_list_comprehension_set(expr, func, args)
|
||||
{
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C404) {
|
||||
if let Some(check) =
|
||||
checks::unnecessary_list_comprehension_dict(expr, func, args)
|
||||
{
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C405) {
|
||||
if let Some(check) = checks::unnecessary_literal_set(expr, func, args) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C406) {
|
||||
if let Some(check) = checks::unnecessary_literal_dict(expr, func, args) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C408) {
|
||||
if let Some(check) =
|
||||
checks::unnecessary_collection_call(expr, func, args, keywords)
|
||||
{
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C415) {
|
||||
if let Some(check) = checks::unnecessary_subscript_reversal(expr, func, args) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
// pyupgrade
|
||||
if self.settings.enabled.contains(&CheckCode::U002)
|
||||
&& self.settings.target_version >= PythonVersion::Py310
|
||||
{
|
||||
plugins::unnecessary_abspath(self, expr, func, args);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::U003) {
|
||||
plugins::type_of_primitive(self, expr, func, args);
|
||||
}
|
||||
|
||||
if let ExprKind::Name { id, ctx } = &func.node {
|
||||
@@ -879,8 +833,7 @@ where
|
||||
}
|
||||
}
|
||||
ExprKind::Yield { .. } | ExprKind::YieldFrom { .. } | ExprKind::Await { .. } => {
|
||||
let scope =
|
||||
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
let scope = self.current_scope();
|
||||
if self
|
||||
.settings
|
||||
.enabled
|
||||
@@ -916,22 +869,7 @@ where
|
||||
..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::F633) {
|
||||
if let ExprKind::Name { id, .. } = &left.node {
|
||||
if id == "print" {
|
||||
let scope = &self.scopes
|
||||
[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
if let Some(Binding {
|
||||
kind: BindingKind::Builtin,
|
||||
..
|
||||
}) = scope.values.get("print")
|
||||
{
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::InvalidPrintSyntax,
|
||||
Range::from_located(left),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
plugins::invalid_print_syntax(self, left);
|
||||
}
|
||||
}
|
||||
ExprKind::UnaryOp { op, operand } => {
|
||||
@@ -985,9 +923,11 @@ where
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(value),
|
||||
..
|
||||
} if self.in_annotation && !self.in_literal => {
|
||||
self.deferred_string_annotations
|
||||
.push((Range::from_located(expr), value));
|
||||
} => {
|
||||
if self.in_annotation && !self.in_literal {
|
||||
self.deferred_string_annotations
|
||||
.push((Range::from_located(expr), value));
|
||||
}
|
||||
}
|
||||
ExprKind::Lambda { args, .. } => {
|
||||
// Visit the arguments, but avoid the body, which will be deferred.
|
||||
@@ -1138,12 +1078,35 @@ where
|
||||
}
|
||||
}
|
||||
ExprKind::Subscript { value, slice, ctx } => {
|
||||
if is_annotated_subscript(value) {
|
||||
self.visit_expr(value);
|
||||
self.visit_annotation(slice);
|
||||
self.visit_expr_context(ctx);
|
||||
} else {
|
||||
visitor::walk_expr(self, expr);
|
||||
match helpers::match_annotated_subscript(value) {
|
||||
Some(subscript) => match subscript {
|
||||
// Ex) Optional[int]
|
||||
SubscriptKind::AnnotatedSubscript => {
|
||||
self.visit_expr(value);
|
||||
self.visit_annotation(slice);
|
||||
self.visit_expr_context(ctx);
|
||||
}
|
||||
// Ex) Annotated[int, "Hello, world!"]
|
||||
SubscriptKind::PEP593AnnotatedSubscript => {
|
||||
// First argument is a type (including forward references); the rest are
|
||||
// arbitrary Python objects.
|
||||
self.visit_expr(value);
|
||||
if let ExprKind::Tuple { elts, ctx } = &slice.node {
|
||||
if let Some(expr) = elts.first() {
|
||||
self.visit_expr(expr);
|
||||
self.in_annotation = false;
|
||||
for expr in elts.iter().skip(1) {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
self.in_annotation = true;
|
||||
self.visit_expr_context(ctx);
|
||||
}
|
||||
} else {
|
||||
error!("Found non-ExprKind::Tuple argument to PEP 593 Annotation.")
|
||||
}
|
||||
}
|
||||
},
|
||||
None => visitor::walk_expr(self, expr),
|
||||
}
|
||||
}
|
||||
_ => visitor::walk_expr(self, expr),
|
||||
@@ -1191,11 +1154,7 @@ where
|
||||
false,
|
||||
);
|
||||
|
||||
let scope = &self.scopes
|
||||
[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
if scope.values.contains_key(name) {
|
||||
let parent = self.parents
|
||||
[*(self.parent_stack.last().expect("No parent found."))];
|
||||
if self.current_scope().values.contains_key(name) {
|
||||
self.handle_node_store(
|
||||
&Expr::new(
|
||||
excepthandler.location,
|
||||
@@ -1205,15 +1164,11 @@ where
|
||||
ctx: ExprContext::Store,
|
||||
},
|
||||
),
|
||||
parent,
|
||||
self.current_parent(),
|
||||
);
|
||||
}
|
||||
|
||||
let parent =
|
||||
self.parents[*(self.parent_stack.last().expect("No parent found."))];
|
||||
let scope = &self.scopes
|
||||
[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
let definition = scope.values.get(name).cloned();
|
||||
let definition = self.current_scope().values.get(name).cloned();
|
||||
self.handle_node_store(
|
||||
&Expr::new(
|
||||
excepthandler.location,
|
||||
@@ -1223,7 +1178,7 @@ where
|
||||
ctx: ExprContext::Store,
|
||||
},
|
||||
),
|
||||
parent,
|
||||
self.current_parent(),
|
||||
);
|
||||
|
||||
walk_excepthandler(self, excepthandler);
|
||||
@@ -1305,7 +1260,52 @@ impl CheckLocator for Checker<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn try_mark_used(scope: &mut Scope, scope_id: usize, id: &str, expr: &Expr) -> bool {
|
||||
let alias = if let Some(binding) = scope.values.get_mut(id) {
|
||||
// Mark the binding as used.
|
||||
binding.used = Some((scope_id, Range::from_located(expr)));
|
||||
|
||||
// If the name of the sub-importation is the same as an alias of another importation and the
|
||||
// alias is used, that sub-importation should be marked as used too.
|
||||
//
|
||||
// This handles code like:
|
||||
// import pyarrow as pa
|
||||
// import pyarrow.csv
|
||||
// print(pa.csv.read_csv("test.csv"))
|
||||
if let BindingKind::Importation(name, full_name, _)
|
||||
| BindingKind::FromImportation(name, full_name, _)
|
||||
| BindingKind::SubmoduleImportation(name, full_name, _) = &binding.kind
|
||||
{
|
||||
let has_alias = full_name
|
||||
.split('.')
|
||||
.last()
|
||||
.map(|segment| segment != name)
|
||||
.unwrap_or_default();
|
||||
if has_alias {
|
||||
// Clone the alias. (We'll mutate it below.)
|
||||
full_name.to_string()
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Mark the sub-importation as used.
|
||||
if let Some(binding) = scope.values.get_mut(&alias) {
|
||||
binding.used = Some((scope_id, Range::from_located(expr)));
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
pub fn add_check(&mut self, check: Check) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
|
||||
fn push_parent(&mut self, parent: &'a Stmt) {
|
||||
self.parent_stack.push(self.parents.len());
|
||||
self.parents.push(parent);
|
||||
@@ -1355,7 +1355,15 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn binding_context(&self) -> BindingContext {
|
||||
pub fn current_scope(&self) -> &Scope {
|
||||
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))]
|
||||
}
|
||||
|
||||
pub fn current_parent(&self) -> &'a Stmt {
|
||||
self.parents[*(self.parent_stack.last().expect("No parent found."))]
|
||||
}
|
||||
|
||||
pub fn binding_context(&self) -> BindingContext {
|
||||
let mut rev = self.parent_stack.iter().rev().fuse();
|
||||
let defined_by = *rev.next().expect("Expected to bind within a statement.");
|
||||
let defined_in = rev.next().cloned();
|
||||
@@ -1376,7 +1384,11 @@ impl<'a> Checker<'a> {
|
||||
&& matches!(binding.kind, BindingKind::LoopVar)
|
||||
&& matches!(
|
||||
existing.kind,
|
||||
BindingKind::Importation(_, _) | BindingKind::FromImportation(_, _)
|
||||
BindingKind::Importation(_, _, _)
|
||||
| BindingKind::FromImportation(_, _, _)
|
||||
| BindingKind::SubmoduleImportation(_, _, _)
|
||||
| BindingKind::StarImportation
|
||||
| BindingKind::FutureImportation
|
||||
)
|
||||
{
|
||||
self.checks.push(Check::new(
|
||||
@@ -1415,8 +1427,8 @@ impl<'a> Checker<'a> {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if let Some(binding) = scope.values.get_mut(id) {
|
||||
binding.used = Some((scope_id, Range::from_located(expr)));
|
||||
|
||||
if try_mark_used(scope, scope_id, id, expr) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1586,14 +1598,37 @@ impl<'a> Checker<'a> {
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
while let Some((location, expression)) = self.deferred_string_annotations.pop() {
|
||||
while let Some((range, expression)) = self.deferred_string_annotations.pop() {
|
||||
// HACK(charlie): We need to modify `range` such that it represents the range of the
|
||||
// expression _within_ the string annotation (as opposed to the range of the string
|
||||
// annotation itself). RustPython seems to return an off-by-one start column for every
|
||||
// string value, so we check for double quotes (which are really triple quotes).
|
||||
let contents = self.locator.slice_source_code_at(&range.location);
|
||||
let range = if contents.starts_with("\"\"") || contents.starts_with("\'\'") {
|
||||
Range {
|
||||
location: Location::new(range.location.row(), range.location.column() + 2),
|
||||
end_location: Location::new(
|
||||
range.end_location.row(),
|
||||
range.end_location.column() - 2,
|
||||
),
|
||||
}
|
||||
} else {
|
||||
Range {
|
||||
location: Location::new(range.location.row(), range.location.column()),
|
||||
end_location: Location::new(
|
||||
range.end_location.row(),
|
||||
range.end_location.column() - 1,
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
if let Ok(mut expr) = parser::parse_expression(expression, "<filename>") {
|
||||
relocate_expr(&mut expr, location);
|
||||
relocate_expr(&mut expr, range);
|
||||
allocator.push(expr);
|
||||
} else if self.settings.enabled.contains(&CheckCode::F722) {
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::ForwardAnnotationSyntaxError(expression.to_string()),
|
||||
self.locate_check(location),
|
||||
self.locate_check(range),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1728,25 +1763,25 @@ impl<'a> Checker<'a> {
|
||||
|
||||
if !used {
|
||||
match &binding.kind {
|
||||
BindingKind::FromImportation(full_name, context) => {
|
||||
BindingKind::FromImportation(_, full_name, context) => {
|
||||
let full_names = unused
|
||||
.entry((
|
||||
ImportKind::ImportFrom,
|
||||
context.defined_by,
|
||||
context.defined_in,
|
||||
))
|
||||
.or_insert(vec![]);
|
||||
.or_default();
|
||||
full_names.push(full_name);
|
||||
}
|
||||
BindingKind::Importation(full_name, context)
|
||||
| BindingKind::SubmoduleImportation(full_name, context) => {
|
||||
BindingKind::Importation(_, full_name, context)
|
||||
| BindingKind::SubmoduleImportation(_, full_name, context) => {
|
||||
let full_names = unused
|
||||
.entry((
|
||||
ImportKind::Import,
|
||||
context.defined_by,
|
||||
context.defined_in,
|
||||
))
|
||||
.or_insert(vec![]);
|
||||
.or_default();
|
||||
full_names.push(full_name);
|
||||
}
|
||||
_ => {}
|
||||
@@ -1758,12 +1793,8 @@ impl<'a> Checker<'a> {
|
||||
let child = self.parents[defined_by];
|
||||
let parent = defined_in.map(|defined_in| self.parents[defined_in]);
|
||||
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedImport(full_names.join(", ")),
|
||||
self.locate_check(Range::from_located(child)),
|
||||
);
|
||||
|
||||
if matches!(self.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
let fix = if matches!(self.autofix, fixer::Mode::Generate | fixer::Mode::Apply)
|
||||
{
|
||||
let deleted: Vec<&Stmt> = self
|
||||
.deletions
|
||||
.iter()
|
||||
@@ -1776,14 +1807,22 @@ impl<'a> Checker<'a> {
|
||||
};
|
||||
|
||||
match removal_fn(&mut self.locator, &full_names, child, parent, &deleted) {
|
||||
Ok(fix) => {
|
||||
if fix.content.is_empty() || fix.content == "pass" {
|
||||
self.deletions.insert(defined_by);
|
||||
}
|
||||
check.amend(fix)
|
||||
Ok(fix) => Some(fix),
|
||||
Err(e) => {
|
||||
error!("Failed to fix unused imports: {}", e);
|
||||
None
|
||||
}
|
||||
Err(e) => error!("Failed to fix unused imports: {}", e),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedImport(full_names.into_iter().map(String::from).collect()),
|
||||
self.locate_check(Range::from_located(child)),
|
||||
);
|
||||
if let Some(fix) = fix {
|
||||
check.amend(fix);
|
||||
}
|
||||
|
||||
self.checks.push(check);
|
||||
@@ -1793,7 +1832,7 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
fn check_builtin_shadowing(&mut self, name: &str, location: Range, is_attribute: bool) {
|
||||
let scope = &self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
let scope = self.current_scope();
|
||||
|
||||
// flake8-builtins
|
||||
if is_attribute && matches!(scope.kind, ScopeKind::Class) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::fixer;
|
||||
use crate::checks::{Check, CheckCode, CheckKind, Fix};
|
||||
use crate::noqa;
|
||||
@@ -66,12 +66,12 @@ pub fn check_lines(
|
||||
|
||||
match noqa {
|
||||
(Directive::All(_, _), matches) => {
|
||||
matches.push(check.kind.code().as_str());
|
||||
matches.push(check.kind.code().as_ref());
|
||||
ignored.push(index)
|
||||
}
|
||||
(Directive::Codes(_, _, codes), matches) => {
|
||||
if codes.contains(&check.kind.code().as_str()) {
|
||||
matches.push(check.kind.code().as_str());
|
||||
if codes.contains(&check.kind.code().as_ref()) {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
ignored.push(index);
|
||||
}
|
||||
}
|
||||
@@ -98,11 +98,11 @@ pub fn check_lines(
|
||||
|
||||
match noqa {
|
||||
(Directive::All(_, _), matches) => {
|
||||
matches.push(check.kind.code().as_str());
|
||||
matches.push(check.kind.code().as_ref());
|
||||
}
|
||||
(Directive::Codes(_, _, codes), matches) => {
|
||||
if codes.contains(&check.kind.code().as_str()) {
|
||||
matches.push(check.kind.code().as_str());
|
||||
if codes.contains(&check.kind.code().as_ref()) {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
} else {
|
||||
line_checks.push(check);
|
||||
}
|
||||
@@ -113,6 +113,45 @@ pub fn check_lines(
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce newlines at end of files.
|
||||
if settings.enabled.contains(&CheckCode::W292) && !contents.ends_with('\n') {
|
||||
// Note: if `lines.last()` is `None`, then `contents` is empty (and so we don't want to
|
||||
// raise W292 anyway).
|
||||
if let Some(line) = lines.last() {
|
||||
let lineno = lines.len() - 1;
|
||||
let noqa_lineno = noqa_line_for
|
||||
.get(lineno)
|
||||
.map(|lineno| lineno - 1)
|
||||
.unwrap_or(lineno);
|
||||
|
||||
let noqa = noqa_directives
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(|| (noqa::extract_noqa_directive(lines[noqa_lineno]), vec![]));
|
||||
|
||||
let check = Check::new(
|
||||
CheckKind::NoNewLineAtEndOfFile,
|
||||
Range {
|
||||
location: Location::new(lines.len(), line.len() + 1),
|
||||
end_location: Location::new(lines.len(), line.len() + 1),
|
||||
},
|
||||
);
|
||||
|
||||
match noqa {
|
||||
(Directive::All(_, _), matches) => {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
}
|
||||
(Directive::Codes(_, _, codes), matches) => {
|
||||
if codes.contains(&check.kind.code().as_ref()) {
|
||||
matches.push(check.kind.code().as_ref());
|
||||
} else {
|
||||
line_checks.push(check);
|
||||
}
|
||||
}
|
||||
(Directive::None, _) => line_checks.push(check),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce that the noqa directive was actually used.
|
||||
if enforce_noqa {
|
||||
for (row, (directive, matches)) in noqa_directives {
|
||||
|
||||
597
src/checks.rs
597
src/checks.rs
@@ -1,13 +1,13 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
use rustpython_parser::ast::Location;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::{AsRefStr, EnumIter, EnumString};
|
||||
|
||||
pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
|
||||
// pycodestyle
|
||||
use crate::ast::checks::Primitive;
|
||||
use crate::ast::types::Range;
|
||||
|
||||
pub const DEFAULT_CHECK_CODES: [CheckCode; 43] = [
|
||||
// pycodestyle errors
|
||||
CheckCode::E402,
|
||||
CheckCode::E501,
|
||||
CheckCode::E711,
|
||||
@@ -22,6 +22,8 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
|
||||
CheckCode::E743,
|
||||
CheckCode::E902,
|
||||
CheckCode::E999,
|
||||
// pycodestyle warnings
|
||||
CheckCode::W292,
|
||||
// pyflakes
|
||||
CheckCode::F401,
|
||||
CheckCode::F402,
|
||||
@@ -53,72 +55,22 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
|
||||
CheckCode::F901,
|
||||
];
|
||||
|
||||
pub const ALL_CHECK_CODES: [CheckCode; 52] = [
|
||||
// pycodestyle
|
||||
CheckCode::E402,
|
||||
CheckCode::E501,
|
||||
CheckCode::E711,
|
||||
CheckCode::E712,
|
||||
CheckCode::E713,
|
||||
CheckCode::E714,
|
||||
CheckCode::E721,
|
||||
CheckCode::E722,
|
||||
CheckCode::E731,
|
||||
CheckCode::E741,
|
||||
CheckCode::E742,
|
||||
CheckCode::E743,
|
||||
CheckCode::E902,
|
||||
CheckCode::E999,
|
||||
// pyflakes
|
||||
CheckCode::F401,
|
||||
CheckCode::F402,
|
||||
CheckCode::F403,
|
||||
CheckCode::F404,
|
||||
CheckCode::F405,
|
||||
CheckCode::F406,
|
||||
CheckCode::F407,
|
||||
CheckCode::F541,
|
||||
CheckCode::F601,
|
||||
CheckCode::F602,
|
||||
CheckCode::F621,
|
||||
CheckCode::F622,
|
||||
CheckCode::F631,
|
||||
CheckCode::F632,
|
||||
CheckCode::F633,
|
||||
CheckCode::F634,
|
||||
CheckCode::F701,
|
||||
CheckCode::F702,
|
||||
CheckCode::F704,
|
||||
CheckCode::F706,
|
||||
CheckCode::F707,
|
||||
CheckCode::F722,
|
||||
CheckCode::F821,
|
||||
CheckCode::F822,
|
||||
CheckCode::F823,
|
||||
CheckCode::F831,
|
||||
CheckCode::F841,
|
||||
CheckCode::F901,
|
||||
// flake8-builtins
|
||||
CheckCode::A001,
|
||||
CheckCode::A002,
|
||||
CheckCode::A003,
|
||||
// flake8-super
|
||||
CheckCode::SPR001,
|
||||
// flake8-print
|
||||
CheckCode::T201,
|
||||
CheckCode::T203,
|
||||
// pyupgrade
|
||||
CheckCode::U001,
|
||||
// Refactor
|
||||
CheckCode::R001,
|
||||
CheckCode::R002,
|
||||
// Meta
|
||||
CheckCode::M001,
|
||||
];
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Hash, PartialOrd, Ord)]
|
||||
#[derive(
|
||||
AsRefStr,
|
||||
EnumIter,
|
||||
EnumString,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
pub enum CheckCode {
|
||||
// pycodestyle
|
||||
// pycodestyle errors
|
||||
E402,
|
||||
E501,
|
||||
E711,
|
||||
@@ -133,6 +85,8 @@ pub enum CheckCode {
|
||||
E743,
|
||||
E902,
|
||||
E999,
|
||||
// pycodestyle warnings
|
||||
W292,
|
||||
// pyflakes
|
||||
F401,
|
||||
F402,
|
||||
@@ -166,6 +120,16 @@ pub enum CheckCode {
|
||||
A001,
|
||||
A002,
|
||||
A003,
|
||||
// flake8-comprehensions
|
||||
C400,
|
||||
C401,
|
||||
C402,
|
||||
C403,
|
||||
C404,
|
||||
C405,
|
||||
C406,
|
||||
C408,
|
||||
C415,
|
||||
// flake8-super
|
||||
SPR001,
|
||||
// flake8-print
|
||||
@@ -173,146 +137,108 @@ pub enum CheckCode {
|
||||
T203,
|
||||
// pyupgrade
|
||||
U001,
|
||||
// Refactor
|
||||
R001,
|
||||
R002,
|
||||
U002,
|
||||
U003,
|
||||
U004,
|
||||
U005,
|
||||
U006,
|
||||
U007,
|
||||
// Meta
|
||||
M001,
|
||||
}
|
||||
|
||||
impl FromStr for CheckCode {
|
||||
type Err = anyhow::Error;
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub enum LintSource {
|
||||
AST,
|
||||
Lines,
|
||||
FileSystem,
|
||||
}
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
match s {
|
||||
// pycodestyle
|
||||
"E402" => Ok(CheckCode::E402),
|
||||
"E501" => Ok(CheckCode::E501),
|
||||
"E711" => Ok(CheckCode::E711),
|
||||
"E712" => Ok(CheckCode::E712),
|
||||
"E713" => Ok(CheckCode::E713),
|
||||
"E714" => Ok(CheckCode::E714),
|
||||
"E721" => Ok(CheckCode::E721),
|
||||
"E722" => Ok(CheckCode::E722),
|
||||
"E731" => Ok(CheckCode::E731),
|
||||
"E741" => Ok(CheckCode::E741),
|
||||
"E742" => Ok(CheckCode::E742),
|
||||
"E743" => Ok(CheckCode::E743),
|
||||
"E902" => Ok(CheckCode::E902),
|
||||
"E999" => Ok(CheckCode::E999),
|
||||
// pyflakes
|
||||
"F401" => Ok(CheckCode::F401),
|
||||
"F402" => Ok(CheckCode::F402),
|
||||
"F403" => Ok(CheckCode::F403),
|
||||
"F404" => Ok(CheckCode::F404),
|
||||
"F405" => Ok(CheckCode::F405),
|
||||
"F406" => Ok(CheckCode::F406),
|
||||
"F407" => Ok(CheckCode::F407),
|
||||
"F541" => Ok(CheckCode::F541),
|
||||
"F601" => Ok(CheckCode::F601),
|
||||
"F602" => Ok(CheckCode::F602),
|
||||
"F621" => Ok(CheckCode::F621),
|
||||
"F622" => Ok(CheckCode::F622),
|
||||
"F631" => Ok(CheckCode::F631),
|
||||
"F632" => Ok(CheckCode::F632),
|
||||
"F633" => Ok(CheckCode::F633),
|
||||
"F634" => Ok(CheckCode::F634),
|
||||
"F701" => Ok(CheckCode::F701),
|
||||
"F702" => Ok(CheckCode::F702),
|
||||
"F704" => Ok(CheckCode::F704),
|
||||
"F706" => Ok(CheckCode::F706),
|
||||
"F707" => Ok(CheckCode::F707),
|
||||
"F722" => Ok(CheckCode::F722),
|
||||
"F821" => Ok(CheckCode::F821),
|
||||
"F822" => Ok(CheckCode::F822),
|
||||
"F823" => Ok(CheckCode::F823),
|
||||
"F831" => Ok(CheckCode::F831),
|
||||
"F841" => Ok(CheckCode::F841),
|
||||
"F901" => Ok(CheckCode::F901),
|
||||
// flake8-builtins
|
||||
"A001" => Ok(CheckCode::A001),
|
||||
"A002" => Ok(CheckCode::A002),
|
||||
"A003" => Ok(CheckCode::A003),
|
||||
// flake8-super
|
||||
"SPR001" => Ok(CheckCode::SPR001),
|
||||
// pyupgrade
|
||||
"U001" => Ok(CheckCode::U001),
|
||||
// Refactor
|
||||
"R001" => Ok(CheckCode::R001),
|
||||
"R002" => Ok(CheckCode::R002),
|
||||
// Meta
|
||||
"M001" => Ok(CheckCode::M001),
|
||||
_ => Err(anyhow::anyhow!("Unknown check code: {s}")),
|
||||
}
|
||||
}
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum RejectedCmpop {
|
||||
Eq,
|
||||
NotEq,
|
||||
}
|
||||
|
||||
#[derive(AsRefStr, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CheckKind {
|
||||
// pycodestyle errors
|
||||
AmbiguousClassName(String),
|
||||
AmbiguousFunctionName(String),
|
||||
AmbiguousVariableName(String),
|
||||
AssertTuple,
|
||||
BreakOutsideLoop,
|
||||
ContinueOutsideLoop,
|
||||
DefaultExceptNotLast,
|
||||
DoNotAssignLambda,
|
||||
DoNotUseBareExcept,
|
||||
DuplicateArgumentName,
|
||||
ExpressionsInStarAssignment,
|
||||
FStringMissingPlaceholders,
|
||||
ForwardAnnotationSyntaxError(String),
|
||||
FutureFeatureNotDefined(String),
|
||||
IOError(String),
|
||||
IfTuple,
|
||||
ImportShadowedByLoopVar(String, usize),
|
||||
ImportStarNotPermitted(String),
|
||||
ImportStarUsage(String, Vec<String>),
|
||||
ImportStarUsed(String),
|
||||
InvalidPrintSyntax,
|
||||
IsLiteral,
|
||||
LateFutureImport,
|
||||
LineTooLong(usize, usize),
|
||||
ModuleImportNotAtTopOfFile,
|
||||
MultiValueRepeatedKeyLiteral,
|
||||
MultiValueRepeatedKeyVariable(String),
|
||||
NoneComparison(RejectedCmpop),
|
||||
NotInTest,
|
||||
NotIsTest,
|
||||
RaiseNotImplemented,
|
||||
ReturnOutsideFunction,
|
||||
SyntaxError(String),
|
||||
TrueFalseComparison(bool, RejectedCmpop),
|
||||
TwoStarredExpressions,
|
||||
TypeComparison,
|
||||
UndefinedExport(String),
|
||||
UndefinedLocal(String),
|
||||
UndefinedName(String),
|
||||
UnusedImport(Vec<String>),
|
||||
UnusedVariable(String),
|
||||
YieldOutsideFunction,
|
||||
// pycodestyle warnings
|
||||
NoNewLineAtEndOfFile,
|
||||
// flake8-builtin
|
||||
BuiltinVariableShadowing(String),
|
||||
BuiltinArgumentShadowing(String),
|
||||
BuiltinAttributeShadowing(String),
|
||||
// flakes8-comprehensions
|
||||
UnnecessaryGeneratorList,
|
||||
UnnecessaryGeneratorSet,
|
||||
UnnecessaryGeneratorDict,
|
||||
UnnecessaryListComprehensionSet,
|
||||
UnnecessaryListComprehensionDict,
|
||||
UnnecessaryLiteralSet(String),
|
||||
UnnecessaryLiteralDict(String),
|
||||
UnnecessaryCollectionCall(String),
|
||||
UnnecessarySubscriptReversal(String),
|
||||
// flake8-super
|
||||
SuperCallWithParameters,
|
||||
// flake8-print
|
||||
PrintFound,
|
||||
PPrintFound,
|
||||
// pyupgrade
|
||||
TypeOfPrimitive(Primitive),
|
||||
UnnecessaryAbspath,
|
||||
UselessMetaclassType,
|
||||
NoAssertEquals,
|
||||
UselessObjectInheritance(String),
|
||||
UsePEP585Annotation(String),
|
||||
UsePEP604Annotation,
|
||||
// Meta
|
||||
UnusedNOQA(Option<String>),
|
||||
}
|
||||
|
||||
impl CheckCode {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
// pycodestyle
|
||||
CheckCode::E402 => "E402",
|
||||
CheckCode::E501 => "E501",
|
||||
CheckCode::E711 => "E711",
|
||||
CheckCode::E712 => "E712",
|
||||
CheckCode::E713 => "E713",
|
||||
CheckCode::E714 => "E714",
|
||||
CheckCode::E721 => "E721",
|
||||
CheckCode::E722 => "E722",
|
||||
CheckCode::E731 => "E731",
|
||||
CheckCode::E741 => "E741",
|
||||
CheckCode::E742 => "E742",
|
||||
CheckCode::E743 => "E743",
|
||||
CheckCode::E902 => "E902",
|
||||
CheckCode::E999 => "E999",
|
||||
// pyflakes
|
||||
CheckCode::F401 => "F401",
|
||||
CheckCode::F402 => "F402",
|
||||
CheckCode::F403 => "F403",
|
||||
CheckCode::F404 => "F404",
|
||||
CheckCode::F405 => "F405",
|
||||
CheckCode::F406 => "F406",
|
||||
CheckCode::F407 => "F407",
|
||||
CheckCode::F541 => "F541",
|
||||
CheckCode::F601 => "F601",
|
||||
CheckCode::F602 => "F602",
|
||||
CheckCode::F621 => "F621",
|
||||
CheckCode::F622 => "F622",
|
||||
CheckCode::F631 => "F631",
|
||||
CheckCode::F632 => "F632",
|
||||
CheckCode::F633 => "F633",
|
||||
CheckCode::F634 => "F634",
|
||||
CheckCode::F701 => "F701",
|
||||
CheckCode::F702 => "F702",
|
||||
CheckCode::F704 => "F704",
|
||||
CheckCode::F706 => "F706",
|
||||
CheckCode::F707 => "F707",
|
||||
CheckCode::F722 => "F722",
|
||||
CheckCode::F821 => "F821",
|
||||
CheckCode::F822 => "F822",
|
||||
CheckCode::F823 => "F823",
|
||||
CheckCode::F831 => "F831",
|
||||
CheckCode::F841 => "F841",
|
||||
CheckCode::F901 => "F901",
|
||||
// flake8-builtins
|
||||
CheckCode::A001 => "A001",
|
||||
CheckCode::A002 => "A002",
|
||||
CheckCode::A003 => "A003",
|
||||
// flake8-super
|
||||
CheckCode::SPR001 => "SPR001",
|
||||
// flake8-print
|
||||
CheckCode::T201 => "T201",
|
||||
CheckCode::T203 => "T203",
|
||||
// pyupgrade
|
||||
CheckCode::U001 => "U001",
|
||||
// Refactor
|
||||
CheckCode::R001 => "R001",
|
||||
CheckCode::R002 => "R002",
|
||||
// Meta
|
||||
CheckCode::M001 => "M001",
|
||||
}
|
||||
}
|
||||
|
||||
/// The source for the check (either the AST, the filesystem, or the physical lines).
|
||||
pub fn lint_source(&self) -> &'static LintSource {
|
||||
match self {
|
||||
@@ -325,7 +251,7 @@ impl CheckCode {
|
||||
/// A placeholder representation of the CheckKind for the check.
|
||||
pub fn kind(&self) -> CheckKind {
|
||||
match self {
|
||||
// pycodestyle
|
||||
// pycodestyle errors
|
||||
CheckCode::E402 => CheckKind::ModuleImportNotAtTopOfFile,
|
||||
CheckCode::E501 => CheckKind::LineTooLong(89, 88),
|
||||
CheckCode::E711 => CheckKind::NoneComparison(RejectedCmpop::Eq),
|
||||
@@ -340,8 +266,10 @@ impl CheckCode {
|
||||
CheckCode::E743 => CheckKind::AmbiguousFunctionName("...".to_string()),
|
||||
CheckCode::E902 => CheckKind::IOError("IOError: `...`".to_string()),
|
||||
CheckCode::E999 => CheckKind::SyntaxError("`...`".to_string()),
|
||||
// pycodestyle warnings
|
||||
CheckCode::W292 => CheckKind::NoNewLineAtEndOfFile,
|
||||
// pyflakes
|
||||
CheckCode::F401 => CheckKind::UnusedImport("...".to_string()),
|
||||
CheckCode::F401 => CheckKind::UnusedImport(vec!["...".to_string()]),
|
||||
CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1),
|
||||
CheckCode::F403 => CheckKind::ImportStarUsed("...".to_string()),
|
||||
CheckCode::F404 => CheckKind::LateFutureImport,
|
||||
@@ -375,6 +303,20 @@ impl CheckCode {
|
||||
CheckCode::A001 => CheckKind::BuiltinVariableShadowing("...".to_string()),
|
||||
CheckCode::A002 => CheckKind::BuiltinArgumentShadowing("...".to_string()),
|
||||
CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()),
|
||||
// flake8-comprehensions
|
||||
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
|
||||
CheckCode::C401 => CheckKind::UnnecessaryGeneratorSet,
|
||||
CheckCode::C402 => CheckKind::UnnecessaryGeneratorDict,
|
||||
CheckCode::C403 => CheckKind::UnnecessaryListComprehensionSet,
|
||||
CheckCode::C404 => CheckKind::UnnecessaryListComprehensionDict,
|
||||
CheckCode::C405 => CheckKind::UnnecessaryLiteralSet("<list/tuple>".to_string()),
|
||||
CheckCode::C406 => CheckKind::UnnecessaryLiteralDict("<list/tuple>".to_string()),
|
||||
CheckCode::C408 => {
|
||||
CheckKind::UnnecessaryCollectionCall("<dict/list/tuple>".to_string())
|
||||
}
|
||||
CheckCode::C415 => {
|
||||
CheckKind::UnnecessarySubscriptReversal("<reversed/set/sorted>".to_string())
|
||||
}
|
||||
// flake8-super
|
||||
CheckCode::SPR001 => CheckKind::SuperCallWithParameters,
|
||||
// flake8-print
|
||||
@@ -382,155 +324,23 @@ impl CheckCode {
|
||||
CheckCode::T203 => CheckKind::PPrintFound,
|
||||
// pyupgrade
|
||||
CheckCode::U001 => CheckKind::UselessMetaclassType,
|
||||
// Refactor
|
||||
CheckCode::R001 => CheckKind::UselessObjectInheritance("...".to_string()),
|
||||
CheckCode::R002 => CheckKind::NoAssertEquals,
|
||||
CheckCode::U002 => CheckKind::UnnecessaryAbspath,
|
||||
CheckCode::U003 => CheckKind::TypeOfPrimitive(Primitive::Str),
|
||||
CheckCode::U004 => CheckKind::UselessObjectInheritance("...".to_string()),
|
||||
CheckCode::U005 => CheckKind::NoAssertEquals,
|
||||
CheckCode::U006 => CheckKind::UsePEP585Annotation("List".to_string()),
|
||||
CheckCode::U007 => CheckKind::UsePEP604Annotation,
|
||||
// Meta
|
||||
CheckCode::M001 => CheckKind::UnusedNOQA(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub enum LintSource {
|
||||
AST,
|
||||
Lines,
|
||||
FileSystem,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum RejectedCmpop {
|
||||
Eq,
|
||||
NotEq,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CheckKind {
|
||||
AmbiguousClassName(String),
|
||||
AmbiguousFunctionName(String),
|
||||
AmbiguousVariableName(String),
|
||||
AssertTuple,
|
||||
BreakOutsideLoop,
|
||||
ContinueOutsideLoop,
|
||||
DefaultExceptNotLast,
|
||||
DoNotAssignLambda,
|
||||
DoNotUseBareExcept,
|
||||
DuplicateArgumentName,
|
||||
ExpressionsInStarAssignment,
|
||||
FStringMissingPlaceholders,
|
||||
ForwardAnnotationSyntaxError(String),
|
||||
FutureFeatureNotDefined(String),
|
||||
IOError(String),
|
||||
IfTuple,
|
||||
ImportShadowedByLoopVar(String, usize),
|
||||
ImportStarNotPermitted(String),
|
||||
ImportStarUsage(String, Vec<String>),
|
||||
ImportStarUsed(String),
|
||||
InvalidPrintSyntax,
|
||||
IsLiteral,
|
||||
LateFutureImport,
|
||||
LineTooLong(usize, usize),
|
||||
ModuleImportNotAtTopOfFile,
|
||||
MultiValueRepeatedKeyLiteral,
|
||||
MultiValueRepeatedKeyVariable(String),
|
||||
NoAssertEquals,
|
||||
NoneComparison(RejectedCmpop),
|
||||
NotInTest,
|
||||
NotIsTest,
|
||||
RaiseNotImplemented,
|
||||
ReturnOutsideFunction,
|
||||
SyntaxError(String),
|
||||
TrueFalseComparison(bool, RejectedCmpop),
|
||||
TwoStarredExpressions,
|
||||
TypeComparison,
|
||||
UndefinedExport(String),
|
||||
UndefinedLocal(String),
|
||||
UndefinedName(String),
|
||||
UnusedImport(String),
|
||||
UnusedNOQA(Option<String>),
|
||||
UnusedVariable(String),
|
||||
UselessMetaclassType,
|
||||
UselessObjectInheritance(String),
|
||||
YieldOutsideFunction,
|
||||
// flake8-builtin
|
||||
BuiltinVariableShadowing(String),
|
||||
BuiltinArgumentShadowing(String),
|
||||
BuiltinAttributeShadowing(String),
|
||||
// flake8-super
|
||||
SuperCallWithParameters,
|
||||
// flake8-print
|
||||
PrintFound,
|
||||
PPrintFound,
|
||||
}
|
||||
|
||||
impl CheckKind {
|
||||
/// The name of the check.
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
CheckKind::AmbiguousClassName(_) => "AmbiguousClassName",
|
||||
CheckKind::AmbiguousFunctionName(_) => "AmbiguousFunctionName",
|
||||
CheckKind::AmbiguousVariableName(_) => "AmbiguousVariableName",
|
||||
CheckKind::AssertTuple => "AssertTuple",
|
||||
CheckKind::BreakOutsideLoop => "BreakOutsideLoop",
|
||||
CheckKind::ContinueOutsideLoop => "ContinueOutsideLoop",
|
||||
CheckKind::DefaultExceptNotLast => "DefaultExceptNotLast",
|
||||
CheckKind::DoNotAssignLambda => "DoNotAssignLambda",
|
||||
CheckKind::DoNotUseBareExcept => "DoNotUseBareExcept",
|
||||
CheckKind::DuplicateArgumentName => "DuplicateArgumentName",
|
||||
CheckKind::ExpressionsInStarAssignment => "ExpressionsInStarAssignment",
|
||||
CheckKind::FStringMissingPlaceholders => "FStringMissingPlaceholders",
|
||||
CheckKind::ForwardAnnotationSyntaxError(_) => "ForwardAnnotationSyntaxError",
|
||||
CheckKind::FutureFeatureNotDefined(_) => "FutureFeatureNotDefined",
|
||||
CheckKind::IOError(_) => "IOError",
|
||||
CheckKind::IfTuple => "IfTuple",
|
||||
CheckKind::ImportShadowedByLoopVar(_, _) => "ImportShadowedByLoopVar",
|
||||
CheckKind::ImportStarNotPermitted(_) => "ImportStarNotPermitted",
|
||||
CheckKind::ImportStarUsage(_, _) => "ImportStarUsage",
|
||||
CheckKind::ImportStarUsed(_) => "ImportStarUsed",
|
||||
CheckKind::InvalidPrintSyntax => "InvalidPrintSyntax",
|
||||
CheckKind::IsLiteral => "IsLiteral",
|
||||
CheckKind::LateFutureImport => "LateFutureImport",
|
||||
CheckKind::LineTooLong(_, _) => "LineTooLong",
|
||||
CheckKind::ModuleImportNotAtTopOfFile => "ModuleImportNotAtTopOfFile",
|
||||
CheckKind::MultiValueRepeatedKeyLiteral => "MultiValueRepeatedKeyLiteral",
|
||||
CheckKind::MultiValueRepeatedKeyVariable(_) => "MultiValueRepeatedKeyVariable",
|
||||
CheckKind::NoneComparison(_) => "NoneComparison",
|
||||
CheckKind::NotInTest => "NotInTest",
|
||||
CheckKind::NotIsTest => "NotIsTest",
|
||||
CheckKind::RaiseNotImplemented => "RaiseNotImplemented",
|
||||
CheckKind::ReturnOutsideFunction => "ReturnOutsideFunction",
|
||||
CheckKind::SyntaxError(_) => "SyntaxError",
|
||||
CheckKind::TrueFalseComparison(_, _) => "TrueFalseComparison",
|
||||
CheckKind::TwoStarredExpressions => "TwoStarredExpressions",
|
||||
CheckKind::TypeComparison => "TypeComparison",
|
||||
CheckKind::UndefinedExport(_) => "UndefinedExport",
|
||||
CheckKind::UndefinedLocal(_) => "UndefinedLocal",
|
||||
CheckKind::UndefinedName(_) => "UndefinedName",
|
||||
CheckKind::UnusedImport(_) => "UnusedImport",
|
||||
CheckKind::UnusedVariable(_) => "UnusedVariable",
|
||||
CheckKind::YieldOutsideFunction => "YieldOutsideFunction",
|
||||
// flake8-builtins
|
||||
CheckKind::BuiltinVariableShadowing(_) => "BuiltinVariableShadowing",
|
||||
CheckKind::BuiltinArgumentShadowing(_) => "BuiltinArgumentShadowing",
|
||||
CheckKind::BuiltinAttributeShadowing(_) => "BuiltinAttributeShadowing",
|
||||
// flake8-super
|
||||
CheckKind::SuperCallWithParameters => "SuperCallWithParameters",
|
||||
// flake8-print
|
||||
CheckKind::PrintFound => "PrintFound",
|
||||
CheckKind::PPrintFound => "PPrintFound",
|
||||
// pyupgrade
|
||||
CheckKind::UselessMetaclassType => "UselessMetaclassType",
|
||||
// Refactor
|
||||
CheckKind::NoAssertEquals => "NoAssertEquals",
|
||||
CheckKind::UselessObjectInheritance(_) => "UselessObjectInheritance",
|
||||
// Meta
|
||||
CheckKind::UnusedNOQA(_) => "UnusedNOQA",
|
||||
}
|
||||
}
|
||||
|
||||
/// A four-letter shorthand code for the check.
|
||||
pub fn code(&self) -> &'static CheckCode {
|
||||
match self {
|
||||
// pycodestyle errors
|
||||
CheckKind::AmbiguousClassName(_) => &CheckCode::E742,
|
||||
CheckKind::AmbiguousFunctionName(_) => &CheckCode::E743,
|
||||
CheckKind::AmbiguousVariableName(_) => &CheckCode::E741,
|
||||
@@ -573,20 +383,35 @@ impl CheckKind {
|
||||
CheckKind::UnusedImport(_) => &CheckCode::F401,
|
||||
CheckKind::UnusedVariable(_) => &CheckCode::F841,
|
||||
CheckKind::YieldOutsideFunction => &CheckCode::F704,
|
||||
// pycodestyle warnings
|
||||
CheckKind::NoNewLineAtEndOfFile => &CheckCode::W292,
|
||||
// flake8-builtins
|
||||
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
|
||||
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
|
||||
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
|
||||
// flake8-comprehensions
|
||||
CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
|
||||
CheckKind::UnnecessaryGeneratorSet => &CheckCode::C401,
|
||||
CheckKind::UnnecessaryGeneratorDict => &CheckCode::C402,
|
||||
CheckKind::UnnecessaryListComprehensionSet => &CheckCode::C403,
|
||||
CheckKind::UnnecessaryListComprehensionDict => &CheckCode::C404,
|
||||
CheckKind::UnnecessaryLiteralSet(_) => &CheckCode::C405,
|
||||
CheckKind::UnnecessaryLiteralDict(_) => &CheckCode::C406,
|
||||
CheckKind::UnnecessaryCollectionCall(_) => &CheckCode::C408,
|
||||
CheckKind::UnnecessarySubscriptReversal(_) => &CheckCode::C415,
|
||||
// flake8-super
|
||||
CheckKind::SuperCallWithParameters => &CheckCode::SPR001,
|
||||
// flake8-print
|
||||
CheckKind::PrintFound => &CheckCode::T201,
|
||||
CheckKind::PPrintFound => &CheckCode::T203,
|
||||
// pyupgrade
|
||||
CheckKind::TypeOfPrimitive(_) => &CheckCode::U003,
|
||||
CheckKind::UnnecessaryAbspath => &CheckCode::U002,
|
||||
CheckKind::UselessMetaclassType => &CheckCode::U001,
|
||||
// Refactor
|
||||
CheckKind::NoAssertEquals => &CheckCode::R002,
|
||||
CheckKind::UselessObjectInheritance(_) => &CheckCode::R001,
|
||||
CheckKind::NoAssertEquals => &CheckCode::U005,
|
||||
CheckKind::UsePEP585Annotation(_) => &CheckCode::U006,
|
||||
CheckKind::UsePEP604Annotation => &CheckCode::U007,
|
||||
CheckKind::UselessObjectInheritance(_) => &CheckCode::U004,
|
||||
// Meta
|
||||
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
|
||||
}
|
||||
@@ -595,6 +420,7 @@ impl CheckKind {
|
||||
/// The body text for the check.
|
||||
pub fn body(&self) -> String {
|
||||
match self {
|
||||
// pycodestyle errors
|
||||
CheckKind::AmbiguousClassName(name) => {
|
||||
format!("Ambiguous class name: `{}`", name)
|
||||
}
|
||||
@@ -712,14 +538,18 @@ impl CheckKind {
|
||||
CheckKind::UndefinedName(name) => {
|
||||
format!("Undefined name `{name}`")
|
||||
}
|
||||
CheckKind::UnusedImport(name) => format!("`{name}` imported but unused"),
|
||||
CheckKind::UnusedImport(names) => {
|
||||
let names = names.iter().map(|name| format!("`{name}`")).join(", ");
|
||||
format!("{names} imported but unused")
|
||||
}
|
||||
CheckKind::UnusedVariable(name) => {
|
||||
format!("Local variable `{name}` is assigned to but never used")
|
||||
}
|
||||
CheckKind::YieldOutsideFunction => {
|
||||
"`yield` or `yield from` statement outside of a function/method".to_string()
|
||||
}
|
||||
|
||||
// pycodestyle warnings
|
||||
CheckKind::NoNewLineAtEndOfFile => "No newline at end of file".to_string(),
|
||||
// flake8-builtins
|
||||
CheckKind::BuiltinVariableShadowing(name) => {
|
||||
format!("Variable `{name}` is shadowing a python builtin")
|
||||
@@ -730,6 +560,34 @@ impl CheckKind {
|
||||
CheckKind::BuiltinAttributeShadowing(name) => {
|
||||
format!("Class attribute `{name}` is shadowing a python builtin")
|
||||
}
|
||||
// flake8-comprehensions
|
||||
CheckKind::UnnecessaryGeneratorList => {
|
||||
"Unnecessary generator - rewrite as a list comprehension".to_string()
|
||||
}
|
||||
CheckKind::UnnecessaryGeneratorSet => {
|
||||
"Unnecessary generator - rewrite as a set comprehension".to_string()
|
||||
}
|
||||
CheckKind::UnnecessaryGeneratorDict => {
|
||||
"Unnecessary generator - rewrite as a dict comprehension".to_string()
|
||||
}
|
||||
CheckKind::UnnecessaryListComprehensionSet => {
|
||||
"Unnecessary list comprehension - rewrite as a set comprehension".to_string()
|
||||
}
|
||||
CheckKind::UnnecessaryListComprehensionDict => {
|
||||
"Unnecessary list comprehension - rewrite as a dict comprehension".to_string()
|
||||
}
|
||||
CheckKind::UnnecessaryLiteralSet(obj_type) => {
|
||||
format!("Unnecessary {obj_type} literal - rewrite as a set literal")
|
||||
}
|
||||
CheckKind::UnnecessaryLiteralDict(obj_type) => {
|
||||
format!("Unnecessary {obj_type} literal - rewrite as a dict literal")
|
||||
}
|
||||
CheckKind::UnnecessaryCollectionCall(obj_type) => {
|
||||
format!("Unnecessary {obj_type} call - rewrite as a literal")
|
||||
}
|
||||
CheckKind::UnnecessarySubscriptReversal(func) => {
|
||||
format!("Unnecessary subscript reversal of iterable within {func}()")
|
||||
}
|
||||
// flake8-super
|
||||
CheckKind::SuperCallWithParameters => {
|
||||
"Use `super()` instead of `super(__class__, self)`".to_string()
|
||||
@@ -738,14 +596,27 @@ impl CheckKind {
|
||||
CheckKind::PrintFound => "`print` found".to_string(),
|
||||
CheckKind::PPrintFound => "`pprint` found".to_string(),
|
||||
// pyupgrade
|
||||
CheckKind::TypeOfPrimitive(primitive) => {
|
||||
format!("Use `{}` instead of `type(...)`", primitive.builtin())
|
||||
}
|
||||
CheckKind::UnnecessaryAbspath => {
|
||||
"`abspath(__file__)` is unnecessary in Python 3.9 and later".to_string()
|
||||
}
|
||||
CheckKind::UselessMetaclassType => "`__metaclass__ = type` is implied".to_string(),
|
||||
// Refactor
|
||||
CheckKind::NoAssertEquals => {
|
||||
"`assertEquals` is deprecated, use `assertEqual` instead".to_string()
|
||||
}
|
||||
CheckKind::UselessObjectInheritance(name) => {
|
||||
format!("Class `{name}` inherits from object")
|
||||
}
|
||||
CheckKind::UsePEP585Annotation(name) => {
|
||||
format!(
|
||||
"Use `{}` instead of `{}` for type annotations",
|
||||
name.to_lowercase(),
|
||||
name,
|
||||
)
|
||||
}
|
||||
CheckKind::UsePEP604Annotation => "Use `X | Y` for type annotations".to_string(),
|
||||
// Meta
|
||||
CheckKind::UnusedNOQA(code) => match code {
|
||||
None => "Unused `noqa` directive".to_string(),
|
||||
@@ -762,10 +633,14 @@ impl CheckKind {
|
||||
| CheckKind::PPrintFound
|
||||
| CheckKind::PrintFound
|
||||
| CheckKind::SuperCallWithParameters
|
||||
| CheckKind::TypeOfPrimitive(_)
|
||||
| CheckKind::UnnecessaryAbspath
|
||||
| CheckKind::UnusedImport(_)
|
||||
| CheckKind::UnusedNOQA(_)
|
||||
| CheckKind::UselessMetaclassType
|
||||
| CheckKind::UselessObjectInheritance(_)
|
||||
| CheckKind::UsePEP585Annotation(_)
|
||||
| CheckKind::UsePEP604Annotation
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -800,3 +675,25 @@ impl Check {
|
||||
self.fix = Some(fix);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
|
||||
#[test]
|
||||
fn check_code_serialization() -> Result<()> {
|
||||
for check_code in CheckCode::iter() {
|
||||
assert!(
|
||||
CheckCode::from_str(check_code.as_ref()).is_ok(),
|
||||
"{:?} could not be round-trip serialized.",
|
||||
check_code
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
136
src/cli.rs
Normal file
136
src/cli.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{command, Parser};
|
||||
use log::warn;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::printer::SerializationFormat;
|
||||
use crate::pyproject::StrCheckCodePair;
|
||||
use crate::settings::PythonVersion;
|
||||
use crate::RawSettings;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(author, about = "ruff: An extremely fast Python linter.")]
|
||||
#[command(version)]
|
||||
pub struct Cli {
|
||||
#[arg(required = true)]
|
||||
pub files: Vec<PathBuf>,
|
||||
/// Enable verbose logging.
|
||||
#[arg(short, long)]
|
||||
pub verbose: bool,
|
||||
/// Disable all logging (but still exit with status code "1" upon detecting errors).
|
||||
#[arg(short, long)]
|
||||
pub quiet: bool,
|
||||
/// Exit with status code "0", even upon detecting errors.
|
||||
#[arg(short, long)]
|
||||
pub exit_zero: bool,
|
||||
/// Run in watch mode by re-running whenever files change.
|
||||
#[arg(short, long)]
|
||||
pub watch: bool,
|
||||
/// Attempt to automatically fix lint errors.
|
||||
#[arg(short, long)]
|
||||
pub fix: bool,
|
||||
/// Disable cache reads.
|
||||
#[arg(short, long)]
|
||||
pub no_cache: bool,
|
||||
/// List of error codes to enable.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub select: Vec<CheckCode>,
|
||||
/// Like --select, but adds additional error codes on top of the selected ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_select: Vec<CheckCode>,
|
||||
/// List of error codes to ignore.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub ignore: Vec<CheckCode>,
|
||||
/// Like --ignore, but adds additional error codes on top of the ignored ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_ignore: Vec<CheckCode>,
|
||||
/// List of paths, used to exclude files and/or directories from checks.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub exclude: Vec<String>,
|
||||
/// Like --exclude, but adds additional files and directories on top of the excluded ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extend_exclude: Vec<String>,
|
||||
/// List of mappings from file pattern to code to exclude
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub per_file_ignores: Vec<StrCheckCodePair>,
|
||||
/// Output serialization format for error messages.
|
||||
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
|
||||
pub format: SerializationFormat,
|
||||
/// See the files ruff will be run against with the current settings.
|
||||
#[arg(long)]
|
||||
pub show_files: bool,
|
||||
/// See ruff's settings.
|
||||
#[arg(long)]
|
||||
pub show_settings: bool,
|
||||
/// Enable automatic additions of noqa directives to failing lines.
|
||||
#[arg(long)]
|
||||
pub add_noqa: bool,
|
||||
/// Regular expression matching the name of dummy variables.
|
||||
#[arg(long)]
|
||||
pub dummy_variable_rgx: Option<Regex>,
|
||||
/// The minimum Python version that should be supported.
|
||||
#[arg(long)]
|
||||
pub target_version: Option<PythonVersion>,
|
||||
/// Round-trip auto-formatting.
|
||||
// TODO(charlie): This should be a sub-command.
|
||||
#[arg(long, hide = true)]
|
||||
pub autoformat: bool,
|
||||
}
|
||||
|
||||
pub enum Warnable {
|
||||
Select,
|
||||
ExtendSelect,
|
||||
}
|
||||
|
||||
impl fmt::Display for Warnable {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Warnable::Select => fmt.write_str("--select"),
|
||||
Warnable::ExtendSelect => fmt.write_str("--extend-select"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Warn the user if they attempt to enable a code that won't be respected.
|
||||
pub fn warn_on(
|
||||
flag: Warnable,
|
||||
codes: &Vec<CheckCode>,
|
||||
cli_ignore: &Vec<CheckCode>,
|
||||
cli_extend_ignore: &Vec<CheckCode>,
|
||||
pyproject_settings: &RawSettings,
|
||||
pyproject_path: &Option<PathBuf>,
|
||||
) {
|
||||
for code in codes {
|
||||
if !cli_ignore.is_empty() {
|
||||
if cli_ignore.contains(code) {
|
||||
warn!("{code:?} was passed to {flag}, but ignored via --ignore")
|
||||
}
|
||||
} else if pyproject_settings.ignore.contains(code) {
|
||||
if let Some(path) = pyproject_path {
|
||||
warn!(
|
||||
"{code:?} was passed to {flag}, but ignored by the `ignore` field in {}",
|
||||
path.to_string_lossy()
|
||||
)
|
||||
} else {
|
||||
warn!("{code:?} was passed to {flag}, but ignored by the default `ignore` field",)
|
||||
}
|
||||
}
|
||||
if !cli_extend_ignore.is_empty() {
|
||||
if cli_extend_ignore.contains(code) {
|
||||
warn!("{code:?} was passed to {flag}, but ignored via --extend-ignore")
|
||||
}
|
||||
} else if pyproject_settings.extend_ignore.contains(code) {
|
||||
if let Some(path) = pyproject_path {
|
||||
warn!(
|
||||
"{code:?} was passed to {flag}, but ignored by the `extend_ignore` field in {}",
|
||||
path.to_string_lossy()
|
||||
)
|
||||
} else {
|
||||
warn!("{code:?} was passed to {flag}, but ignored by the default `extend_ignore` field")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -547,7 +547,7 @@ impl SourceGenerator {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unparse_expr<U>(&mut self, ast: &Expr<U>, level: u8) -> fmt::Result {
|
||||
pub fn unparse_expr<U>(&mut self, ast: &Expr<U>, level: u8) -> fmt::Result {
|
||||
macro_rules! opprec {
|
||||
($opty:ident, $x:expr, $enu:path, $($var:ident($op:literal, $prec:ident)),*$(,)?) => {
|
||||
match $x {
|
||||
|
||||
@@ -15,12 +15,14 @@ pub mod cache;
|
||||
pub mod check_ast;
|
||||
mod check_lines;
|
||||
pub mod checks;
|
||||
pub mod cli;
|
||||
pub mod code_gen;
|
||||
pub mod fs;
|
||||
pub mod linter;
|
||||
pub mod logging;
|
||||
pub mod message;
|
||||
mod noqa;
|
||||
mod plugins;
|
||||
pub mod printer;
|
||||
pub mod pyproject;
|
||||
mod python;
|
||||
@@ -40,7 +42,7 @@ pub fn check(path: &Path, contents: &str) -> Result<Vec<Message>> {
|
||||
None => debug!("Unable to find pyproject.toml; using default settings..."),
|
||||
};
|
||||
|
||||
let settings = Settings::from_raw(RawSettings::from_pyproject(pyproject, project_root)?);
|
||||
let settings = Settings::from_raw(RawSettings::from_pyproject(&pyproject, &project_root)?);
|
||||
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(contents);
|
||||
|
||||
240
src/linter.rs
240
src/linter.rs
@@ -339,6 +339,42 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn w292_0() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/W292_0.py"),
|
||||
&settings::Settings::for_rule(CheckCode::W292),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn w292_1() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/W292_1.py"),
|
||||
&settings::Settings::for_rule(CheckCode::W292),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn w292_2() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/W292_2.py"),
|
||||
&settings::Settings::for_rule(CheckCode::W292),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f401() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
@@ -690,30 +726,6 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn r001() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/R001.py"),
|
||||
&settings::Settings::for_rule(CheckCode::R001),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn r002() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/R002.py"),
|
||||
&settings::Settings::for_rule(CheckCode::R002),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
@@ -786,6 +798,114 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c400() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/C400.py"),
|
||||
&settings::Settings::for_rule(CheckCode::C400),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c401() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/C401.py"),
|
||||
&settings::Settings::for_rule(CheckCode::C401),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c402() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/C402.py"),
|
||||
&settings::Settings::for_rule(CheckCode::C402),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c403() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/C403.py"),
|
||||
&settings::Settings::for_rule(CheckCode::C403),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c404() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/C404.py"),
|
||||
&settings::Settings::for_rule(CheckCode::C404),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c405() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/C405.py"),
|
||||
&settings::Settings::for_rule(CheckCode::C405),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c406() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/C406.py"),
|
||||
&settings::Settings::for_rule(CheckCode::C406),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c408() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/C408.py"),
|
||||
&settings::Settings::for_rule(CheckCode::C408),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c415() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/C415.py"),
|
||||
&settings::Settings::for_rule(CheckCode::C415),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spr001() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
@@ -833,4 +953,76 @@ mod tests {
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u002() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/U002.py"),
|
||||
&settings::Settings::for_rule(CheckCode::U002),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u003() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/U003.py"),
|
||||
&settings::Settings::for_rule(CheckCode::U003),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u004() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/U004.py"),
|
||||
&settings::Settings::for_rule(CheckCode::U004),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u005() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/U005.py"),
|
||||
&settings::Settings::for_rule(CheckCode::U005),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u006() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/U006.py"),
|
||||
&settings::Settings::for_rule(CheckCode::U006),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u007() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/U007.py"),
|
||||
&settings::Settings::for_rule(CheckCode::U007),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
127
src/main.rs
127
src/main.rs
@@ -5,98 +5,33 @@ use std::sync::mpsc::channel;
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{command, Parser};
|
||||
use clap::Parser;
|
||||
use colored::Colorize;
|
||||
use log::{debug, error};
|
||||
use notify::{raw_watcher, RecursiveMode, Watcher};
|
||||
use rayon::prelude::*;
|
||||
use regex::Regex;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use ::ruff::cache;
|
||||
use ::ruff::checks::CheckCode;
|
||||
use ::ruff::checks::CheckKind;
|
||||
use ::ruff::fs::iter_python_files;
|
||||
use ::ruff::linter::add_noqa_to_path;
|
||||
use ::ruff::linter::lint_path;
|
||||
use ::ruff::logging::set_up_logging;
|
||||
use ::ruff::message::Message;
|
||||
use ::ruff::printer::{Printer, SerializationFormat};
|
||||
use ::ruff::pyproject::{self, StrCheckCodePair};
|
||||
use ::ruff::settings::CurrentSettings;
|
||||
use ::ruff::settings::{FilePattern, PerFileIgnore, Settings};
|
||||
use ::ruff::tell_user;
|
||||
use ruff::cache;
|
||||
use ruff::checks::CheckCode;
|
||||
use ruff::checks::CheckKind;
|
||||
use ruff::cli::{warn_on, Cli, Warnable};
|
||||
use ruff::fs::iter_python_files;
|
||||
use ruff::linter::add_noqa_to_path;
|
||||
use ruff::linter::autoformat_path;
|
||||
use ruff::linter::lint_path;
|
||||
use ruff::logging::set_up_logging;
|
||||
use ruff::message::Message;
|
||||
use ruff::printer::{Printer, SerializationFormat};
|
||||
use ruff::pyproject::{self};
|
||||
use ruff::settings::CurrentSettings;
|
||||
use ruff::settings::RawSettings;
|
||||
use ruff::settings::{FilePattern, PerFileIgnore, Settings};
|
||||
use ruff::tell_user;
|
||||
|
||||
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
||||
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(author, about = "ruff: An extremely fast Python linter.")]
|
||||
#[command(version)]
|
||||
struct Cli {
|
||||
#[arg(required = true)]
|
||||
files: Vec<PathBuf>,
|
||||
/// Enable verbose logging.
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
/// Disable all logging (but still exit with status code "1" upon detecting errors).
|
||||
#[arg(short, long)]
|
||||
quiet: bool,
|
||||
/// Exit with status code "0", even upon detecting errors.
|
||||
#[arg(short, long)]
|
||||
exit_zero: bool,
|
||||
/// Run in watch mode by re-running whenever files change.
|
||||
#[arg(short, long)]
|
||||
watch: bool,
|
||||
/// Attempt to automatically fix lint errors.
|
||||
#[arg(short, long)]
|
||||
fix: bool,
|
||||
/// Disable cache reads.
|
||||
#[arg(short, long)]
|
||||
no_cache: bool,
|
||||
/// List of error codes to enable.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
select: Vec<CheckCode>,
|
||||
/// Like --select, but adds additional error codes on top of the selected ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
extend_select: Vec<CheckCode>,
|
||||
/// List of error codes to ignore.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
ignore: Vec<CheckCode>,
|
||||
/// Like --ignore, but adds additional error codes on top of the ignored ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
extend_ignore: Vec<CheckCode>,
|
||||
/// List of paths, used to exclude files and/or directories from checks.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
exclude: Vec<String>,
|
||||
/// Like --exclude, but adds additional files and directories on top of the excluded ones.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
extend_exclude: Vec<String>,
|
||||
/// List of mappings from file pattern to code to exclude
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
per_file_ignores: Vec<StrCheckCodePair>,
|
||||
/// Output serialization format for error messages.
|
||||
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
|
||||
format: SerializationFormat,
|
||||
/// See the files ruff will be run against with the current settings.
|
||||
#[arg(long)]
|
||||
show_files: bool,
|
||||
/// See ruff's settings.
|
||||
#[arg(long)]
|
||||
show_settings: bool,
|
||||
/// Enable automatic additions of noqa directives to failing lines.
|
||||
#[arg(long)]
|
||||
add_noqa: bool,
|
||||
/// Enable automatic formatting.
|
||||
#[arg(long)]
|
||||
autoformat: bool,
|
||||
/// Regular expression matching the name of dummy variables.
|
||||
#[arg(long)]
|
||||
dummy_variable_rgx: Option<Regex>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "update-informer")]
|
||||
fn check_for_updates() {
|
||||
use update_informer::{registry, Check};
|
||||
@@ -121,8 +56,11 @@ fn check_for_updates() {
|
||||
}
|
||||
}
|
||||
|
||||
fn show_settings(settings: RawSettings) {
|
||||
println!("{:#?}", CurrentSettings::from_settings(settings));
|
||||
fn show_settings(settings: RawSettings, project_root: Option<PathBuf>, pyproject: Option<PathBuf>) {
|
||||
println!(
|
||||
"{:#?}",
|
||||
CurrentSettings::from_settings(settings, project_root, pyproject)
|
||||
);
|
||||
}
|
||||
|
||||
fn show_files(files: &[PathBuf], settings: &Settings) {
|
||||
@@ -288,7 +226,7 @@ fn inner_main() -> Result<ExitCode> {
|
||||
.map(|pair| PerFileIgnore::new(pair, &project_root))
|
||||
.collect();
|
||||
|
||||
let mut settings = RawSettings::from_pyproject(pyproject, project_root)?;
|
||||
let mut settings = RawSettings::from_pyproject(&pyproject, &project_root)?;
|
||||
if !exclude.is_empty() {
|
||||
settings.exclude = exclude;
|
||||
}
|
||||
@@ -299,9 +237,25 @@ fn inner_main() -> Result<ExitCode> {
|
||||
settings.per_file_ignores = per_file_ignores;
|
||||
}
|
||||
if !cli.select.is_empty() {
|
||||
warn_on(
|
||||
Warnable::Select,
|
||||
&cli.select,
|
||||
&cli.ignore,
|
||||
&cli.extend_ignore,
|
||||
&settings,
|
||||
&pyproject,
|
||||
);
|
||||
settings.select = cli.select;
|
||||
}
|
||||
if !cli.extend_select.is_empty() {
|
||||
warn_on(
|
||||
Warnable::ExtendSelect,
|
||||
&cli.extend_select,
|
||||
&cli.ignore,
|
||||
&cli.extend_ignore,
|
||||
&settings,
|
||||
&pyproject,
|
||||
);
|
||||
settings.extend_select = cli.extend_select;
|
||||
}
|
||||
if !cli.ignore.is_empty() {
|
||||
@@ -310,6 +264,9 @@ fn inner_main() -> Result<ExitCode> {
|
||||
if !cli.extend_ignore.is_empty() {
|
||||
settings.extend_ignore = cli.extend_ignore;
|
||||
}
|
||||
if let Some(target_version) = cli.target_version {
|
||||
settings.target_version = target_version;
|
||||
}
|
||||
if let Some(dummy_variable_rgx) = cli.dummy_variable_rgx {
|
||||
settings.dummy_variable_rgx = dummy_variable_rgx;
|
||||
}
|
||||
@@ -319,7 +276,7 @@ fn inner_main() -> Result<ExitCode> {
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
if cli.show_settings {
|
||||
show_settings(settings);
|
||||
show_settings(settings, project_root, pyproject);
|
||||
return Ok(ExitCode::SUCCESS);
|
||||
}
|
||||
|
||||
@@ -403,7 +360,7 @@ fn inner_main() -> Result<ExitCode> {
|
||||
#[cfg(feature = "update-informer")]
|
||||
check_for_updates();
|
||||
|
||||
if !messages.is_empty() && !cli.exit_zero {
|
||||
if messages.iter().any(|message| !message.fixed) && !cli.exit_zero {
|
||||
return Ok(ExitCode::FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ impl fmt::Display for Message {
|
||||
":".cyan(),
|
||||
self.location.column(),
|
||||
":".cyan(),
|
||||
self.kind.code().as_str().red().bold(),
|
||||
self.kind.code().as_ref().red().bold(),
|
||||
self.kind.body()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -104,9 +104,7 @@ fn add_noqa_inner(
|
||||
.unwrap_or(lineno);
|
||||
|
||||
if !codes.is_empty() {
|
||||
let matches = matches_by_line
|
||||
.entry(noqa_lineno)
|
||||
.or_insert_with(BTreeSet::new);
|
||||
let matches = matches_by_line.entry(noqa_lineno).or_default();
|
||||
matches.append(&mut codes);
|
||||
}
|
||||
}
|
||||
@@ -127,7 +125,7 @@ fn add_noqa_inner(
|
||||
Directive::All(start, _) => output.push_str(&line[..start]),
|
||||
Directive::Codes(start, _, _) => output.push_str(&line[..start]),
|
||||
};
|
||||
let codes: Vec<&str> = codes.iter().map(|code| code.as_str()).collect();
|
||||
let codes: Vec<&str> = codes.iter().map(|code| code.as_ref()).collect();
|
||||
output.push_str(" # noqa: ");
|
||||
output.push_str(&codes.join(", "));
|
||||
output.push('\n');
|
||||
@@ -152,12 +150,12 @@ pub fn add_noqa(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ast::types::Range;
|
||||
use anyhow::Result;
|
||||
use rustpython_parser::ast::Location;
|
||||
use rustpython_parser::lexer;
|
||||
use rustpython_parser::lexer::LexResult;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::noqa::{add_noqa_inner, extract_noqa_line_for};
|
||||
|
||||
|
||||
25
src/plugins.rs
Normal file
25
src/plugins.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
pub use assert_equals::assert_equals;
|
||||
pub use assert_tuple::assert_tuple;
|
||||
pub use if_tuple::if_tuple;
|
||||
pub use invalid_print_syntax::invalid_print_syntax;
|
||||
pub use print_call::print_call;
|
||||
pub use super_call_with_parameters::super_call_with_parameters;
|
||||
pub use type_of_primitive::type_of_primitive;
|
||||
pub use unnecessary_abspath::unnecessary_abspath;
|
||||
pub use use_pep585_annotation::use_pep585_annotation;
|
||||
pub use use_pep604_annotation::use_pep604_annotation;
|
||||
pub use useless_metaclass_type::useless_metaclass_type;
|
||||
pub use useless_object_inheritance::useless_object_inheritance;
|
||||
|
||||
mod assert_equals;
|
||||
mod assert_tuple;
|
||||
mod if_tuple;
|
||||
mod invalid_print_syntax;
|
||||
mod print_call;
|
||||
mod super_call_with_parameters;
|
||||
mod type_of_primitive;
|
||||
mod unnecessary_abspath;
|
||||
mod use_pep585_annotation;
|
||||
mod use_pep604_annotation;
|
||||
mod useless_metaclass_type;
|
||||
mod useless_object_inheritance;
|
||||
23
src/plugins/assert_equals.rs
Normal file
23
src/plugins/assert_equals.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use rustpython_ast::{Expr, Location};
|
||||
|
||||
use crate::ast::checks;
|
||||
use crate::autofix::fixer;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::Fix;
|
||||
|
||||
pub fn assert_equals(checker: &mut Checker, expr: &Expr) {
|
||||
if let Some(mut check) = checks::check_assert_equals(expr) {
|
||||
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
check.amend(Fix {
|
||||
content: "assertEqual".to_string(),
|
||||
location: Location::new(expr.location.row(), expr.location.column() + 1),
|
||||
end_location: Location::new(
|
||||
expr.location.row(),
|
||||
expr.location.column() + 1 + "assertEquals".len(),
|
||||
),
|
||||
applied: false,
|
||||
});
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
13
src/plugins/assert_tuple.rs
Normal file
13
src/plugins/assert_tuple.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use rustpython_ast::{Expr, Stmt};
|
||||
|
||||
use crate::ast::checks;
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::check_ast::Checker;
|
||||
|
||||
pub fn assert_tuple(checker: &mut Checker, stmt: &Stmt, test: &Expr) {
|
||||
if let Some(check) =
|
||||
checks::check_assert_tuple(test, checker.locate_check(Range::from_located(stmt)))
|
||||
{
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
13
src/plugins/if_tuple.rs
Normal file
13
src/plugins/if_tuple.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use rustpython_ast::{Expr, Stmt};
|
||||
|
||||
use crate::ast::checks;
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::check_ast::Checker;
|
||||
|
||||
pub fn if_tuple(checker: &mut Checker, stmt: &Stmt, test: &Expr) {
|
||||
if let Some(check) =
|
||||
checks::check_if_tuple(test, checker.locate_check(Range::from_located(stmt)))
|
||||
{
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
23
src/plugins/invalid_print_syntax.rs
Normal file
23
src/plugins/invalid_print_syntax.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::{Binding, BindingKind, Range};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
pub fn invalid_print_syntax(checker: &mut Checker, left: &Expr) {
|
||||
if let ExprKind::Name { id, .. } = &left.node {
|
||||
if id == "print" {
|
||||
let scope = checker.current_scope();
|
||||
if let Some(Binding {
|
||||
kind: BindingKind::Builtin,
|
||||
..
|
||||
}) = scope.values.get("print")
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::InvalidPrintSyntax,
|
||||
Range::from_located(left),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/plugins/print_call.rs
Normal file
46
src/plugins/print_call.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use log::error;
|
||||
use rustpython_ast::{Expr, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::checks;
|
||||
use crate::autofix::{fixer, fixes};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::CheckCode;
|
||||
|
||||
pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
|
||||
if let Some(mut check) = checks::check_print_call(
|
||||
expr,
|
||||
func,
|
||||
checker.settings.enabled.contains(&CheckCode::T201),
|
||||
checker.settings.enabled.contains(&CheckCode::T203),
|
||||
) {
|
||||
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
let context = checker.binding_context();
|
||||
if matches!(
|
||||
checker.parents[context.defined_by].node,
|
||||
StmtKind::Expr { .. }
|
||||
) {
|
||||
let deleted: Vec<&Stmt> = checker
|
||||
.deletions
|
||||
.iter()
|
||||
.map(|index| checker.parents[*index])
|
||||
.collect();
|
||||
|
||||
match fixes::remove_stmt(
|
||||
checker.parents[context.defined_by],
|
||||
context.defined_in.map(|index| checker.parents[index]),
|
||||
&deleted,
|
||||
) {
|
||||
Ok(fix) => {
|
||||
if fix.content.is_empty() || fix.content == "pass" {
|
||||
checker.deletions.insert(context.defined_by);
|
||||
}
|
||||
check.amend(fix)
|
||||
}
|
||||
Err(e) => error!("Failed to fix unused imports: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
31
src/plugins/super_call_with_parameters.rs
Normal file
31
src/plugins/super_call_with_parameters.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use rustpython_ast::{Expr, Stmt};
|
||||
|
||||
use crate::ast::checks;
|
||||
use crate::autofix::{fixer, fixes};
|
||||
use crate::check_ast::Checker;
|
||||
|
||||
pub fn super_call_with_parameters(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &Vec<Expr>,
|
||||
) {
|
||||
// Only bother going through the super check at all if we're in a `super` call.
|
||||
// (We check this in `check_super_args` too, so this is just an optimization.)
|
||||
if checks::is_super_call_with_arguments(func, args) {
|
||||
let scope = checker.current_scope();
|
||||
let parents: Vec<&Stmt> = checker
|
||||
.parent_stack
|
||||
.iter()
|
||||
.map(|index| checker.parents[*index])
|
||||
.collect();
|
||||
if let Some(mut check) = checks::check_super_args(scope, &parents, expr, func, args) {
|
||||
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
if let Some(fix) = fixes::remove_super_arguments(&mut checker.locator, expr) {
|
||||
check.amend(fix);
|
||||
}
|
||||
}
|
||||
checker.add_check(check)
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/plugins/type_of_primitive.rs
Normal file
25
src/plugins/type_of_primitive.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use rustpython_ast::Expr;
|
||||
|
||||
use crate::ast::checks;
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::autofix::fixer;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{CheckKind, Fix};
|
||||
|
||||
pub fn type_of_primitive(checker: &mut Checker, expr: &Expr, func: &Expr, args: &Vec<Expr>) {
|
||||
if let Some(mut check) =
|
||||
checks::check_type_of_primitive(func, args, checker.locate_check(Range::from_located(expr)))
|
||||
{
|
||||
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
if let CheckKind::TypeOfPrimitive(primitive) = &check.kind {
|
||||
check.amend(Fix {
|
||||
content: primitive.builtin(),
|
||||
location: expr.location,
|
||||
end_location: expr.end_location,
|
||||
applied: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
25
src/plugins/unnecessary_abspath.rs
Normal file
25
src/plugins/unnecessary_abspath.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use rustpython_ast::Expr;
|
||||
|
||||
use crate::ast::checks;
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::autofix::fixer;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::Fix;
|
||||
|
||||
pub fn unnecessary_abspath(checker: &mut Checker, expr: &Expr, func: &Expr, args: &Vec<Expr>) {
|
||||
if let Some(mut check) = checks::check_unnecessary_abspath(
|
||||
func,
|
||||
args,
|
||||
checker.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
check.amend(Fix {
|
||||
content: "__file__".to_string(),
|
||||
location: expr.location,
|
||||
end_location: expr.end_location,
|
||||
applied: false,
|
||||
});
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
26
src/plugins/use_pep585_annotation.rs
Normal file
26
src/plugins/use_pep585_annotation.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use rustpython_ast::Expr;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::fixer;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind, Fix};
|
||||
use crate::python::typing;
|
||||
|
||||
pub fn use_pep585_annotation(checker: &mut Checker, expr: &Expr, id: &str) {
|
||||
// TODO(charlie): Verify that the builtin is imported from the `typing` module.
|
||||
if typing::is_pep585_builtin(id) {
|
||||
let mut check = Check::new(
|
||||
CheckKind::UsePEP585Annotation(id.to_string()),
|
||||
Range::from_located(expr),
|
||||
);
|
||||
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
check.amend(Fix {
|
||||
content: id.to_lowercase(),
|
||||
location: expr.location,
|
||||
end_location: expr.end_location,
|
||||
applied: false,
|
||||
})
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
100
src/plugins/use_pep604_annotation.rs
Normal file
100
src/plugins/use_pep604_annotation.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprKind, Operator};
|
||||
|
||||
use crate::ast::helpers::match_name_or_attr;
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::fixer;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind, Fix};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
|
||||
fn optional(expr: &Expr) -> Expr {
|
||||
Expr::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
ExprKind::BinOp {
|
||||
left: Box::new(expr.clone()),
|
||||
op: Operator::BitOr,
|
||||
right: Box::new(Expr::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
ExprKind::Constant {
|
||||
value: Constant::None,
|
||||
kind: None,
|
||||
},
|
||||
)),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn union(elts: &[Expr]) -> Expr {
|
||||
if elts.len() == 1 {
|
||||
elts[0].clone()
|
||||
} else {
|
||||
Expr::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
ExprKind::BinOp {
|
||||
left: Box::new(union(&elts[..elts.len() - 1])),
|
||||
op: Operator::BitOr,
|
||||
right: Box::new(elts[elts.len() - 1].clone()),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, slice: &Expr) {
|
||||
if match_name_or_attr(value, "Optional") {
|
||||
let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr));
|
||||
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
let mut generator = SourceGenerator::new();
|
||||
if let Ok(()) = generator.unparse_expr(&optional(slice), 0) {
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix {
|
||||
content,
|
||||
location: expr.location,
|
||||
end_location: expr.end_location,
|
||||
applied: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
} else if match_name_or_attr(value, "Union") {
|
||||
let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr));
|
||||
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
match &slice.node {
|
||||
ExprKind::Slice { .. } => {
|
||||
// Invalid type annotation.
|
||||
}
|
||||
ExprKind::Tuple { elts, .. } => {
|
||||
let mut generator = SourceGenerator::new();
|
||||
if let Ok(()) = generator.unparse_expr(&union(elts), 0) {
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix {
|
||||
content,
|
||||
location: expr.location,
|
||||
end_location: expr.end_location,
|
||||
applied: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Single argument.
|
||||
let mut generator = SourceGenerator::new();
|
||||
if let Ok(()) = generator.unparse_expr(slice, 0) {
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix {
|
||||
content,
|
||||
location: expr.location,
|
||||
end_location: expr.end_location,
|
||||
applied: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
44
src/plugins/useless_metaclass_type.rs
Normal file
44
src/plugins/useless_metaclass_type.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use log::error;
|
||||
use rustpython_ast::{Expr, Stmt};
|
||||
|
||||
use crate::ast::checks;
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::autofix::{fixer, fixes};
|
||||
use crate::check_ast::Checker;
|
||||
|
||||
pub fn useless_metaclass_type(
|
||||
checker: &mut Checker,
|
||||
stmt: &Stmt,
|
||||
value: &Expr,
|
||||
targets: &Vec<Expr>,
|
||||
) {
|
||||
if let Some(mut check) = checks::check_useless_metaclass_type(
|
||||
targets,
|
||||
value,
|
||||
checker.locate_check(Range::from_located(stmt)),
|
||||
) {
|
||||
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
let context = checker.binding_context();
|
||||
let deleted: Vec<&Stmt> = checker
|
||||
.deletions
|
||||
.iter()
|
||||
.map(|index| checker.parents[*index])
|
||||
.collect();
|
||||
|
||||
match fixes::remove_stmt(
|
||||
checker.parents[context.defined_by],
|
||||
context.defined_in.map(|index| checker.parents[index]),
|
||||
&deleted,
|
||||
) {
|
||||
Ok(fix) => {
|
||||
if fix.content.is_empty() || fix.content == "pass" {
|
||||
checker.deletions.insert(context.defined_by);
|
||||
}
|
||||
check.amend(fix)
|
||||
}
|
||||
Err(e) => error!("Failed to fix unused imports: {}", e),
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
29
src/plugins/useless_object_inheritance.rs
Normal file
29
src/plugins/useless_object_inheritance.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use rustpython_ast::{Expr, Keyword, Stmt};
|
||||
|
||||
use crate::ast::checks;
|
||||
use crate::autofix::{fixer, fixes};
|
||||
use crate::check_ast::Checker;
|
||||
|
||||
pub fn useless_object_inheritance(
|
||||
checker: &mut Checker,
|
||||
stmt: &Stmt,
|
||||
name: &str,
|
||||
bases: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
let scope = checker.current_scope();
|
||||
if let Some(mut check) = checks::check_useless_object_inheritance(name, bases, scope) {
|
||||
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
if let Some(fix) = fixes::remove_class_def_base(
|
||||
&mut checker.locator,
|
||||
&stmt.location,
|
||||
check.location,
|
||||
bases,
|
||||
keywords,
|
||||
) {
|
||||
check.amend(fix);
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use serde::{Deserialize, Deserializer};
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::fs;
|
||||
use crate::settings::PythonVersion;
|
||||
|
||||
pub fn load_config(pyproject: &Option<PathBuf>) -> Result<Config> {
|
||||
match pyproject {
|
||||
@@ -41,6 +42,7 @@ pub struct Config {
|
||||
#[serde(default)]
|
||||
pub per_file_ignores: Vec<StrCheckCodePair>,
|
||||
pub dummy_variable_rgx: Option<String>,
|
||||
pub target_version: Option<PythonVersion>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -152,12 +154,13 @@ mod tests {
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use super::StrCheckCodePair;
|
||||
use crate::checks::CheckCode;
|
||||
use crate::pyproject::{
|
||||
find_project_root, find_pyproject_toml, parse_pyproject_toml, Config, PyProject, Tools,
|
||||
};
|
||||
|
||||
use super::StrCheckCodePair;
|
||||
|
||||
#[test]
|
||||
fn deserialize() -> Result<()> {
|
||||
let pyproject: PyProject = toml::from_str(r#""#)?;
|
||||
@@ -189,6 +192,7 @@ mod tests {
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![],
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -213,6 +217,7 @@ line-length = 79
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![],
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -237,6 +242,7 @@ exclude = ["foo.py"]
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![],
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -261,6 +267,7 @@ select = ["E501"]
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![],
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -286,6 +293,7 @@ ignore = ["E501"]
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![],
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -354,6 +362,7 @@ other-attribute = 1
|
||||
extend_ignore: vec![],
|
||||
per_file_ignores: vec![],
|
||||
dummy_variable_rgx: None,
|
||||
target_version: None,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ static ANNOTATED_SUBSCRIPTS: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
"AbstractAsyncContextManager",
|
||||
"AbstractContextManager",
|
||||
"AbstractSet",
|
||||
// "Annotated",
|
||||
"AsyncContextManager",
|
||||
"AsyncGenerator",
|
||||
"AsyncIterable",
|
||||
@@ -87,3 +88,14 @@ static ANNOTATED_SUBSCRIPTS: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
pub fn is_annotated_subscript(name: &str) -> bool {
|
||||
ANNOTATED_SUBSCRIPTS.contains(name)
|
||||
}
|
||||
|
||||
pub fn is_pep593_annotated_subscript(name: &str) -> bool {
|
||||
name == "Annotated"
|
||||
}
|
||||
|
||||
static PEP_585_BUILTINS: Lazy<BTreeSet<&'static str>> =
|
||||
Lazy::new(|| BTreeSet::from(["Dict", "FrozenSet", "List", "Set", "Tuple", "Type"]));
|
||||
|
||||
pub fn is_pep585_builtin(name: &str) -> bool {
|
||||
PEP_585_BUILTINS.contains(name)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,50 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use glob::Pattern;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::checks::{CheckCode, DEFAULT_CHECK_CODES};
|
||||
use crate::fs;
|
||||
use crate::pyproject::{load_config, StrCheckCodePair};
|
||||
|
||||
#[derive(Clone, Debug, PartialOrd, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum PythonVersion {
|
||||
Py33,
|
||||
Py34,
|
||||
Py35,
|
||||
Py36,
|
||||
Py37,
|
||||
Py38,
|
||||
Py39,
|
||||
Py310,
|
||||
Py311,
|
||||
}
|
||||
|
||||
impl FromStr for PythonVersion {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(string: &str) -> Result<Self, Self::Err> {
|
||||
match string {
|
||||
"py33" => Ok(PythonVersion::Py33),
|
||||
"py34" => Ok(PythonVersion::Py34),
|
||||
"py35" => Ok(PythonVersion::Py35),
|
||||
"py36" => Ok(PythonVersion::Py36),
|
||||
"py37" => Ok(PythonVersion::Py37),
|
||||
"py38" => Ok(PythonVersion::Py38),
|
||||
"py39" => Ok(PythonVersion::Py39),
|
||||
"py310" => Ok(PythonVersion::Py310),
|
||||
"py311" => Ok(PythonVersion::Py311),
|
||||
_ => Err(anyhow!("Unknown version: {}", string)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub enum FilePattern {
|
||||
Simple(&'static str),
|
||||
@@ -60,9 +94,8 @@ pub struct RawSettings {
|
||||
pub ignore: Vec<CheckCode>,
|
||||
pub line_length: usize,
|
||||
pub per_file_ignores: Vec<PerFileIgnore>,
|
||||
pub project_root: Option<PathBuf>,
|
||||
pub pyproject: Option<PathBuf>,
|
||||
pub select: Vec<CheckCode>,
|
||||
pub target_version: PythonVersion,
|
||||
}
|
||||
|
||||
static DEFAULT_EXCLUDE: Lazy<Vec<FilePattern>> = Lazy::new(|| {
|
||||
@@ -94,44 +127,43 @@ static DEFAULT_DUMMY_VARIABLE_RGX: Lazy<Regex> =
|
||||
|
||||
impl RawSettings {
|
||||
pub fn from_pyproject(
|
||||
pyproject: Option<PathBuf>,
|
||||
project_root: Option<PathBuf>,
|
||||
pyproject: &Option<PathBuf>,
|
||||
project_root: &Option<PathBuf>,
|
||||
) -> Result<Self> {
|
||||
let config = load_config(&pyproject)?;
|
||||
let config = load_config(pyproject)?;
|
||||
Ok(RawSettings {
|
||||
dummy_variable_rgx: match config.dummy_variable_rgx {
|
||||
Some(pattern) => Regex::new(&pattern)
|
||||
.map_err(|e| anyhow!("Invalid dummy-variable-rgx value: {e}"))?,
|
||||
None => DEFAULT_DUMMY_VARIABLE_RGX.clone(),
|
||||
},
|
||||
target_version: config.target_version.unwrap_or(PythonVersion::Py310),
|
||||
exclude: config
|
||||
.exclude
|
||||
.map(|paths| {
|
||||
paths
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, &project_root))
|
||||
.map(|path| FilePattern::from_user(path, project_root))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()),
|
||||
extend_exclude: config
|
||||
.extend_exclude
|
||||
.iter()
|
||||
.map(|path| FilePattern::from_user(path, &project_root))
|
||||
.map(|path| FilePattern::from_user(path, project_root))
|
||||
.collect(),
|
||||
extend_ignore: config.extend_ignore,
|
||||
select: config
|
||||
.select
|
||||
.unwrap_or_else(|| DEFAULT_CHECK_CODES.to_vec()),
|
||||
extend_select: config.extend_select,
|
||||
ignore: config.ignore,
|
||||
line_length: config.line_length.unwrap_or(88),
|
||||
per_file_ignores: config
|
||||
.per_file_ignores
|
||||
.into_iter()
|
||||
.map(|pair| PerFileIgnore::new(pair, &project_root))
|
||||
.map(|pair| PerFileIgnore::new(pair, project_root))
|
||||
.collect(),
|
||||
project_root,
|
||||
pyproject,
|
||||
select: config
|
||||
.select
|
||||
.unwrap_or_else(|| DEFAULT_CHECK_CODES.to_vec()),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -144,6 +176,7 @@ pub struct Settings {
|
||||
pub extend_exclude: Vec<FilePattern>,
|
||||
pub line_length: usize,
|
||||
pub per_file_ignores: Vec<PerFileIgnore>,
|
||||
pub target_version: PythonVersion,
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
@@ -165,6 +198,7 @@ impl Settings {
|
||||
extend_exclude: settings.extend_exclude,
|
||||
line_length: settings.line_length,
|
||||
per_file_ignores: settings.per_file_ignores,
|
||||
target_version: PythonVersion::Py310,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +210,7 @@ impl Settings {
|
||||
extend_exclude: vec![],
|
||||
line_length: 88,
|
||||
per_file_ignores: vec![],
|
||||
target_version: PythonVersion::Py310,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +222,7 @@ impl Settings {
|
||||
extend_exclude: vec![],
|
||||
line_length: 88,
|
||||
per_file_ignores: vec![],
|
||||
target_version: PythonVersion::Py310,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,13 +274,18 @@ pub struct CurrentSettings {
|
||||
pub ignore: Vec<CheckCode>,
|
||||
pub line_length: usize,
|
||||
pub per_file_ignores: Vec<PerFileIgnore>,
|
||||
pub select: Vec<CheckCode>,
|
||||
pub target_version: PythonVersion,
|
||||
pub project_root: Option<PathBuf>,
|
||||
pub pyproject: Option<PathBuf>,
|
||||
pub select: Vec<CheckCode>,
|
||||
}
|
||||
|
||||
impl CurrentSettings {
|
||||
pub fn from_settings(settings: RawSettings) -> Self {
|
||||
pub fn from_settings(
|
||||
settings: RawSettings,
|
||||
project_root: Option<PathBuf>,
|
||||
pyproject: Option<PathBuf>,
|
||||
) -> Self {
|
||||
Self {
|
||||
dummy_variable_rgx: settings.dummy_variable_rgx,
|
||||
exclude: settings
|
||||
@@ -262,9 +303,10 @@ impl CurrentSettings {
|
||||
ignore: settings.ignore,
|
||||
line_length: settings.line_length,
|
||||
per_file_ignores: settings.per_file_ignores,
|
||||
project_root: settings.project_root,
|
||||
pyproject: settings.pyproject,
|
||||
select: settings.select,
|
||||
target_version: settings.target_version,
|
||||
project_root,
|
||||
pyproject,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
src/snapshots/ruff__linter__tests__c400.snap
Normal file
13
src/snapshots/ruff__linter__tests__c400.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnnecessaryGeneratorList
|
||||
location:
|
||||
row: 1
|
||||
column: 5
|
||||
end_location:
|
||||
row: 1
|
||||
column: 30
|
||||
fix: ~
|
||||
|
||||
13
src/snapshots/ruff__linter__tests__c401.snap
Normal file
13
src/snapshots/ruff__linter__tests__c401.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnnecessaryGeneratorList
|
||||
location:
|
||||
row: 1
|
||||
column: 5
|
||||
end_location:
|
||||
row: 1
|
||||
column: 29
|
||||
fix: ~
|
||||
|
||||
13
src/snapshots/ruff__linter__tests__c402.snap
Normal file
13
src/snapshots/ruff__linter__tests__c402.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnnecessaryListComprehensionDict
|
||||
location:
|
||||
row: 1
|
||||
column: 5
|
||||
end_location:
|
||||
row: 1
|
||||
column: 35
|
||||
fix: ~
|
||||
|
||||
13
src/snapshots/ruff__linter__tests__c403.snap
Normal file
13
src/snapshots/ruff__linter__tests__c403.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnnecessaryListComprehensionSet
|
||||
location:
|
||||
row: 1
|
||||
column: 5
|
||||
end_location:
|
||||
row: 1
|
||||
column: 31
|
||||
fix: ~
|
||||
|
||||
13
src/snapshots/ruff__linter__tests__c404.snap
Normal file
13
src/snapshots/ruff__linter__tests__c404.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnnecessaryListComprehensionDict
|
||||
location:
|
||||
row: 1
|
||||
column: 5
|
||||
end_location:
|
||||
row: 1
|
||||
column: 37
|
||||
fix: ~
|
||||
|
||||
41
src/snapshots/ruff__linter__tests__c405.snap
Normal file
41
src/snapshots/ruff__linter__tests__c405.snap
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnnecessaryLiteralSet: list
|
||||
location:
|
||||
row: 1
|
||||
column: 6
|
||||
end_location:
|
||||
row: 1
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryLiteralSet: tuple
|
||||
location:
|
||||
row: 2
|
||||
column: 6
|
||||
end_location:
|
||||
row: 2
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryLiteralSet: list
|
||||
location:
|
||||
row: 3
|
||||
column: 6
|
||||
end_location:
|
||||
row: 3
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryLiteralSet: tuple
|
||||
location:
|
||||
row: 4
|
||||
column: 6
|
||||
end_location:
|
||||
row: 4
|
||||
column: 13
|
||||
fix: ~
|
||||
|
||||
41
src/snapshots/ruff__linter__tests__c406.snap
Normal file
41
src/snapshots/ruff__linter__tests__c406.snap
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnnecessaryLiteralDict: list
|
||||
location:
|
||||
row: 1
|
||||
column: 6
|
||||
end_location:
|
||||
row: 1
|
||||
column: 20
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryLiteralDict: tuple
|
||||
location:
|
||||
row: 2
|
||||
column: 6
|
||||
end_location:
|
||||
row: 2
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryLiteralDict: list
|
||||
location:
|
||||
row: 3
|
||||
column: 6
|
||||
end_location:
|
||||
row: 3
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryLiteralDict: tuple
|
||||
location:
|
||||
row: 4
|
||||
column: 6
|
||||
end_location:
|
||||
row: 4
|
||||
column: 14
|
||||
fix: ~
|
||||
|
||||
41
src/snapshots/ruff__linter__tests__c408.snap
Normal file
41
src/snapshots/ruff__linter__tests__c408.snap
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnnecessaryCollectionCall: tuple
|
||||
location:
|
||||
row: 1
|
||||
column: 5
|
||||
end_location:
|
||||
row: 1
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryCollectionCall: list
|
||||
location:
|
||||
row: 2
|
||||
column: 5
|
||||
end_location:
|
||||
row: 2
|
||||
column: 11
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryCollectionCall: dict
|
||||
location:
|
||||
row: 3
|
||||
column: 6
|
||||
end_location:
|
||||
row: 3
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryCollectionCall: dict
|
||||
location:
|
||||
row: 4
|
||||
column: 6
|
||||
end_location:
|
||||
row: 4
|
||||
column: 15
|
||||
fix: ~
|
||||
|
||||
41
src/snapshots/ruff__linter__tests__c415.snap
Normal file
41
src/snapshots/ruff__linter__tests__c415.snap
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnnecessarySubscriptReversal: set
|
||||
location:
|
||||
row: 2
|
||||
column: 5
|
||||
end_location:
|
||||
row: 2
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessarySubscriptReversal: reversed
|
||||
location:
|
||||
row: 3
|
||||
column: 5
|
||||
end_location:
|
||||
row: 3
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessarySubscriptReversal: sorted
|
||||
location:
|
||||
row: 4
|
||||
column: 5
|
||||
end_location:
|
||||
row: 4
|
||||
column: 22
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessarySubscriptReversal: sorted
|
||||
location:
|
||||
row: 5
|
||||
column: 5
|
||||
end_location:
|
||||
row: 5
|
||||
column: 36
|
||||
fix: ~
|
||||
|
||||
@@ -3,7 +3,8 @@ source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnusedImport: functools
|
||||
UnusedImport:
|
||||
- functools
|
||||
location:
|
||||
row: 2
|
||||
column: 1
|
||||
@@ -20,7 +21,8 @@ expression: checks
|
||||
column: 21
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedImport: collections.OrderedDict
|
||||
UnusedImport:
|
||||
- collections.OrderedDict
|
||||
location:
|
||||
row: 4
|
||||
column: 1
|
||||
@@ -37,7 +39,8 @@ expression: checks
|
||||
column: 2
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedImport: logging.handlers
|
||||
UnusedImport:
|
||||
- logging.handlers
|
||||
location:
|
||||
row: 12
|
||||
column: 1
|
||||
@@ -54,7 +57,8 @@ expression: checks
|
||||
column: 24
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedImport: shelve
|
||||
UnusedImport:
|
||||
- shelve
|
||||
location:
|
||||
row: 33
|
||||
column: 5
|
||||
@@ -71,7 +75,8 @@ expression: checks
|
||||
column: 1
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedImport: importlib
|
||||
UnusedImport:
|
||||
- importlib
|
||||
location:
|
||||
row: 34
|
||||
column: 5
|
||||
@@ -79,16 +84,17 @@ expression: checks
|
||||
row: 34
|
||||
column: 21
|
||||
fix:
|
||||
content: pass
|
||||
content: ""
|
||||
location:
|
||||
row: 34
|
||||
column: 5
|
||||
column: 1
|
||||
end_location:
|
||||
row: 34
|
||||
column: 21
|
||||
row: 35
|
||||
column: 1
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedImport: pathlib
|
||||
UnusedImport:
|
||||
- pathlib
|
||||
location:
|
||||
row: 38
|
||||
column: 5
|
||||
@@ -105,7 +111,8 @@ expression: checks
|
||||
column: 1
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedImport: pickle
|
||||
UnusedImport:
|
||||
- pickle
|
||||
location:
|
||||
row: 53
|
||||
column: 9
|
||||
|
||||
@@ -9,6 +9,6 @@ expression: checks
|
||||
column: 13
|
||||
end_location:
|
||||
row: 9
|
||||
column: 17
|
||||
column: 16
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ expression: checks
|
||||
column: 5
|
||||
end_location:
|
||||
row: 58
|
||||
column: 9
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: TOMATO
|
||||
@@ -74,4 +74,31 @@ expression: checks
|
||||
row: 89
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: PEP593Test123
|
||||
location:
|
||||
row: 114
|
||||
column: 10
|
||||
end_location:
|
||||
row: 114
|
||||
column: 23
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: foo
|
||||
location:
|
||||
row: 122
|
||||
column: 15
|
||||
end_location:
|
||||
row: 122
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: bar
|
||||
location:
|
||||
row: 122
|
||||
column: 22
|
||||
end_location:
|
||||
row: 122
|
||||
column: 25
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnusedImport: models.Nut
|
||||
UnusedImport:
|
||||
- models.Nut
|
||||
location:
|
||||
row: 5
|
||||
column: 1
|
||||
|
||||
53
src/snapshots/ruff__linter__tests__u002.snap
Normal file
53
src/snapshots/ruff__linter__tests__u002.snap
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnnecessaryAbspath
|
||||
location:
|
||||
row: 3
|
||||
column: 5
|
||||
end_location:
|
||||
row: 3
|
||||
column: 22
|
||||
fix:
|
||||
content: __file__
|
||||
location:
|
||||
row: 3
|
||||
column: 5
|
||||
end_location:
|
||||
row: 3
|
||||
column: 22
|
||||
applied: false
|
||||
- kind: UnnecessaryAbspath
|
||||
location:
|
||||
row: 9
|
||||
column: 5
|
||||
end_location:
|
||||
row: 9
|
||||
column: 30
|
||||
fix:
|
||||
content: __file__
|
||||
location:
|
||||
row: 9
|
||||
column: 5
|
||||
end_location:
|
||||
row: 9
|
||||
column: 30
|
||||
applied: false
|
||||
- kind: UnnecessaryAbspath
|
||||
location:
|
||||
row: 15
|
||||
column: 5
|
||||
end_location:
|
||||
row: 15
|
||||
column: 27
|
||||
fix:
|
||||
content: __file__
|
||||
location:
|
||||
row: 15
|
||||
column: 5
|
||||
end_location:
|
||||
row: 15
|
||||
column: 27
|
||||
applied: false
|
||||
|
||||
90
src/snapshots/ruff__linter__tests__u003.snap
Normal file
90
src/snapshots/ruff__linter__tests__u003.snap
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
TypeOfPrimitive: Str
|
||||
location:
|
||||
row: 1
|
||||
column: 1
|
||||
end_location:
|
||||
row: 1
|
||||
column: 9
|
||||
fix:
|
||||
content: str
|
||||
location:
|
||||
row: 1
|
||||
column: 1
|
||||
end_location:
|
||||
row: 1
|
||||
column: 9
|
||||
applied: false
|
||||
- kind:
|
||||
TypeOfPrimitive: Bytes
|
||||
location:
|
||||
row: 2
|
||||
column: 1
|
||||
end_location:
|
||||
row: 2
|
||||
column: 10
|
||||
fix:
|
||||
content: bytes
|
||||
location:
|
||||
row: 2
|
||||
column: 1
|
||||
end_location:
|
||||
row: 2
|
||||
column: 10
|
||||
applied: false
|
||||
- kind:
|
||||
TypeOfPrimitive: Int
|
||||
location:
|
||||
row: 3
|
||||
column: 1
|
||||
end_location:
|
||||
row: 3
|
||||
column: 8
|
||||
fix:
|
||||
content: int
|
||||
location:
|
||||
row: 3
|
||||
column: 1
|
||||
end_location:
|
||||
row: 3
|
||||
column: 8
|
||||
applied: false
|
||||
- kind:
|
||||
TypeOfPrimitive: Float
|
||||
location:
|
||||
row: 4
|
||||
column: 1
|
||||
end_location:
|
||||
row: 4
|
||||
column: 9
|
||||
fix:
|
||||
content: float
|
||||
location:
|
||||
row: 4
|
||||
column: 1
|
||||
end_location:
|
||||
row: 4
|
||||
column: 9
|
||||
applied: false
|
||||
- kind:
|
||||
TypeOfPrimitive: Complex
|
||||
location:
|
||||
row: 5
|
||||
column: 1
|
||||
end_location:
|
||||
row: 5
|
||||
column: 9
|
||||
fix:
|
||||
content: complex
|
||||
location:
|
||||
row: 5
|
||||
column: 1
|
||||
end_location:
|
||||
row: 5
|
||||
column: 9
|
||||
applied: false
|
||||
|
||||
39
src/snapshots/ruff__linter__tests__u006.snap
Normal file
39
src/snapshots/ruff__linter__tests__u006.snap
Normal file
@@ -0,0 +1,39 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UsePEP585Annotation: List
|
||||
location:
|
||||
row: 4
|
||||
column: 10
|
||||
end_location:
|
||||
row: 4
|
||||
column: 14
|
||||
fix:
|
||||
content: list
|
||||
location:
|
||||
row: 4
|
||||
column: 10
|
||||
end_location:
|
||||
row: 4
|
||||
column: 14
|
||||
applied: false
|
||||
- kind:
|
||||
UsePEP585Annotation: List
|
||||
location:
|
||||
row: 11
|
||||
column: 10
|
||||
end_location:
|
||||
row: 11
|
||||
column: 21
|
||||
fix:
|
||||
content: list
|
||||
location:
|
||||
row: 11
|
||||
column: 10
|
||||
end_location:
|
||||
row: 11
|
||||
column: 21
|
||||
applied: false
|
||||
|
||||
133
src/snapshots/ruff__linter__tests__u007.snap
Normal file
133
src/snapshots/ruff__linter__tests__u007.snap
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UsePEP604Annotation
|
||||
location:
|
||||
row: 4
|
||||
column: 10
|
||||
end_location:
|
||||
row: 4
|
||||
column: 23
|
||||
fix:
|
||||
content: str | None
|
||||
location:
|
||||
row: 4
|
||||
column: 10
|
||||
end_location:
|
||||
row: 4
|
||||
column: 23
|
||||
applied: false
|
||||
- kind: UsePEP604Annotation
|
||||
location:
|
||||
row: 11
|
||||
column: 10
|
||||
end_location:
|
||||
row: 11
|
||||
column: 30
|
||||
fix:
|
||||
content: str | None
|
||||
location:
|
||||
row: 11
|
||||
column: 10
|
||||
end_location:
|
||||
row: 11
|
||||
column: 30
|
||||
applied: false
|
||||
- kind: UsePEP604Annotation
|
||||
location:
|
||||
row: 18
|
||||
column: 10
|
||||
end_location:
|
||||
row: 18
|
||||
column: 46
|
||||
fix:
|
||||
content: "str | int | Union[float, bytes]"
|
||||
location:
|
||||
row: 18
|
||||
column: 10
|
||||
end_location:
|
||||
row: 18
|
||||
column: 46
|
||||
applied: false
|
||||
- kind: UsePEP604Annotation
|
||||
location:
|
||||
row: 18
|
||||
column: 26
|
||||
end_location:
|
||||
row: 18
|
||||
column: 45
|
||||
fix:
|
||||
content: float | bytes
|
||||
location:
|
||||
row: 18
|
||||
column: 26
|
||||
end_location:
|
||||
row: 18
|
||||
column: 45
|
||||
applied: false
|
||||
- kind: UsePEP604Annotation
|
||||
location:
|
||||
row: 25
|
||||
column: 10
|
||||
end_location:
|
||||
row: 25
|
||||
column: 32
|
||||
fix:
|
||||
content: str | int
|
||||
location:
|
||||
row: 25
|
||||
column: 10
|
||||
end_location:
|
||||
row: 25
|
||||
column: 32
|
||||
applied: false
|
||||
- kind: UsePEP604Annotation
|
||||
location:
|
||||
row: 32
|
||||
column: 11
|
||||
end_location:
|
||||
row: 32
|
||||
column: 47
|
||||
fix:
|
||||
content: "str | int | Union[float, bytes]"
|
||||
location:
|
||||
row: 32
|
||||
column: 11
|
||||
end_location:
|
||||
row: 32
|
||||
column: 47
|
||||
applied: false
|
||||
- kind: UsePEP604Annotation
|
||||
location:
|
||||
row: 32
|
||||
column: 11
|
||||
end_location:
|
||||
row: 32
|
||||
column: 47
|
||||
fix:
|
||||
content: float | bytes
|
||||
location:
|
||||
row: 32
|
||||
column: 11
|
||||
end_location:
|
||||
row: 32
|
||||
column: 47
|
||||
applied: false
|
||||
- kind: UsePEP604Annotation
|
||||
location:
|
||||
row: 39
|
||||
column: 11
|
||||
end_location:
|
||||
row: 39
|
||||
column: 33
|
||||
fix:
|
||||
content: str | int
|
||||
location:
|
||||
row: 39
|
||||
column: 11
|
||||
end_location:
|
||||
row: 39
|
||||
column: 33
|
||||
applied: false
|
||||
|
||||
13
src/snapshots/ruff__linter__tests__w292.snap
Normal file
13
src/snapshots/ruff__linter__tests__w292.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: NoNewLineAtEndOfFile
|
||||
location:
|
||||
row: 2
|
||||
column: 9
|
||||
end_location:
|
||||
row: 2
|
||||
column: 9
|
||||
fix: ~
|
||||
|
||||
13
src/snapshots/ruff__linter__tests__w292_0.snap
Normal file
13
src/snapshots/ruff__linter__tests__w292_0.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: NoNewLineAtEndOfFile
|
||||
location:
|
||||
row: 2
|
||||
column: 9
|
||||
end_location:
|
||||
row: 2
|
||||
column: 9
|
||||
fix: ~
|
||||
|
||||
6
src/snapshots/ruff__linter__tests__w292_1.snap
Normal file
6
src/snapshots/ruff__linter__tests__w292_1.snap
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
6
src/snapshots/ruff__linter__tests__w292_2.snap
Normal file
6
src/snapshots/ruff__linter__tests__w292_2.snap
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
Reference in New Issue
Block a user