Compare commits

..

38 Commits

Author SHA1 Message Date
Charlie Marsh
e1b711d9c6 Bump version to 0.0.67 2022-10-10 12:55:04 -04:00
Charlie Marsh
35f593846e Implement B014 from flake8-bugbear (#392) 2022-10-10 12:53:42 -04:00
Charlie Marsh
c384fa513b Implement B025 from flake8-bugbear (#391) 2022-10-10 12:18:31 -04:00
Charlie Marsh
022ff64d29 Implement B011 from flake8-bugbear (#390) 2022-10-10 10:55:55 -04:00
Charlie Marsh
5a06fb28fd Bump version to 0.0.66 2022-10-10 10:03:59 -04:00
Charlie Marsh
46750a3e17 Flag unimplemented error codes in M001 (#388) 2022-10-10 10:03:40 -04:00
Charlie Marsh
9cc902b802 Avoid F821 false-positives with NameError (#386) 2022-10-10 09:39:59 -04:00
Harutaka Kawamura
c2a36ebd1e Implement C410 (#382) 2022-10-09 23:33:55 -04:00
Charlie Marsh
34ca225393 Rename flakes8 to flake8 2022-10-09 23:02:44 -04:00
Harutaka Kawamura
38c30905e6 Implement C409 (#381) 2022-10-09 22:34:52 -04:00
Charlie Marsh
2774194b03 Bump version to 0.0.65 2022-10-09 22:14:04 -04:00
Charlie Marsh
71ebd39f35 Extend assertEquals check to all deprecated unittest aliases (#380) 2022-10-09 22:13:51 -04:00
Charlie Marsh
a2f78ba2c7 Fix auto-fix for assertEquals rename 2022-10-09 22:11:32 -04:00
Charlie Marsh
b51a080a44 Rename SPR001 to U008 (#379) 2022-10-09 21:58:22 -04:00
Charlie Marsh
6a1d7d8a1c Defer string annotations even when futures annotations are enabled (#378) 2022-10-09 18:28:29 -04:00
Charlie Marsh
10b250ee57 Bump version to 0.0.64 2022-10-09 17:38:09 -04:00
Charlie Marsh
30b1b1e15a Treat TypeAlias values as annotations (#377) 2022-10-09 17:37:19 -04:00
Charlie Marsh
aafe7c0c39 Mark aliased submodule imports as used (#374) 2022-10-09 17:01:14 -04:00
Harutaka Kawamura
f060248656 Fix collapsed message (#372) 2022-10-09 13:01:36 -04:00
Harutaka Kawamura
bbe0220c72 Implement C415 (#371) 2022-10-09 10:12:58 -04:00
Charlie Marsh
129e2b6ad3 Bump version to 0.0.63 2022-10-08 22:51:49 -04:00
Charlie Marsh
73e744b1d0 Create unified Expr for PEP 604 rewrites (#370) 2022-10-08 22:13:00 -04:00
Charlie Marsh
50a3fc5a67 Move some code into helpers.rs 2022-10-08 20:45:15 -04:00
Charlie Marsh
de499f0258 Optimize imports 2022-10-08 20:39:13 -04:00
Charlie Marsh
e1abe37c6a Bump version to 0.0.62 2022-10-08 20:28:38 -04:00
Charlie Marsh
7fe5945541 Implement PEP 604 annotation rewrites (#369) 2022-10-08 20:28:00 -04:00
Charlie Marsh
806f3fd4f6 Implement PEP 585 annotation rewrites (#368) 2022-10-08 18:22:24 -04:00
Charlie Marsh
2bba643dd2 Use strum to facilitate simple enum serialization (#367) 2022-10-08 17:47:25 -04:00
Charlie Marsh
54090bd7ac Use strum to iterate over all check codes (#366) 2022-10-08 17:41:47 -04:00
Charlie Marsh
c62727db42 Bump version to 0.0.61 2022-10-08 17:25:36 -04:00
Charlie Marsh
d0e1612507 Check newline ending on contents directly (#365) 2022-10-08 17:25:22 -04:00
Harutaka Kawamura
5ccd907398 Implement C408 (#364) 2022-10-08 17:17:34 -04:00
Harutaka Kawamura
346610c2e3 Implement C406 (#363) 2022-10-08 11:15:41 -04:00
Harutaka Kawamura
307fa26515 Implement C405 (#362) 2022-10-08 09:03:21 -04:00
Harutaka Kawamura
136d412edd Add missing C400,C401, and C402 to CheckCode.from_str (#361) 2022-10-08 09:02:03 -04:00
Harutaka Kawamura
d9edec0ac9 Implement C402 (#359) 2022-10-07 22:57:04 -04:00
Chris Pryer
473675fffb Add check for W292 (#339) 2022-10-07 21:10:16 -04:00
Steven Maude
95dfc61315 Update GitHub Actions versions in README (#358) 2022-10-07 20:24:00 -04:00
76 changed files with 2901 additions and 734 deletions

35
Cargo.lock generated
View File

@@ -1907,7 +1907,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.60"
version = "0.0.67"
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"

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.60"
version = "0.0.67"
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"] }

View File

@@ -215,7 +215,8 @@ 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)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) (11/16)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (3/32)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (partial)
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
@@ -246,6 +247,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 | ✅ | |
@@ -277,18 +279,30 @@ 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 | | |
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | | 🛠 |
| B014 | DuplicateHandlerException | Exception handler with duplicate exception `Exception` | | |
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` | | |
| 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 | | |
| SPR001 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | | 🛠 |
| 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 | | |
| C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary <list/tuple> literal passed to tuple() - remove the outer call to tuple() | | |
| C410 | UnnecessaryLiteralWithinListCall | Unnecessary <list/tuple> literal passed to list() - rewrite as a list literal | | |
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within <reversed/set/sorted>() | | |
| T201 | PrintFound | `print` found | | 🛠 |
| T203 | PPrintFound | `pprint` found | | 🛠 |
| U001 | UselessMetaclassType | `__metaclass__ = type` is implied | | 🛠 |
| U002 | UnnecessaryAbspath | `abspath(__file__)` is unnecessary in Python 3.9 and later | | 🛠 |
| U003 | TypeOfPrimitive | Use `str` instead of `type(...)` | | 🛠 |
| U004 | UselessObjectInheritance | Class `...` inherits from object | | 🛠 |
| U005 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead | | 🛠 |
| U005 | DeprecatedUnittestAlias | `assertEquals` is deprecated, use `assertEqual` instead | | 🛠 |
| U006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | | 🛠 |
| U007 | UsePEP604Annotation | Use `X \| Y` for type annotations | | 🛠 |
| U008 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | | 🛠 |
| M001 | UnusedNOQA | Unused `noqa` directive | | 🛠 |
## Integrations
@@ -316,9 +330,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

View File

@@ -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
);

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

@@ -0,0 +1,10 @@
"""
Should emit:
B011 - on line 8
B011 - on line 10
"""
assert 1 != 2
assert False
assert 1 != 2, "message"
assert False, "message"

76
resources/test/fixtures/B014.py vendored Normal file
View File

@@ -0,0 +1,76 @@
"""
Should emit:
B014 - on lines 11, 17, 28, 42, 49, 56, and 74.
"""
import binascii
import re
try:
pass
except (Exception, TypeError):
# TypeError is a subclass of Exception, so it doesn't add anything
pass
try:
pass
except (OSError, OSError) as err:
# Duplicate exception types are useless
pass
class MyError(Exception):
pass
try:
pass
except (MyError, MyError):
# Detect duplicate non-builtin errors
pass
try:
pass
except (MyError, Exception) as e:
# Don't assume that we're all subclasses of Exception
pass
try:
pass
except (MyError, BaseException) as e:
# But we *can* assume that everything is a subclass of BaseException
pass
try:
pass
except (re.error, re.error):
# Duplicate exception types as attributes
pass
try:
pass
except (IOError, EnvironmentError, OSError):
# Detect if a primary exception and any its aliases are present.
#
# Since Python 3.3, IOError, EnvironmentError, WindowsError, mmap.error,
# socket.error and select.error are aliases of OSError. See PEP 3151 for
# more info.
pass
try:
pass
except (MyException, NotImplemented):
# NotImplemented is not an exception, let's not crash on it.
pass
try:
pass
except (ValueError, binascii.Error):
# binascii.Error is a subclass of ValueError.
pass

38
resources/test/fixtures/B025.py vendored Normal file
View File

@@ -0,0 +1,38 @@
"""
Should emit:
B025 - on lines 15, 22, 31
"""
import pickle
try:
a = 1
except ValueError:
a = 2
finally:
a = 3
try:
a = 1
except ValueError:
a = 2
except ValueError:
a = 2
try:
a = 1
except pickle.PickleError:
a = 2
except ValueError:
a = 2
except pickle.PickleError:
a = 2
try:
a = 1
except (ValueError, TypeError):
a = 2
except ValueError:
a = 2
except (OSError, TypeError):
a = 2

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,3 @@
t1 = tuple([1, 2])
t2 = tuple((1, 2))
t3 = tuple([])

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

@@ -0,0 +1,4 @@
l1 = list([1, 2])
l2 = list((1, 2))
l3 = list([])
l4 = list(())

9
resources/test/fixtures/C415.py vendored Normal file
View 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])

View File

@@ -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"

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

@@ -0,0 +1,5 @@
"""Access a sub-importation via an alias."""
import pyarrow as pa
import pyarrow.csv
print(pa.csv.read_csv("test.csv"))

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

@@ -0,0 +1,12 @@
"""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"

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

@@ -0,0 +1,14 @@
"""Test: referencing an import via TypeAlias (with future annotations)."""
from __future__ import annotations
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

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

@@ -0,0 +1,14 @@
"""Test: referencing an import via TypeAlias (with future annotations and quotes)."""
from __future__ import annotations
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"

View File

@@ -122,3 +122,12 @@ class PEP593Test:
dict["foo", "bar"], # Expected to fail as undefined.
123,
]
def in_ipython_notebook() -> bool:
try:
# autoimported by notebooks
get_ipython() # type: ignore[name-defined]
except NameError:
return False # not in notebook
return True

View File

@@ -15,6 +15,9 @@ def f() -> None:
# Invalid
d = 1 # noqa: F841, E501
# Invalid (and unimplemented)
d = 1 # noqa: F841, W191
# Valid
_ = """Lorem ipsum dolor sit amet.

View File

@@ -1,3 +1,10 @@
self.assertEquals (1, 2)
self.assertEquals(1, 2)
self.assertEqual(3, 4)
import unittest
class Suite(unittest.TestCase):
def test(self) -> None:
self.assertEquals (1, 2)
self.assertEquals(1, 2)
self.assertEqual(3, 4)
self.failUnlessAlmostEqual(1, 1.1)
self.assertNotRegexpMatches("a", "b")

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

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

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

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

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

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

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

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

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

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

View File

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

View File

@@ -1,13 +1,15 @@
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,
Stmt, StmtKind, Unaryop,
KeywordData, Located, Stmt, StmtKind, Unaryop,
};
use serde::{Deserialize, Serialize};
use crate::ast::helpers;
use crate::ast::types::{
Binding, BindingKind, CheckLocator, FunctionScope, Range, Scope, ScopeKind,
};
@@ -360,23 +362,6 @@ pub fn check_duplicate_arguments(arguments: &Arguments) -> Vec<Check> {
checks
}
/// Check AssertEquals compliance.
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" {
return Some(Check::new(
CheckKind::NoAssertEquals,
Range::from_located(expr),
));
}
}
}
}
None
}
#[derive(Debug, PartialEq)]
enum DictionaryKey<'a> {
Constant(&'a Constant),
@@ -763,17 +748,7 @@ pub fn check_builtin_shadowing(
}
}
/// Returns `true` if a call is an argumented `super` invocation.
pub fn is_super_call_with_arguments(func: &Expr, args: &Vec<Expr>) -> bool {
// Check: is this a `super` call?
if let ExprKind::Name { id, .. } = &func.node {
id == "super" && !args.is_empty()
} else {
false
}
}
// flakes8-comprehensions
// flake8-comprehensions
/// Check `list(generator)` compliance.
pub fn unnecessary_generator_list(expr: &Expr, func: &Expr, args: &Vec<Expr>) -> Option<Check> {
if args.len() == 1 {
@@ -808,6 +783,28 @@ pub fn unnecessary_generator_set(expr: &Expr, func: &Expr, args: &Vec<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,
@@ -855,6 +852,212 @@ pub fn unnecessary_list_comprehension_dict(
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_literal_within_tuple_call(
expr: &Expr,
func: &Expr,
args: &[Expr],
) -> Option<Check> {
if let ExprKind::Name { id, .. } = &func.node {
if id == "tuple" {
if let Some(arg) = args.first() {
match &arg.node {
ExprKind::Tuple { .. } => {
return Some(Check::new(
CheckKind::UnnecessaryLiteralWithinTupleCall("tuple".to_string()),
Range::from_located(expr),
));
}
ExprKind::List { .. } => {
return Some(Check::new(
CheckKind::UnnecessaryLiteralWithinTupleCall("list".to_string()),
Range::from_located(expr),
));
}
_ => {}
}
}
}
}
None
}
pub fn unnecessary_literal_within_list_call(
expr: &Expr,
func: &Expr,
args: &[Expr],
) -> Option<Check> {
if let ExprKind::Name { id, .. } = &func.node {
if id == "list" {
if let Some(arg) = args.first() {
match &arg.node {
ExprKind::Tuple { .. } => {
return Some(Check::new(
CheckKind::UnnecessaryLiteralWithinListCall("tuple".to_string()),
Range::from_located(expr),
));
}
ExprKind::List { .. } => {
return Some(Check::new(
CheckKind::UnnecessaryLiteralWithinListCall("list".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(
@@ -864,7 +1067,7 @@ pub fn check_super_args(
func: &Expr,
args: &Vec<Expr>,
) -> Option<Check> {
if !is_super_call_with_arguments(func, args) {
if !helpers::is_super_call_with_arguments(func, args) {
return None;
}

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

@@ -0,0 +1,133 @@
use once_cell::sync::Lazy;
use regex::Regex;
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, StmtKind};
use crate::python::typing;
fn compose_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
match &expr.node {
ExprKind::Call { func, .. } => {
compose_call_path_inner(func, parts);
}
ExprKind::Attribute { value, attr, .. } => {
compose_call_path_inner(value, parts);
parts.push(attr);
}
ExprKind::Name { id, .. } => {
parts.push(id);
}
_ => {}
}
}
pub fn compose_call_path(expr: &Expr) -> Option<String> {
let mut segments = vec![];
compose_call_path_inner(expr, &mut segments);
if segments.is_empty() {
None
} else {
Some(segments.join("."))
}
}
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,
}
}
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,
}
}
static DUNDER_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"__[^\s]+__").unwrap());
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,
}
}
/// Extract the names of all handled exceptions.
pub fn extract_handler_names(handlers: &[Excepthandler]) -> Vec<String> {
let mut handler_names = vec![];
for handler in handlers {
match &handler.node {
ExcepthandlerKind::ExceptHandler { type_, .. } => {
if let Some(type_) = type_ {
if let ExprKind::Tuple { elts, .. } = &type_.node {
for type_ in elts {
if let Some(name) = compose_call_path(type_) {
handler_names.push(name);
}
}
} else if let Some(name) = compose_call_path(type_) {
handler_names.push(name);
}
}
}
}
}
handler_names
}
/// Returns `true` if a call is an argumented `super` invocation.
pub fn is_super_call_with_arguments(func: &Expr, args: &Vec<Expr>) -> bool {
// Check: is this a `super` call?
if let ExprKind::Name { id, .. } = &func.node {
id == "super" && !args.is_empty()
} else {
false
}
}

View File

@@ -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)]

View File

@@ -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);
}

View File

@@ -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::{extract_handler_names, match_name_or_attr, SubscriptKind};
use crate::ast::operations::{extract_all_names, SourceCodeLocator};
use crate::ast::relocate::relocate_expr;
use crate::ast::types::{
@@ -18,19 +18,16 @@ 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::{PythonVersion, Settings};
pub const GLOBAL_SCOPE_INDEX: usize = 0;
static DUNDER_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"__[^\s]+__").unwrap());
pub struct Checker<'a> {
// Input data.
path: &'a Path,
@@ -63,6 +60,7 @@ pub struct Checker<'a> {
seen_docstring: bool,
futures_allowed: bool,
annotations_future_enabled: bool,
except_handlers: Vec<Vec<String>>,
}
impl<'a> Checker<'a> {
@@ -77,96 +75,30 @@ impl<'a> Checker<'a> {
autofix,
path,
locator: SourceCodeLocator::new(content),
checks: vec![],
parents: vec![],
parent_stack: vec![],
scopes: vec![],
scope_stack: vec![],
dead_scopes: vec![],
deferred_string_annotations: vec![],
deferred_annotations: vec![],
deferred_functions: vec![],
deferred_lambdas: vec![],
deferred_assignments: vec![],
in_f_string: None,
in_annotation: false,
in_literal: false,
seen_non_import: false,
seen_docstring: false,
futures_allowed: true,
annotations_future_enabled: false,
checks: Default::default(),
deletions: Default::default(),
parents: Default::default(),
parent_stack: Default::default(),
scopes: Default::default(),
scope_stack: Default::default(),
dead_scopes: Default::default(),
deferred_string_annotations: Default::default(),
deferred_annotations: Default::default(),
deferred_functions: Default::default(),
deferred_lambdas: Default::default(),
deferred_assignments: Default::default(),
in_f_string: None,
in_annotation: Default::default(),
in_literal: Default::default(),
seen_non_import: Default::default(),
seen_docstring: Default::default(),
futures_allowed: true,
annotations_future_enabled: Default::default(),
except_handlers: Default::default(),
}
}
}
fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
match &expr.node {
ExprKind::Attribute { attr, .. } => target == attr,
ExprKind::Name { id, .. } => target == id,
_ => false,
}
}
enum SubscriptKind {
AnnotatedSubscript,
PEP593AnnotatedSubscript,
}
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,
}
}
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,
@@ -227,7 +159,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;
@@ -434,13 +366,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,
@@ -452,19 +387,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,
@@ -493,14 +426,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((
@@ -579,18 +508,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),
},
)
}
}
}
@@ -611,10 +548,13 @@ where
plugins::if_tuple(self, stmt, test);
}
}
StmtKind::Assert { test, .. } => {
StmtKind::Assert { test, msg } => {
if self.settings.enabled.contains(&CheckCode::F631) {
plugins::assert_tuple(self, stmt, test);
}
if self.settings.enabled.contains(&CheckCode::B011) {
plugins::assert_false(self, stmt, test, msg);
}
}
StmtKind::Try { handlers, .. } => {
if self.settings.enabled.contains(&CheckCode::F707) {
@@ -622,6 +562,11 @@ where
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::B014)
|| self.settings.enabled.contains(&CheckCode::B025)
{
plugins::duplicate_exceptions(self, stmt, handlers);
}
}
StmtKind::Assign { targets, value, .. } => {
if self.settings.enabled.contains(&CheckCode::E731) {
@@ -666,6 +611,27 @@ where
self.visit_stmt(stmt);
}
}
StmtKind::Try {
body,
handlers,
orelse,
finalbody,
} => {
self.except_handlers.push(extract_handler_names(handlers));
for stmt in body {
self.visit_stmt(stmt);
}
self.except_handlers.pop();
for excepthandler in handlers {
self.visit_excepthandler(excepthandler)
}
for stmt in orelse {
self.visit_stmt(stmt);
}
for stmt in finalbody {
self.visit_stmt(stmt);
}
}
_ => visitor::walk_stmt(self, stmt),
};
@@ -698,18 +664,33 @@ where
let prev_in_annotation = self.in_annotation;
if self.in_annotation && self.annotations_future_enabled {
self.deferred_annotations.push((
expr,
self.scope_stack.clone(),
self.parent_stack.clone(),
));
visitor::walk_expr(self, expr);
if let ExprKind::Constant {
value: Constant::Str(value),
..
} = &expr.node
{
self.deferred_string_annotations
.push((Range::from_located(expr), value));
} else {
self.deferred_annotations.push((
expr,
self.scope_stack.clone(),
self.parent_stack.clone(),
));
}
return;
}
// 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;
}
@@ -731,7 +712,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(
@@ -748,13 +738,30 @@ where
}
ExprContext::Del => self.handle_node_delete(expr),
},
ExprKind::Call { func, args, .. } => {
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);
plugins::deprecated_unittest_alias(self, func);
}
// flake8-super
if self.settings.enabled.contains(&CheckCode::SPR001) {
if self.settings.enabled.contains(&CheckCode::U008) {
plugins::super_call_with_parameters(self, expr, func, args);
}
@@ -778,6 +785,12 @@ where
};
}
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)
@@ -794,6 +807,47 @@ where
};
}
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::C409) {
if let Some(check) =
checks::unnecessary_literal_within_tuple_call(expr, func, args)
{
self.checks.push(check);
};
}
if self.settings.enabled.contains(&CheckCode::C410) {
if let Some(check) =
checks::unnecessary_literal_within_list_call(expr, func, args)
{
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
@@ -1078,7 +1132,7 @@ where
}
}
ExprKind::Subscript { value, slice, ctx } => {
match match_annotated_subscript(value) {
match helpers::match_annotated_subscript(value) {
Some(subscript) => match subscript {
// Ex) Optional[int]
SubscriptKind::AnnotatedSubscript => {
@@ -1260,6 +1314,47 @@ 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);
@@ -1343,7 +1438,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(
@@ -1382,8 +1481,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;
}
@@ -1418,6 +1517,14 @@ impl<'a> Checker<'a> {
if self.path.ends_with("__init__.py") && id == "__path__" {
return;
}
// Avoid flagging if NameError is handled.
if let Some(handler_names) = self.except_handlers.last() {
if handler_names.contains(&"NameError".to_string()) {
return;
}
}
self.checks.push(Check::new(
CheckKind::UndefinedName(id.clone()),
self.locate_check(Range::from_located(expr)),
@@ -1553,14 +1660,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),
));
}
}
@@ -1695,7 +1825,7 @@ 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,
@@ -1705,8 +1835,8 @@ impl<'a> Checker<'a> {
.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,

View File

@@ -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 {
@@ -145,15 +184,15 @@ pub fn check_lines(
let mut valid_codes = vec![];
for code in codes {
if !matches.contains(&code) {
invalid_codes.push(code);
invalid_codes.push(code.to_string());
} else {
valid_codes.push(code);
valid_codes.push(code.to_string());
}
}
if !invalid_codes.is_empty() {
let mut check = Check::new(
CheckKind::UnusedNOQA(Some(invalid_codes.join(", "))),
CheckKind::UnusedNOQA(Some(invalid_codes)),
Range {
location: Location::new(row + 1, start + 1),
end_location: Location::new(row + 1, end + 1),

View File

@@ -1,15 +1,14 @@
use std::str::FromStr;
use crate::ast::checks::Primitive;
use anyhow::Result;
use itertools::Itertools;
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use strum_macros::{AsRefStr, EnumIter, EnumString};
use crate::ast::checks::Primitive;
use crate::ast::types::Range;
pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
// pycodestyle
pub const DEFAULT_CHECK_CODES: [CheckCode; 43] = [
// pycodestyle errors
CheckCode::E402,
CheckCode::E501,
CheckCode::E711,
@@ -24,6 +23,8 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
CheckCode::E743,
CheckCode::E902,
CheckCode::E999,
// pycodestyle warnings
CheckCode::W292,
// pyflakes
CheckCode::F401,
CheckCode::F402,
@@ -55,78 +56,22 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
CheckCode::F901,
];
pub const ALL_CHECK_CODES: [CheckCode; 58] = [
// 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-comprehensions
CheckCode::C400,
CheckCode::C401,
CheckCode::C403,
CheckCode::C404,
// flake8-super
CheckCode::SPR001,
// flake8-print
CheckCode::T201,
CheckCode::T203,
// pyupgrade
CheckCode::U001,
CheckCode::U002,
CheckCode::U003,
CheckCode::U004,
CheckCode::U005,
// 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,
@@ -141,6 +86,8 @@ pub enum CheckCode {
E743,
E902,
E999,
// pycodestyle warnings
W292,
// pyflakes
F401,
F402,
@@ -174,13 +121,22 @@ pub enum CheckCode {
A001,
A002,
A003,
// flake8-bugbear
B011,
B014,
B025,
// flake8-comprehensions
C400,
C401,
C402,
C403,
C404,
// flake8-super
SPR001,
C405,
C406,
C408,
C409,
C410,
C415,
// flake8-print
T201,
T203,
@@ -190,240 +146,13 @@ pub enum CheckCode {
U003,
U004,
U005,
U006,
U007,
U008,
// Meta
M001,
}
impl FromStr for CheckCode {
type Err = anyhow::Error;
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-comprehensions
"C403" => Ok(CheckCode::C403),
"C404" => Ok(CheckCode::C404),
// flake8-super
"SPR001" => Ok(CheckCode::SPR001),
// flake8-print
"T201" => Ok(CheckCode::T201),
"T203" => Ok(CheckCode::T203),
// pyupgrade
"U001" => Ok(CheckCode::U001),
"U002" => Ok(CheckCode::U002),
"U003" => Ok(CheckCode::U003),
"U004" => Ok(CheckCode::U004),
"U005" => Ok(CheckCode::U005),
// Meta
"M001" => Ok(CheckCode::M001),
_ => Err(anyhow::anyhow!("Unknown check code: {s}")),
}
}
}
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-comprehensions
CheckCode::C400 => "C400",
CheckCode::C401 => "C401",
CheckCode::C403 => "C403",
CheckCode::C404 => "C404",
// flake8-super
CheckCode::SPR001 => "SPR001",
// flake8-print
CheckCode::T201 => "T201",
CheckCode::T203 => "T203",
// pyupgrade
CheckCode::U001 => "U001",
CheckCode::U002 => "U002",
CheckCode::U003 => "U003",
CheckCode::U004 => "U004",
CheckCode::U005 => "U005",
// 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 {
CheckCode::E501 | CheckCode::M001 => &LintSource::Lines,
CheckCode::E902 => &LintSource::FileSystem,
_ => &LintSource::AST,
}
}
/// A placeholder representation of the CheckKind for the check.
pub fn kind(&self) -> CheckKind {
match self {
// pycodestyle
CheckCode::E402 => CheckKind::ModuleImportNotAtTopOfFile,
CheckCode::E501 => CheckKind::LineTooLong(89, 88),
CheckCode::E711 => CheckKind::NoneComparison(RejectedCmpop::Eq),
CheckCode::E712 => CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq),
CheckCode::E713 => CheckKind::NotInTest,
CheckCode::E714 => CheckKind::NotIsTest,
CheckCode::E721 => CheckKind::TypeComparison,
CheckCode::E722 => CheckKind::DoNotUseBareExcept,
CheckCode::E731 => CheckKind::DoNotAssignLambda,
CheckCode::E741 => CheckKind::AmbiguousVariableName("...".to_string()),
CheckCode::E742 => CheckKind::AmbiguousClassName("...".to_string()),
CheckCode::E743 => CheckKind::AmbiguousFunctionName("...".to_string()),
CheckCode::E902 => CheckKind::IOError("IOError: `...`".to_string()),
CheckCode::E999 => CheckKind::SyntaxError("`...`".to_string()),
// pyflakes
CheckCode::F401 => CheckKind::UnusedImport(vec!["...".to_string()]),
CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1),
CheckCode::F403 => CheckKind::ImportStarUsed("...".to_string()),
CheckCode::F404 => CheckKind::LateFutureImport,
CheckCode::F405 => {
CheckKind::ImportStarUsage("...".to_string(), vec!["...".to_string()])
}
CheckCode::F406 => CheckKind::ImportStarNotPermitted("...".to_string()),
CheckCode::F407 => CheckKind::FutureFeatureNotDefined("...".to_string()),
CheckCode::F541 => CheckKind::FStringMissingPlaceholders,
CheckCode::F601 => CheckKind::MultiValueRepeatedKeyLiteral,
CheckCode::F602 => CheckKind::MultiValueRepeatedKeyVariable("...".to_string()),
CheckCode::F621 => CheckKind::ExpressionsInStarAssignment,
CheckCode::F622 => CheckKind::TwoStarredExpressions,
CheckCode::F631 => CheckKind::AssertTuple,
CheckCode::F632 => CheckKind::IsLiteral,
CheckCode::F633 => CheckKind::InvalidPrintSyntax,
CheckCode::F634 => CheckKind::IfTuple,
CheckCode::F701 => CheckKind::BreakOutsideLoop,
CheckCode::F702 => CheckKind::ContinueOutsideLoop,
CheckCode::F704 => CheckKind::YieldOutsideFunction,
CheckCode::F706 => CheckKind::ReturnOutsideFunction,
CheckCode::F707 => CheckKind::DefaultExceptNotLast,
CheckCode::F722 => CheckKind::ForwardAnnotationSyntaxError("...".to_string()),
CheckCode::F821 => CheckKind::UndefinedName("...".to_string()),
CheckCode::F822 => CheckKind::UndefinedExport("...".to_string()),
CheckCode::F823 => CheckKind::UndefinedLocal("...".to_string()),
CheckCode::F831 => CheckKind::DuplicateArgumentName,
CheckCode::F841 => CheckKind::UnusedVariable("...".to_string()),
CheckCode::F901 => CheckKind::RaiseNotImplemented,
// flake8-builtins
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::C403 => CheckKind::UnnecessaryListComprehensionSet,
CheckCode::C404 => CheckKind::UnnecessaryListComprehensionDict,
// flake8-super
CheckCode::SPR001 => CheckKind::SuperCallWithParameters,
// flake8-print
CheckCode::T201 => CheckKind::PrintFound,
CheckCode::T203 => CheckKind::PPrintFound,
// pyupgrade
CheckCode::U001 => CheckKind::UselessMetaclassType,
CheckCode::U002 => CheckKind::UnnecessaryAbspath,
CheckCode::U003 => CheckKind::TypeOfPrimitive(Primitive::Str),
CheckCode::U004 => CheckKind::UselessObjectInheritance("...".to_string()),
CheckCode::U005 => CheckKind::NoAssertEquals,
// Meta
CheckCode::M001 => CheckKind::UnusedNOQA(None),
}
}
}
#[allow(clippy::upper_case_acronyms)]
pub enum LintSource {
AST,
@@ -437,8 +166,9 @@ pub enum RejectedCmpop {
NotEq,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(AsRefStr, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CheckKind {
// pycodestyle errors
AmbiguousClassName(String),
AmbiguousFunctionName(String),
AmbiguousVariableName(String),
@@ -481,17 +211,28 @@ pub enum CheckKind {
UnusedImport(Vec<String>),
UnusedVariable(String),
YieldOutsideFunction,
// pycodestyle warnings
NoNewLineAtEndOfFile,
// flake8-builtin
BuiltinVariableShadowing(String),
BuiltinArgumentShadowing(String),
BuiltinAttributeShadowing(String),
// flakes8-comprehensions
// flake8-bugbear
DoNotAssertFalse,
DuplicateHandlerException(String),
DuplicateTryBlockException(String),
// flake8-comprehensions
UnnecessaryGeneratorList,
UnnecessaryGeneratorSet,
UnnecessaryGeneratorDict,
UnnecessaryListComprehensionSet,
UnnecessaryListComprehensionDict,
// flake8-super
SuperCallWithParameters,
UnnecessaryLiteralSet(String),
UnnecessaryLiteralDict(String),
UnnecessaryCollectionCall(String),
UnnecessaryLiteralWithinTupleCall(String),
UnnecessaryLiteralWithinListCall(String),
UnnecessarySubscriptReversal(String),
// flake8-print
PrintFound,
PPrintFound,
@@ -499,86 +240,130 @@ pub enum CheckKind {
TypeOfPrimitive(Primitive),
UnnecessaryAbspath,
UselessMetaclassType,
NoAssertEquals,
DeprecatedUnittestAlias(String, String),
UselessObjectInheritance(String),
UsePEP585Annotation(String),
UsePEP604Annotation,
SuperCallWithParameters,
// Meta
UnusedNOQA(Option<String>),
UnusedNOQA(Option<Vec<String>>),
}
impl CheckKind {
/// The name of the check.
pub fn name(&self) -> &'static str {
impl CheckCode {
/// The source for the check (either the AST, the filesystem, or the physical lines).
pub fn lint_source(&self) -> &'static LintSource {
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-comprehensions
CheckKind::UnnecessaryGeneratorList => "UnnecessaryGeneratorList",
CheckKind::UnnecessaryGeneratorSet => "UnnecessaryGeneratorSet",
CheckKind::UnnecessaryListComprehensionSet => "UnnecessaryListComprehensionSet",
CheckKind::UnnecessaryListComprehensionDict => "UnnecessaryListComprehensionDict",
// flake8-super
CheckKind::SuperCallWithParameters => "SuperCallWithParameters",
// flake8-print
CheckKind::PrintFound => "PrintFound",
CheckKind::PPrintFound => "PPrintFound",
// pyupgrade
CheckKind::TypeOfPrimitive(_) => "TypeOfPrimitive",
CheckKind::UnnecessaryAbspath => "UnnecessaryAbspath",
CheckKind::UselessMetaclassType => "UselessMetaclassType",
CheckKind::NoAssertEquals => "NoAssertEquals",
CheckKind::UselessObjectInheritance(_) => "UselessObjectInheritance",
// Meta
CheckKind::UnusedNOQA(_) => "UnusedNOQA",
CheckCode::E501 | CheckCode::M001 => &LintSource::Lines,
CheckCode::E902 => &LintSource::FileSystem,
_ => &LintSource::AST,
}
}
/// A placeholder representation of the CheckKind for the check.
pub fn kind(&self) -> CheckKind {
match self {
// pycodestyle errors
CheckCode::E402 => CheckKind::ModuleImportNotAtTopOfFile,
CheckCode::E501 => CheckKind::LineTooLong(89, 88),
CheckCode::E711 => CheckKind::NoneComparison(RejectedCmpop::Eq),
CheckCode::E712 => CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq),
CheckCode::E713 => CheckKind::NotInTest,
CheckCode::E714 => CheckKind::NotIsTest,
CheckCode::E721 => CheckKind::TypeComparison,
CheckCode::E722 => CheckKind::DoNotUseBareExcept,
CheckCode::E731 => CheckKind::DoNotAssignLambda,
CheckCode::E741 => CheckKind::AmbiguousVariableName("...".to_string()),
CheckCode::E742 => CheckKind::AmbiguousClassName("...".to_string()),
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(vec!["...".to_string()]),
CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1),
CheckCode::F403 => CheckKind::ImportStarUsed("...".to_string()),
CheckCode::F404 => CheckKind::LateFutureImport,
CheckCode::F405 => {
CheckKind::ImportStarUsage("...".to_string(), vec!["...".to_string()])
}
CheckCode::F406 => CheckKind::ImportStarNotPermitted("...".to_string()),
CheckCode::F407 => CheckKind::FutureFeatureNotDefined("...".to_string()),
CheckCode::F541 => CheckKind::FStringMissingPlaceholders,
CheckCode::F601 => CheckKind::MultiValueRepeatedKeyLiteral,
CheckCode::F602 => CheckKind::MultiValueRepeatedKeyVariable("...".to_string()),
CheckCode::F621 => CheckKind::ExpressionsInStarAssignment,
CheckCode::F622 => CheckKind::TwoStarredExpressions,
CheckCode::F631 => CheckKind::AssertTuple,
CheckCode::F632 => CheckKind::IsLiteral,
CheckCode::F633 => CheckKind::InvalidPrintSyntax,
CheckCode::F634 => CheckKind::IfTuple,
CheckCode::F701 => CheckKind::BreakOutsideLoop,
CheckCode::F702 => CheckKind::ContinueOutsideLoop,
CheckCode::F704 => CheckKind::YieldOutsideFunction,
CheckCode::F706 => CheckKind::ReturnOutsideFunction,
CheckCode::F707 => CheckKind::DefaultExceptNotLast,
CheckCode::F722 => CheckKind::ForwardAnnotationSyntaxError("...".to_string()),
CheckCode::F821 => CheckKind::UndefinedName("...".to_string()),
CheckCode::F822 => CheckKind::UndefinedExport("...".to_string()),
CheckCode::F823 => CheckKind::UndefinedLocal("...".to_string()),
CheckCode::F831 => CheckKind::DuplicateArgumentName,
CheckCode::F841 => CheckKind::UnusedVariable("...".to_string()),
CheckCode::F901 => CheckKind::RaiseNotImplemented,
// flake8-builtins
CheckCode::A001 => CheckKind::BuiltinVariableShadowing("...".to_string()),
CheckCode::A002 => CheckKind::BuiltinArgumentShadowing("...".to_string()),
CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()),
// flake8-bugbear
CheckCode::B011 => CheckKind::DoNotAssertFalse,
CheckCode::B014 => CheckKind::DuplicateHandlerException("Exception".to_string()),
CheckCode::B025 => CheckKind::DuplicateTryBlockException("Exception".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::C409 => {
CheckKind::UnnecessaryLiteralWithinTupleCall("<list/tuple>".to_string())
}
CheckCode::C410 => {
CheckKind::UnnecessaryLiteralWithinListCall("<list/tuple>".to_string())
}
CheckCode::C415 => {
CheckKind::UnnecessarySubscriptReversal("<reversed/set/sorted>".to_string())
}
// flake8-print
CheckCode::T201 => CheckKind::PrintFound,
CheckCode::T203 => CheckKind::PPrintFound,
// pyupgrade
CheckCode::U001 => CheckKind::UselessMetaclassType,
CheckCode::U002 => CheckKind::UnnecessaryAbspath,
CheckCode::U003 => CheckKind::TypeOfPrimitive(Primitive::Str),
CheckCode::U004 => CheckKind::UselessObjectInheritance("...".to_string()),
CheckCode::U005 => CheckKind::DeprecatedUnittestAlias(
"assertEquals".to_string(),
"assertEqual".to_string(),
),
CheckCode::U006 => CheckKind::UsePEP585Annotation("List".to_string()),
CheckCode::U007 => CheckKind::UsePEP604Annotation,
CheckCode::U008 => CheckKind::SuperCallWithParameters,
// Meta
CheckCode::M001 => CheckKind::UnusedNOQA(None),
}
}
}
impl CheckKind {
/// 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,
@@ -621,17 +406,28 @@ 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-bugbear
CheckKind::DoNotAssertFalse => &CheckCode::B011,
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
CheckKind::DuplicateTryBlockException(_) => &CheckCode::B025,
// flake8-comprehensions
CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
CheckKind::UnnecessaryGeneratorSet => &CheckCode::C401,
CheckKind::UnnecessaryGeneratorDict => &CheckCode::C402,
CheckKind::UnnecessaryListComprehensionSet => &CheckCode::C403,
CheckKind::UnnecessaryListComprehensionDict => &CheckCode::C404,
// flake8-super
CheckKind::SuperCallWithParameters => &CheckCode::SPR001,
CheckKind::UnnecessaryLiteralSet(_) => &CheckCode::C405,
CheckKind::UnnecessaryLiteralDict(_) => &CheckCode::C406,
CheckKind::UnnecessaryCollectionCall(_) => &CheckCode::C408,
CheckKind::UnnecessaryLiteralWithinTupleCall(..) => &CheckCode::C409,
CheckKind::UnnecessaryLiteralWithinListCall(..) => &CheckCode::C410,
CheckKind::UnnecessarySubscriptReversal(_) => &CheckCode::C415,
// flake8-print
CheckKind::PrintFound => &CheckCode::T201,
CheckKind::PPrintFound => &CheckCode::T203,
@@ -639,8 +435,11 @@ impl CheckKind {
CheckKind::TypeOfPrimitive(_) => &CheckCode::U003,
CheckKind::UnnecessaryAbspath => &CheckCode::U002,
CheckKind::UselessMetaclassType => &CheckCode::U001,
CheckKind::NoAssertEquals => &CheckCode::U005,
CheckKind::DeprecatedUnittestAlias(_, _) => &CheckCode::U005,
CheckKind::UsePEP585Annotation(_) => &CheckCode::U006,
CheckKind::UsePEP604Annotation => &CheckCode::U007,
CheckKind::UselessObjectInheritance(_) => &CheckCode::U004,
CheckKind::SuperCallWithParameters => &CheckCode::U008,
// Meta
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
}
@@ -649,6 +448,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)
}
@@ -776,7 +576,8 @@ impl CheckKind {
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")
@@ -787,6 +588,17 @@ impl CheckKind {
CheckKind::BuiltinAttributeShadowing(name) => {
format!("Class attribute `{name}` is shadowing a python builtin")
}
// flake8-bugbear
CheckKind::DoNotAssertFalse => {
"Do not `assert False` (`python -O` removes these calls), raise `AssertionError()`"
.to_string()
}
CheckKind::DuplicateHandlerException(name) => {
format!("Exception handler with duplicate exception `{name}`")
}
CheckKind::DuplicateTryBlockException(name) => {
format!("try-except block with duplicate exception `{name}`")
}
// flake8-comprehensions
CheckKind::UnnecessaryGeneratorList => {
"Unnecessary generator - rewrite as a list comprehension".to_string()
@@ -794,15 +606,48 @@ impl CheckKind {
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()
}
// flake8-super
CheckKind::SuperCallWithParameters => {
"Use `super()` instead of `super(__class__, self)`".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::UnnecessaryLiteralWithinTupleCall(literal) => {
if literal == "list" {
format!(
"Unnecessary {literal} literal passed to tuple() - rewrite as a tuple literal"
)
} else {
format!(
"Unnecessary {literal} literal passed to tuple() - remove the outer call to tuple()"
)
}
}
CheckKind::UnnecessaryLiteralWithinListCall(literal) => {
if literal == "list" {
format!(
"Unnecessary {literal} literal passed to list() - remove the outer call to list()"
)
} else {
format!(
"Unnecessary {literal} literal passed to list() - rewrite as a list literal"
)
}
}
CheckKind::UnnecessarySubscriptReversal(func) => {
format!("Unnecessary subscript reversal of iterable within {func}()")
}
// flake8-print
CheckKind::PrintFound => "`print` found".to_string(),
@@ -815,16 +660,39 @@ impl CheckKind {
"`abspath(__file__)` is unnecessary in Python 3.9 and later".to_string()
}
CheckKind::UselessMetaclassType => "`__metaclass__ = type` is implied".to_string(),
CheckKind::NoAssertEquals => {
"`assertEquals` is deprecated, use `assertEqual` instead".to_string()
CheckKind::DeprecatedUnittestAlias(alias, target) => {
format!("`{}` is deprecated, use `{}` instead", alias, target)
}
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(),
CheckKind::SuperCallWithParameters => {
"Use `super()` instead of `super(__class__, self)`".to_string()
}
// Meta
CheckKind::UnusedNOQA(code) => match code {
CheckKind::UnusedNOQA(codes) => match codes {
None => "Unused `noqa` directive".to_string(),
Some(code) => format!("Unused `noqa` directive for: {code}"),
Some(codes) => {
let codes = codes
.iter()
.map(|code| {
if CheckCode::from_str(code).is_ok() {
code.to_string()
} else {
format!("{code} (not implemented)")
}
})
.join(", ");
format!("Unused `noqa` directive for: {codes}")
}
},
}
}
@@ -833,7 +701,8 @@ impl CheckKind {
pub fn fixable(&self) -> bool {
matches!(
self,
CheckKind::NoAssertEquals
CheckKind::DeprecatedUnittestAlias(_, _)
| CheckKind::DoNotAssertFalse
| CheckKind::PPrintFound
| CheckKind::PrintFound
| CheckKind::SuperCallWithParameters
@@ -843,6 +712,8 @@ impl CheckKind {
| CheckKind::UnusedNOQA(_)
| CheckKind::UselessMetaclassType
| CheckKind::UselessObjectInheritance(_)
| CheckKind::UsePEP585Annotation(_)
| CheckKind::UsePEP604Annotation
)
}
}
@@ -877,3 +748,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(())
}
}

View File

@@ -115,7 +115,7 @@ impl SourceGenerator {
Ok(())
}
fn unparse_stmt<U>(&mut self, ast: &Stmt<U>) -> fmt::Result {
pub fn unparse_stmt<U>(&mut self, ast: &Stmt<U>) -> fmt::Result {
macro_rules! statement {
($body:block) => {{
self.newline()?;
@@ -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 {

View File

@@ -340,9 +340,93 @@ mod tests {
}
#[test]
fn f401() -> Result<()> {
fn w292_0() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/F401.py"),
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_0() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/F401_0.py"),
&settings::Settings::for_rule(CheckCode::F401),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn f401_1() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/F401_1.py"),
&settings::Settings::for_rule(CheckCode::F401),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn f401_2() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/F401_2.py"),
&settings::Settings::for_rule(CheckCode::F401),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn f401_3() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/F401_3.py"),
&settings::Settings::for_rule(CheckCode::F401),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn f401_4() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/F401_4.py"),
&settings::Settings::for_rule(CheckCode::F401),
&fixer::Mode::Generate,
)?;
@@ -678,42 +762,6 @@ mod tests {
Ok(())
}
#[test]
fn m001() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/M001.py"),
&settings::Settings::for_rules(vec![CheckCode::M001, CheckCode::E501, CheckCode::F841]),
&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(
Path::new("./resources/test/fixtures/__init__.py"),
&settings::Settings::for_rules(vec![CheckCode::F821, CheckCode::F822]),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn future_annotations() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/future_annotations.py"),
&settings::Settings::for_rules(vec![CheckCode::F401, CheckCode::F821]),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn e999() -> Result<()> {
let mut checks = check_path(
@@ -762,6 +810,42 @@ mod tests {
Ok(())
}
#[test]
fn b011() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/B011.py"),
&settings::Settings::for_rule(CheckCode::B011),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn b014() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/B014.py"),
&settings::Settings::for_rule(CheckCode::B014),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn b025() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/B025.py"),
&settings::Settings::for_rule(CheckCode::B025),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn c400() -> Result<()> {
let mut checks = check_path(
@@ -786,6 +870,18 @@ mod tests {
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(
@@ -811,10 +907,82 @@ mod tests {
}
#[test]
fn spr001() -> Result<()> {
fn c405() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/SPR001.py"),
&settings::Settings::for_rule(CheckCode::SPR001),
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 c409() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/C409.py"),
&settings::Settings::for_rule(CheckCode::C409),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn c410() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/C410.py"),
&settings::Settings::for_rule(CheckCode::C410),
&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 u008() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U008.py"),
&settings::Settings::for_rule(CheckCode::U008),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
@@ -905,4 +1073,64 @@ mod tests {
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(())
}
#[test]
fn m001() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/M001.py"),
&settings::Settings::for_rules(vec![CheckCode::M001, CheckCode::E501, CheckCode::F841]),
&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(
Path::new("./resources/test/fixtures/__init__.py"),
&settings::Settings::for_rules(vec![CheckCode::F821, CheckCode::F822]),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn future_annotations() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/future_annotations.py"),
&settings::Settings::for_rules(vec![CheckCode::F401, CheckCode::F821]),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
}

View File

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

View File

@@ -125,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');
@@ -150,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};

View File

@@ -1,21 +1,29 @@
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 useless_metaclass_type;
mod useless_object_inheritance;
pub use assert_equals::assert_equals;
pub use assert_false::assert_false;
pub use assert_tuple::assert_tuple;
pub use deprecated_unittest_alias::deprecated_unittest_alias;
pub use duplicate_exceptions::duplicate_exceptions;
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_false;
mod assert_tuple;
mod deprecated_unittest_alias;
mod duplicate_exceptions;
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;

View File

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

View File

@@ -0,0 +1,61 @@
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Stmt, StmtKind};
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 assertion_error(msg: &Option<Box<Expr>>) -> Stmt {
Stmt::new(
Default::default(),
Default::default(),
StmtKind::Raise {
exc: Some(Box::new(Expr::new(
Default::default(),
Default::default(),
ExprKind::Call {
func: Box::new(Expr::new(
Default::default(),
Default::default(),
ExprKind::Name {
id: "AssertionError".to_string(),
ctx: ExprContext::Load,
},
)),
args: if let Some(msg) = msg {
vec![*msg.clone()]
} else {
vec![]
},
keywords: vec![],
},
))),
cause: None,
},
)
}
pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: &Option<Box<Expr>>) {
if let ExprKind::Constant {
value: Constant::Bool(false),
..
} = &test.node
{
let mut check = Check::new(CheckKind::DoNotAssertFalse, Range::from_located(test));
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_stmt(&assertion_error(msg)) {
if let Ok(content) = generator.generate() {
check.amend(Fix {
content,
location: stmt.location,
end_location: stmt.end_location,
applied: false,
})
}
}
}
checker.add_check(check);
}
}

View File

@@ -0,0 +1,56 @@
use std::collections::BTreeMap;
use once_cell::sync::Lazy;
use rustpython_ast::{Expr, ExprKind, Location};
use crate::ast::types::Range;
use crate::autofix::fixer;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind, Fix};
static DEPRECATED_ALIASES: Lazy<BTreeMap<&'static str, &'static str>> = Lazy::new(|| {
BTreeMap::from([
("failUnlessEqual", "assertEqual"),
("assertEquals", "assertEqual"),
("failIfEqual", "assertNotEqual"),
("assertNotEquals", "assertNotEqual"),
("failUnless", "assertTrue"),
("assert_", "assertTrue"),
("failIf", "assertFalse"),
("failUnlessRaises", "assertRaises"),
("failUnlessAlmostEqual", "assertAlmostEqual"),
("assertAlmostEquals", "assertAlmostEqual"),
("failIfAlmostEqual", "assertNotAlmostEqual"),
("assertNotAlmostEquals", "assertNotAlmostEqual"),
("assertRegexpMatches", "assertRegex"),
("assertNotRegexpMatches", "assertNotRegex"),
("assertRaisesRegexp", "assertRaisesRegex"),
])
});
pub fn deprecated_unittest_alias(checker: &mut Checker, expr: &Expr) {
if let ExprKind::Attribute { value, attr, .. } = &expr.node {
if let Some(target) = DEPRECATED_ALIASES.get(attr.as_str()) {
if let ExprKind::Name { id, .. } = &value.node {
if id == "self" {
let mut check = Check::new(
CheckKind::DeprecatedUnittestAlias(attr.to_string(), target.to_string()),
Range::from_located(expr),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix {
content: format!("self.{}", target),
location: Location::new(expr.location.row(), expr.location.column()),
end_location: Location::new(
expr.end_location.row(),
expr.end_location.column(),
),
applied: false,
});
}
checker.add_check(check);
}
}
}
}
}

View File

@@ -0,0 +1,82 @@
use itertools::Itertools;
use std::collections::BTreeSet;
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, Stmt};
use crate::ast::helpers;
use crate::ast::types::{CheckLocator, Range};
use crate::check_ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
pub fn duplicate_handler_exceptions(
checker: &mut Checker,
stmt: &Stmt,
elts: &Vec<Expr>,
) -> BTreeSet<String> {
let mut seen: BTreeSet<String> = Default::default();
let mut duplicates: BTreeSet<String> = Default::default();
for type_ in elts {
if let Some(name) = helpers::compose_call_path(type_) {
if seen.contains(&name) {
duplicates.insert(name);
} else {
seen.insert(name);
}
}
}
if checker.settings.enabled.contains(&CheckCode::B014) {
// TODO(charlie): Handle "BaseException" and redundant exception aliases.
for duplicate in duplicates.into_iter().sorted() {
checker.add_check(Check::new(
CheckKind::DuplicateHandlerException(duplicate),
checker.locate_check(Range::from_located(stmt)),
));
}
}
seen
}
pub fn duplicate_exceptions(checker: &mut Checker, stmt: &Stmt, handlers: &[Excepthandler]) {
let mut seen: BTreeSet<String> = Default::default();
let mut duplicates: BTreeSet<String> = Default::default();
for handler in handlers {
match &handler.node {
ExcepthandlerKind::ExceptHandler { type_, .. } => {
if let Some(type_) = type_ {
match &type_.node {
ExprKind::Attribute { .. } | ExprKind::Name { .. } => {
if let Some(name) = helpers::compose_call_path(type_) {
if seen.contains(&name) {
duplicates.insert(name);
} else {
seen.insert(name);
}
}
}
ExprKind::Tuple { elts, .. } => {
for name in duplicate_handler_exceptions(checker, stmt, elts) {
if seen.contains(&name) {
duplicates.insert(name);
} else {
seen.insert(name);
}
}
}
_ => {}
}
}
}
}
}
if checker.settings.enabled.contains(&CheckCode::B025) {
for duplicate in duplicates.into_iter().sorted() {
checker.add_check(Check::new(
CheckKind::DuplicateTryBlockException(duplicate),
checker.locate_check(Range::from_located(stmt)),
));
}
}
}

View File

@@ -1,6 +1,6 @@
use rustpython_ast::{Expr, Stmt};
use crate::ast::checks;
use crate::ast::{checks, helpers};
use crate::autofix::{fixer, fixes};
use crate::check_ast::Checker;
@@ -12,7 +12,7 @@ pub fn super_call_with_parameters(
) {
// 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) {
if helpers::is_super_call_with_arguments(func, args) {
let scope = checker.current_scope();
let parents: Vec<&Stmt> = checker
.parent_stack

View File

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

View File

@@ -0,0 +1,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);
}
}

View File

@@ -154,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#""#)?;

View File

@@ -92,3 +92,10 @@ pub fn is_annotated_subscript(name: &str) -> bool {
pub fn is_pep593_annotated_subscript(name: &str) -> bool {
name == "Annotated"
}
static PEP_585_BUILTINS: Lazy<BTreeSet<&'static str>> =
Lazy::new(|| BTreeSet::from(["Dict", "FrozenSet", "List", "Set", "Tuple", "Type"]));
pub fn is_pep585_builtin(name: &str) -> bool {
PEP_585_BUILTINS.contains(name)
}

View File

@@ -0,0 +1,37 @@
---
source: src/linter.rs
expression: checks
---
- kind: DoNotAssertFalse
location:
row: 8
column: 8
end_location:
row: 8
column: 13
fix:
content: raise AssertionError()
location:
row: 8
column: 1
end_location:
row: 8
column: 13
applied: false
- kind: DoNotAssertFalse
location:
row: 10
column: 8
end_location:
row: 10
column: 13
fix:
content: "raise AssertionError(\"message\")"
location:
row: 10
column: 1
end_location:
row: 10
column: 24
applied: false

View File

@@ -0,0 +1,32 @@
---
source: src/linter.rs
expression: checks
---
- kind:
DuplicateHandlerException: OSError
location:
row: 15
column: 1
end_location:
row: 22
column: 1
fix: ~
- kind:
DuplicateHandlerException: MyError
location:
row: 26
column: 1
end_location:
row: 33
column: 1
fix: ~
- kind:
DuplicateHandlerException: re.error
location:
row: 47
column: 1
end_location:
row: 54
column: 1
fix: ~

View File

@@ -0,0 +1,41 @@
---
source: src/linter.rs
expression: checks
---
- kind:
DuplicateTryBlockException: ValueError
location:
row: 15
column: 1
end_location:
row: 22
column: 1
fix: ~
- kind:
DuplicateTryBlockException: pickle.PickleError
location:
row: 22
column: 1
end_location:
row: 31
column: 1
fix: ~
- kind:
DuplicateTryBlockException: TypeError
location:
row: 31
column: 1
end_location:
row: 39
column: 1
fix: ~
- kind:
DuplicateTryBlockException: ValueError
location:
row: 31
column: 1
end_location:
row: 39
column: 1
fix: ~

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,32 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UnnecessaryLiteralWithinTupleCall: list
location:
row: 1
column: 6
end_location:
row: 1
column: 19
fix: ~
- kind:
UnnecessaryLiteralWithinTupleCall: tuple
location:
row: 2
column: 6
end_location:
row: 2
column: 19
fix: ~
- kind:
UnnecessaryLiteralWithinTupleCall: list
location:
row: 3
column: 6
end_location:
row: 3
column: 15
fix: ~

View File

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

View File

@@ -0,0 +1,41 @@
---
source: src/linter.rs
expression: checks
---
- kind:
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: ~

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -45,7 +45,7 @@ expression: checks
column: 5
end_location:
row: 58
column: 9
column: 8
fix: ~
- kind:
UndefinedName: TOMATO
@@ -81,7 +81,7 @@ expression: checks
column: 10
end_location:
row: 114
column: 24
column: 23
fix: ~
- kind:
UndefinedName: foo
@@ -90,7 +90,7 @@ expression: checks
column: 15
end_location:
row: 122
column: 19
column: 18
fix: ~
- kind:
UndefinedName: bar
@@ -99,6 +99,6 @@ expression: checks
column: 22
end_location:
row: 122
column: 26
column: 25
fix: ~

View File

@@ -20,7 +20,8 @@ expression: checks
column: 18
applied: false
- kind:
UnusedNOQA: E501
UnusedNOQA:
- E501
location:
row: 13
column: 10
@@ -37,7 +38,9 @@ expression: checks
column: 24
applied: false
- kind:
UnusedNOQA: E501
UnusedNOQA:
- F841
- E501
location:
row: 16
column: 10
@@ -45,7 +48,7 @@ expression: checks
row: 16
column: 30
fix:
content: " # noqa: F841"
content: ""
location:
row: 16
column: 10
@@ -54,54 +57,74 @@ expression: checks
column: 30
applied: false
- kind:
UnusedNOQA: F841
UnusedNOQA:
- W191
location:
row: 41
row: 19
column: 10
end_location:
row: 19
column: 30
fix:
content: " # noqa: F841"
location:
row: 19
column: 10
end_location:
row: 19
column: 30
applied: false
- kind:
UnusedNOQA:
- F841
location:
row: 44
column: 4
end_location:
row: 41
row: 44
column: 24
fix:
content: " # noqa: E501"
location:
row: 41
row: 44
column: 4
end_location:
row: 41
row: 44
column: 24
applied: false
- kind:
UnusedNOQA: E501
UnusedNOQA:
- E501
location:
row: 49
row: 52
column: 4
end_location:
row: 49
row: 52
column: 18
fix:
content: ""
location:
row: 49
row: 52
column: 4
end_location:
row: 49
row: 52
column: 18
applied: false
- kind:
UnusedNOQA: ~
location:
row: 57
row: 60
column: 4
end_location:
row: 57
row: 60
column: 12
fix:
content: ""
location:
row: 57
row: 60
column: 4
end_location:
row: 57
row: 60
column: 12
applied: false

View File

@@ -2,36 +2,80 @@
source: src/linter.rs
expression: checks
---
- kind: NoAssertEquals
- kind:
DeprecatedUnittestAlias:
- assertEquals
- assertEqual
location:
row: 1
column: 1
row: 6
column: 9
end_location:
row: 1
column: 18
row: 6
column: 26
fix:
content: assertEqual
content: self.assertEqual
location:
row: 1
column: 2
row: 6
column: 9
end_location:
row: 1
column: 14
row: 6
column: 26
applied: false
- kind: NoAssertEquals
- kind:
DeprecatedUnittestAlias:
- assertEquals
- assertEqual
location:
row: 2
column: 1
row: 7
column: 9
end_location:
row: 2
column: 18
row: 7
column: 26
fix:
content: assertEqual
content: self.assertEqual
location:
row: 2
column: 2
row: 7
column: 9
end_location:
row: 2
column: 14
row: 7
column: 26
applied: false
- kind:
DeprecatedUnittestAlias:
- failUnlessAlmostEqual
- assertAlmostEqual
location:
row: 9
column: 9
end_location:
row: 9
column: 35
fix:
content: self.assertAlmostEqual
location:
row: 9
column: 9
end_location:
row: 9
column: 35
applied: false
- kind:
DeprecatedUnittestAlias:
- assertNotRegexpMatches
- assertNotRegex
location:
row: 10
column: 9
end_location:
row: 10
column: 36
fix:
content: self.assertNotRegex
location:
row: 10
column: 9
end_location:
row: 10
column: 36
applied: false

View File

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

View File

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

View File

@@ -0,0 +1,85 @@
---
source: src/linter.rs
expression: checks
---
- kind: SuperCallWithParameters
location:
row: 17
column: 18
end_location:
row: 17
column: 36
fix:
content: super()
location:
row: 17
column: 18
end_location:
row: 17
column: 36
applied: false
- kind: SuperCallWithParameters
location:
row: 18
column: 9
end_location:
row: 18
column: 27
fix:
content: super()
location:
row: 18
column: 9
end_location:
row: 18
column: 27
applied: false
- kind: SuperCallWithParameters
location:
row: 19
column: 9
end_location:
row: 22
column: 10
fix:
content: super()
location:
row: 19
column: 9
end_location:
row: 22
column: 10
applied: false
- kind: SuperCallWithParameters
location:
row: 36
column: 9
end_location:
row: 36
column: 29
fix:
content: super()
location:
row: 36
column: 9
end_location:
row: 36
column: 29
applied: false
- kind: SuperCallWithParameters
location:
row: 50
column: 13
end_location:
row: 50
column: 33
fix:
content: super()
location:
row: 50
column: 13
end_location:
row: 50
column: 33
applied: false

View File

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

View File

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

View File

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

View File

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