Compare commits

...

38 Commits

Author SHA1 Message Date
Charlie Marsh
a2df89dedd Bump version to 0.0.38 2022-09-14 22:38:42 -04:00
Charlie Marsh
b8f12d2e79 Raise error when failing to parse (#199) 2022-09-14 22:37:55 -04:00
Charlie Marsh
67b1d0463a Pull in pycodestyle tests for E checks (#195) 2022-09-14 22:22:53 -04:00
Charlie Marsh
d008a181ec Improve default exclusions and support extend-exclude (#188) 2022-09-14 22:21:17 -04:00
Charlie Marsh
6d612a428a Migrate linter tests to insta (#194) 2022-09-14 21:52:44 -04:00
Charlie Marsh
c0cb73ab16 Implement E721 (#193) 2022-09-14 21:10:29 -04:00
Charlie Marsh
2e1eb84cbf Implement F632 (#190) 2022-09-14 18:22:35 -04:00
Charlie Marsh
b03a8728b5 Add support for from __future__ import annotations (#189) 2022-09-14 18:22:19 -04:00
Charlie Marsh
1dd3350a30 Revert "Adding flag and logic for different output format" (#187) 2022-09-14 14:20:02 -04:00
Patrick Haller
bda34945a5 Adding flag and logic for different output format (#185) 2022-09-14 10:43:32 -04:00
Dmitry Dygalo
85dcaa8d3c chore: Avoid collect in inner_main (#184) 2022-09-14 08:16:04 -04:00
Charlie Marsh
4ac74ed0ad Revert erroneous pyproject.toml changes 2022-09-14 08:14:26 -04:00
Dmitry Dygalo
b7e2a4b9a9 feat: Implement InvalidPrintSyntax (F633) (#182) 2022-09-13 21:10:20 -04:00
Dmitry Dygalo
53a7758248 Avoid some allocations (#179) 2022-09-13 10:07:22 -04:00
Dmitry Dygalo
2ba767957d refactor: Use while let Some instead of calling is_empty (#180) 2022-09-13 10:06:49 -04:00
Dmitry Dygalo
08152787e1 chore: Use once_cell instead of lazy_static (#178) 2022-09-13 10:06:21 -04:00
Charlie Marsh
5f77b420cd Bump version to 0.0.37 2022-09-12 21:35:08 -04:00
Charlie Marsh
90f9e60517 Implement F722 (#175) 2022-09-12 21:34:27 -04:00
Brian Okken
320737f6e4 Change URL to comply with PEP 621 (#173) 2022-09-12 18:38:20 -04:00
Charlie Marsh
dfba1416b2 Implement F406 (#172) 2022-09-12 16:47:30 -04:00
Charlie Marsh
2ca3f35bd1 Remove one match from checks.rs 2022-09-12 16:32:27 -04:00
Adrian Garcia Badaracco
b1c40d5fa7 Run MacOS builds in parallel (#171) 2022-09-12 16:24:13 -04:00
Charlie Marsh
a129e27b3e Tweak rule counts 2022-09-12 15:26:21 -04:00
Charlie Marsh
ad7daa008e Update README to enumerate missing Flake8 rules 2022-09-12 15:24:32 -04:00
Charlie Marsh
062d7081a0 Bump version to 0.0.36 2022-09-12 11:16:26 -04:00
Charlie Marsh
40c1e7e005 Implement F701 and F702 (#169) 2022-09-12 11:16:08 -04:00
Charlie Marsh
3cbd05ddff Update README 2022-09-12 09:31:16 -04:00
Harutaka Kawamura
9414090617 Implement E722 (#166) 2022-09-12 09:04:39 -04:00
Harutaka Kawamura
825777edc1 Add test case for assignment expression in E741.py (#168) 2022-09-12 09:03:36 -04:00
Charlie Marsh
4e0807e908 Include line length in E501 messages (#165) 2022-09-11 22:54:11 -04:00
Charlie Marsh
546be5692a Bump version to 0.0.35 2022-09-11 21:54:00 -04:00
Charlie Marsh
43e1f20b28 Allow unused assignments in for loops and unpacking (#163) 2022-09-11 21:53:45 -04:00
Harutaka Kawamura
97388cefda Implement E743 (#162) 2022-09-11 21:27:33 -04:00
Harutaka Kawamura
63ce579989 Implement E742 (#160) 2022-09-11 20:27:48 -04:00
Charlie Marsh
5f4a62aa40 Bump version to 0.0.34 2022-09-11 18:05:52 -04:00
Charlie Marsh
02ab52b3e2 Implement F407 (#158) 2022-09-11 18:05:28 -04:00
Charlie Marsh
549732b1da Implement F404 (#159) 2022-09-11 18:05:00 -04:00
Harutaka Kawamura
c4565fe0f5 Implement E741 (#137) 2022-09-11 12:30:28 -04:00
83 changed files with 3224 additions and 1014 deletions

View File

@@ -16,7 +16,7 @@ env:
PYTHON_VERSION: "3.7" # to build abi3 wheels
jobs:
macos:
macos-x86_64:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
@@ -39,6 +39,25 @@ jobs:
- name: Install built wheel - x86_64
run: |
pip install dist/${{ env.PACKAGE_NAME }}-*.whl --force-reinstall
- name: Upload wheels
uses: actions/upload-artifact@v2
with:
name: wheels
path: dist
macos-universal:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
default: true
- name: Build wheels - universal2
uses: messense/maturin-action@v1
with:
@@ -261,7 +280,8 @@ jobs:
name: Release
runs-on: ubuntu-latest
needs:
- macos
- macos-universal
- macos-x86_64
- windows
- linux
- linux-cross

View File

@@ -1,5 +1,5 @@
repos:
- repo: https://github.com/charliermarsh/ruff
rev: v0.0.33
rev: v0.0.38
hooks:
- id: lint

68
Cargo.lock generated
View File

@@ -458,6 +458,19 @@ dependencies = [
"cache-padded",
]
[[package]]
name = "console"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847"
dependencies = [
"encode_unicode",
"libc",
"once_cell",
"terminal_size",
"winapi 0.3.9",
]
[[package]]
name = "core-foundation"
version = "0.9.3"
@@ -652,6 +665,12 @@ dependencies = [
"log",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "event-listener"
version = "2.5.3"
@@ -1011,6 +1030,20 @@ dependencies = [
"libc",
]
[[package]]
name = "insta"
version = "1.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc61e98be01e89296f3343a878e9f8ca75a494cb5aaf29df65ef55734aeb85f5"
dependencies = [
"console",
"linked-hash-map",
"once_cell",
"serde",
"similar",
"yaml-rust",
]
[[package]]
name = "instant"
version = "0.1.12"
@@ -1122,6 +1155,12 @@ version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b"
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "lock_api"
version = "0.4.7"
@@ -1744,7 +1783,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.33"
version = "0.0.38"
dependencies = [
"anyhow",
"bincode",
@@ -1758,8 +1797,8 @@ dependencies = [
"fern",
"filetime",
"glob",
"insta",
"itertools",
"lazy_static",
"log",
"notify",
"once_cell",
@@ -1978,6 +2017,12 @@ dependencies = [
"libc",
]
[[package]]
name = "similar"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803"
[[package]]
name = "siphasher"
version = "0.3.10"
@@ -2100,6 +2145,16 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "terminal_size"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc",
"winapi 0.3.9",
]
[[package]]
name = "terminfo"
version = "0.7.3"
@@ -2595,3 +2650,12 @@ dependencies = [
"winapi 0.2.8",
"winapi-build",
]
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.33"
version = "0.0.38"
edition = "2021"
[lib]
@@ -18,9 +18,8 @@ common-path = { version = "1.0.0" }
dirs = { version = "4.0.0" }
fern = { version = "0.6.1" }
filetime = { version = "0.2.17" }
glob = { version = "0.3.0" }
glob = "0.3.0"
itertools = "0.10.3"
lazy_static = "1.4.0"
log = { version = "0.4.17" }
notify = { version = "4.0.17" }
once_cell = { version = "1.13.1" }
@@ -33,6 +32,9 @@ toml = { version = "0.5.9" }
update-informer = { version = "0.5.0", default_features = false, features = ["pypi"], optional = true }
walkdir = { version = "2.3.2" }
[dev-dependencies]
insta = { version = "1.19.1", features = ["yaml"] }
[features]
default = ["update-informer"]
update-informer = ["dep:update-informer"]
@@ -42,3 +44,9 @@ panic = "abort"
lto = "thin"
codegen-units = 1
opt-level = 3
[profile.dev.package.insta]
opt-level = 3
[profile.dev.package.similar]
opt-level = 3

View File

@@ -57,7 +57,7 @@ ruff also works with [Pre-Commit](https://pre-commit.com) (requires Cargo on sys
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff
rev: v0.0.33
rev: v0.0.38
hooks:
- id: lint
```
@@ -86,7 +86,7 @@ ruff path/to/code/ --select F401 F403
See `ruff --help` for more:
```shell
ruff (v0.0.33)
ruff (v0.0.38)
An extremely fast Python linter.
USAGE:
@@ -123,18 +123,15 @@ ruff's goal is to achieve feature-parity with Flake8 when used (1) without any p
(2) alongside Black, and (3) on Python 3 code. (Using Black obviates the need for many of Flake8's
stylistic checks; limiting to Python 3 obviates the need for certain compatibility checks.)
Under those conditions, Flake8 implements about 58 rules, give or take. At time of writing, ruff
implements 28 rules. (Note that these 28 rules likely cover a disproportionate share of errors:
Under those conditions, Flake8 implements about 60 rules, give or take. At time of writing, ruff
implements 42 rules. (Note that these 42 rules likely cover a disproportionate share of errors:
unused imports, undefined variables, etc.)
Of the unimplemented rules, ruff is missing:
The unimplemented rules are tracked in #170, and include:
- 15 rules related to string `.format` calls.
- 6 rules related to misplaced `yield`, `break`, and `return` statements.
- 3 rules related to syntax errors in doctests and annotations.
- 2 rules related to redefining unused names.
...along with a variety of others that don't fit neatly into any specific category.
- 14 rules related to string `.format` calls.
- 4 logical rules.
- 1 rule related to parsing.
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
@@ -147,25 +144,39 @@ Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis F
| Code | Name | Message |
| ---- | ----- | ------- |
| E402 | ModuleImportNotAtTopOfFile | Module level import not at top of file |
| E501 | LineTooLong | Line too long |
| E501 | LineTooLong | Line too long (89 > 88 characters) |
| E711 | NoneComparison | Comparison to `None` should be `cond is None` |
| E712 | TrueFalseComparison | Comparison to `True` should be `cond is True` |
| E713 | NotInTest | Test for membership should be `not in` |
| E714 | NotIsTest | Test for object identity should be `is not` |
| E721 | TypeComparison | do not compare types, use `isinstance()` |
| E722 | DoNotUseBareExcept | Do not use bare `except` |
| E731 | DoNotAssignLambda | Do not assign a lambda expression, use a def |
| E741 | AmbiguousVariableName | ambiguous variable name '...' |
| E742 | AmbiguousClassName | ambiguous class name '...' |
| E743 | AmbiguousFunctionName | ambiguous function name '...' |
| E902 | IOError | No such file or directory: `...` |
| E999 | SyntaxError | SyntaxError: ... |
| F401 | UnusedImport | `...` imported but unused |
| F403 | ImportStarUsage | Unable to detect undefined names |
| F403 | ImportStarUsage | `from ... import *` used; unable to detect undefined names |
| F404 | LateFutureImport | from __future__ imports must occur at the beginning of the file |
| F406 | ImportStarNotPermitted | `from ... import *` only allowed at module level |
| F407 | FutureFeatureNotDefined | future feature '...' is not defined |
| F541 | FStringMissingPlaceholders | f-string without any placeholders |
| F601 | MultiValueRepeatedKeyLiteral | Dictionary key literal repeated |
| F602 | MultiValueRepeatedKeyVariable | Dictionary key `...` repeated |
| F621 | TooManyExpressionsInStarredAssignment | too many expressions in star-unpacking assignment |
| F622 | TwoStarredExpressions | two starred expressions in assignment |
| F631 | AssertTuple | Assert test is a non-empty tuple, which is always `True` |
| F632 | IsLiteral | use ==/!= to compare constant literals |
| F633 | InvalidPrintSyntax | use of >> is invalid with print function |
| F634 | IfTuple | If test is a tuple, which is always `True` |
| F701 | BreakOutsideLoop | `break` outside loop |
| F702 | ContinueOutsideLoop | `continue` not properly in loop |
| F704 | YieldOutsideFunction | a `yield` or `yield from` statement outside of a function/method |
| F706 | ReturnOutsideFunction | a `return` statement outside of a function/method |
| F707 | DefaultExceptNotLast | an `except:` block as not the last exception handler |
| F722 | ForwardAnnotationSyntaxError | syntax error in forward annotation '...' |
| F821 | UndefinedName | Undefined name `...` |
| F822 | UndefinedExport | Undefined name `...` in `__all__` |
| F823 | UndefinedLocal | Local variable `...` referenced before assignment |

View File

@@ -3,15 +3,27 @@ use ruff::checks::{CheckKind, RejectedCmpop};
fn main() {
let mut check_kinds: Vec<CheckKind> = vec![
CheckKind::AmbiguousClassName("...".to_string()),
CheckKind::AmbiguousFunctionName("...".to_string()),
CheckKind::AmbiguousVariableName("...".to_string()),
CheckKind::AssertTuple,
CheckKind::BreakOutsideLoop,
CheckKind::ContinueOutsideLoop,
CheckKind::DefaultExceptNotLast,
CheckKind::DoNotAssignLambda,
CheckKind::DoNotUseBareExcept,
CheckKind::DuplicateArgumentName,
CheckKind::FStringMissingPlaceholders,
CheckKind::ForwardAnnotationSyntaxError("...".to_string()),
CheckKind::FutureFeatureNotDefined("...".to_string()),
CheckKind::IOError("...".to_string()),
CheckKind::IfTuple,
CheckKind::ImportStarUsage,
CheckKind::LineTooLong,
CheckKind::ImportStarNotPermitted("...".to_string()),
CheckKind::ImportStarUsage("...".to_string()),
CheckKind::InvalidPrintSyntax,
CheckKind::IsLiteral,
CheckKind::LateFutureImport,
CheckKind::LineTooLong(89, 88),
CheckKind::ModuleImportNotAtTopOfFile,
CheckKind::MultiValueRepeatedKeyLiteral,
CheckKind::MultiValueRepeatedKeyVariable("...".to_string()),
@@ -21,9 +33,11 @@ fn main() {
CheckKind::NotIsTest,
CheckKind::RaiseNotImplemented,
CheckKind::ReturnOutsideFunction,
CheckKind::SyntaxError("...".to_string()),
CheckKind::TooManyExpressionsInStarredAssignment,
CheckKind::TrueFalseComparison(true, RejectedCmpop::Eq),
CheckKind::TwoStarredExpressions,
CheckKind::TypeComparison,
CheckKind::UndefinedExport("...".to_string()),
CheckKind::UndefinedLocal("...".to_string()),
CheckKind::UndefinedName("...".to_string()),

View File

@@ -19,10 +19,12 @@ classifiers = [
]
author = "Charlie Marsh"
author_email = "charlie.r.marsh@gmail.com"
url = "https://github.com/charliermarsh/ruff"
description = "An extremely fast Python linter, written in Rust."
requires-python = ">=3.7"
[project.urls]
repository = "https://github.com/charliermarsh/ruff"
[build-system]
requires = ["maturin>=0.13,<0.14"]
build-backend = "maturin"

View File

@@ -1,11 +1,50 @@
if var == None:
#: E711
if res == None:
pass
#: E711
if res != None:
pass
#: E711
if None == res:
pass
#: E711
if None != res:
pass
#: E711
if res[1] == None:
pass
#: E711
if res[1] != None:
pass
#: E711
if None != res[1]:
pass
#: E711
if None == res[1]:
pass
if None != var:
#: Okay
if x not in y:
pass
if var is None:
if not (X in Y or X is Z):
pass
if None is not var:
if not (X in Y):
pass
if x is not y:
pass
if X is not Y is not Z:
pass
if TrueElement.get_element(True) == TrueElement.get_element(False):
pass
if (True) == TrueElement or x == TrueElement:
pass
assert (not foo) in bar
assert {"x": not foo} in bar
assert [42, not foo] in bar

View File

@@ -1,14 +1,46 @@
if var == True:
#: E712
if res == True:
pass
#: E712
if res != False:
pass
#: E712
if True != res:
pass
#: E712
if False == res:
pass
#: E712
if res[1] == True:
pass
#: E712
if res[1] != False:
pass
#: E712
var = 1 if cond == True else -1 if cond == False else cond
#: E712
if (True) == TrueElement or x == TrueElement:
pass
if False != var:
#: Okay
if x not in y:
pass
if var != False != True:
if not (X in Y or X is Z):
pass
if var is True:
if not (X in Y):
pass
if False is not var:
if x is not y:
pass
if X is not Y is not Z:
pass
if TrueElement.get_element(True) == TrueElement.get_element(False):
pass
assert (not foo) in bar
assert {"x": not foo} in bar
assert [42, not foo] in bar

View File

@@ -1,7 +1,38 @@
my_list = [1, 2, 3]
if not num in my_list:
print(num)
#: E713
if not X in Y:
pass
#: E713
if not X.B in Y:
pass
#: E713
if not X in Y and Z == "zero":
pass
#: E713
if X == "zero" or not Y in Z:
pass
#: E713
if not (X in Y):
pass
my_list = [1, 2, 3]
if num not in my_list:
print(num)
#: Okay
if x not in y:
pass
if not (X in Y or X is Z):
pass
if x is not y:
pass
if X is not Y is not Z:
pass
if TrueElement.get_element(True) == TrueElement.get_element(False):
pass
if (True) == TrueElement or x == TrueElement:
pass
assert (not foo) in bar
assert {"x": not foo} in bar
assert [42, not foo] in bar

View File

@@ -1,5 +1,38 @@
if not user is None:
print(user.name)
#: E714
if not X is Y:
pass
#: E714
if not X.B is Y:
pass
#: E714
if not X is Y is not Z:
pass
if user is not None:
print(user.name)
#: Okay
if not X is not Y:
pass
if x not in y:
pass
if not (X in Y or X is Z):
pass
if not (X in Y):
pass
if x is not y:
pass
if X is not Y is not Z:
pass
if TrueElement.get_element(True) == TrueElement.get_element(False):
pass
if (True) == TrueElement or x == TrueElement:
pass
assert (not foo) in bar
assert {"x": not foo} in bar
assert [42, not foo] in bar

54
resources/test/fixtures/E721.py vendored Normal file
View File

@@ -0,0 +1,54 @@
#: E721
if type(res) == type(42):
pass
#: E721
if type(res) != type(""):
pass
#: E721
import types
if res == types.IntType:
pass
#: E721
import types
if type(res) is not types.ListType:
pass
#: E721
assert type(res) == type(False) or type(res) == type(None)
#: E721
assert type(res) == type([])
#: E721
assert type(res) == type(())
#: E721
assert type(res) == type((0,))
#: E721
assert type(res) == type((0))
#: E721
assert type(res) != type((1,))
#: E721
assert type(res) is type((1,))
#: E721
assert type(res) is not type((1,))
#: E211 E721
assert type(res) == type(
[
2,
]
)
#: E201 E201 E202 E721
assert type(res) == type(())
#: E201 E202 E721
assert type(res) == type((0,))
#: Okay
import types
if isinstance(res, int):
pass
if isinstance(res, str):
pass
if isinstance(res, types.MethodType):
pass
if type(a) != type(b) or type(a) == type(ccc):
pass

33
resources/test/fixtures/E722.py vendored Normal file
View File

@@ -0,0 +1,33 @@
#: E722
try:
pass
except:
pass
#: E722
try:
pass
except Exception:
pass
except:
pass
#: E722
try:
pass
except:
pass
#: Okay
fake_code = """"
try:
do_something()
except:
pass
"""
try:
pass
except Exception:
pass
#: Okay
from . import compute_type
if compute_type(foo) == 5:
pass

View File

@@ -1,6 +1,21 @@
from typing import Callable, Iterable
#: E731
f = lambda x: 2 * x
#: E731
f = lambda x: 2 * x
#: E731
while False:
this = lambda y, z: 2 * x
a = lambda x: x**2
b = map(lambda x: 2 * x, range(3))
c: Callable = lambda x: x**2
d: Iterable = map(lambda x: 2 * x, range(3))
f = object()
#: E731
f.method = lambda: "Method"
f = {}
#: E731
f["a"] = lambda x: x ** 2
f = []
f.append(lambda x: x ** 2)
lambda: "no-op"

75
resources/test/fixtures/E741.py vendored Normal file
View File

@@ -0,0 +1,75 @@
from contextlib import contextmanager
l = 0
I = 0
O = 0
l: int = 0
a, l = 0, 1
[a, l] = 0, 1
a, *l = 0, 1, 2
a = l = 0
o = 0
i = 0
for l in range(3):
pass
for a, l in zip(range(3), range(3)):
pass
def f1():
global l
l = 0
def f2():
l = 0
def f3():
nonlocal l
l = 1
f3()
return l
def f4(l, /, I):
return l, I, O
def f5(l=0, *, I=1):
return l, I
def f6(*l, **I):
return l, I
@contextmanager
def ctx1():
yield 0
with ctx1() as l:
pass
@contextmanager
def ctx2():
yield 0, 1
with ctx2() as (a, l):
pass
try:
pass
except ValueError as l:
pass
if (l := 5) > 0:
pass

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

@@ -0,0 +1,14 @@
class l:
pass
class I:
pass
class O:
pass
class X:
pass

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

@@ -0,0 +1,15 @@
def l():
pass
def I():
pass
class X:
def O(self):
pass
def x():
pass

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

@@ -0,0 +1,2 @@
def x():

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

@@ -0,0 +1,7 @@
from __future__ import print_function
"""Docstring"""
from __future__ import absolute_import
from collections import namedtuple
from __future__ import print_function

9
resources/test/fixtures/F406.py vendored Normal file
View File

@@ -0,0 +1,9 @@
from F634 import *
def f():
from F634 import *
class F:
from F634 import *

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

@@ -0,0 +1,2 @@
from __future__ import print_function
from __future__ import non_existent_feature

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

@@ -0,0 +1,5 @@
if x is "abc":
pass
if 123 is not y:
pass

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

@@ -0,0 +1,4 @@
from __future__ import print_function
import sys
print >> sys.stderr, "Hello"

23
resources/test/fixtures/F701.py vendored Normal file
View File

@@ -0,0 +1,23 @@
for i in range(10):
break
else:
break
i = 0
while i < 10:
i += 1
break
def f():
for i in range(10):
break
break
class Foo:
break
break

23
resources/test/fixtures/F702.py vendored Normal file
View File

@@ -0,0 +1,23 @@
for i in range(10):
continue
else:
continue
i = 0
while i < 10:
i += 1
continue
def f():
for i in range(10):
continue
continue
class Foo:
continue
continue

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

@@ -0,0 +1,10 @@
class A:
pass
def f() -> "A":
pass
def g() -> "///":
pass

View File

@@ -14,3 +14,11 @@ def f():
x = 1
y = 2
z = x + y
def g():
foo = (1, 2)
(a, b) = (1, 2)
bar = (1, 2)
(c, d) = bar

View File

@@ -0,0 +1,27 @@
from __future__ import annotations
from dataclasses import dataclass
from models import Fruit, Nut
@dataclass
class Foo:
x: int
y: int
@classmethod
def a(cls) -> Foo:
return cls(x=0, y=0)
@classmethod
def b(cls) -> "Foo":
return cls(x=0, y=0)
@classmethod
def c(cls) -> Bar:
return cls(x=0, y=0)
@classmethod
def d(cls) -> Fruit:
return cls(x=0, y=0)

View File

@@ -1,6 +1,6 @@
[tool.ruff]
line-length = 88
exclude = ["excluded.py", "**/migrations"]
extend-exclude = ["excluded.py", "migrations"]
select = [
"E402",
"E501",
@@ -8,20 +8,34 @@ select = [
"E712",
"E713",
"E714",
"E721",
"E722",
"E731",
"E741",
"E742",
"E743",
"E902",
"E999",
"F401",
"F403",
"F404",
"F406",
"F407",
"F541",
"F601",
"F602",
"F621",
"F622",
"F631",
"F632",
"F633",
"F634",
"F701",
"F702",
"F704",
"F706",
"F707",
"F722",
"F821",
"F822",
"F823",

View File

@@ -3,7 +3,7 @@ use std::collections::BTreeSet;
use itertools::izip;
use rustpython_parser::ast::{
Arg, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Keyword,
Location, Stmt, Unaryop,
Location, Stmt, StmtKind, Unaryop,
};
use crate::ast::operations::SourceCodeLocator;
@@ -42,18 +42,20 @@ pub fn check_not_tests(
if matches!(op, Unaryop::Not) {
if let ExprKind::Compare { ops, .. } = &operand.node {
match ops[..] {
[Cmpop::In] => {
if check_not_in {
checks.push(Check::new(CheckKind::NotInTest, operand.location));
for op in ops {
match op {
Cmpop::In => {
if check_not_in {
checks.push(Check::new(CheckKind::NotInTest, operand.location));
}
}
}
[Cmpop::Is] => {
if check_not_is {
checks.push(Check::new(CheckKind::NotIsTest, operand.location));
Cmpop::Is => {
if check_not_is {
checks.push(Check::new(CheckKind::NotIsTest, operand.location));
}
}
_ => {}
}
_ => {}
}
}
}
@@ -93,6 +95,46 @@ pub fn check_do_not_assign_lambda(value: &Expr, location: Location) -> Option<Ch
}
}
fn is_ambiguous_name(name: &str) -> bool {
name == "l" || name == "I" || name == "O"
}
/// Check AmbiguousVariableName compliance.
pub fn check_ambiguous_variable_name(name: &str, location: Location) -> Option<Check> {
if is_ambiguous_name(name) {
Some(Check::new(
CheckKind::AmbiguousVariableName(name.to_string()),
location,
))
} else {
None
}
}
/// Check AmbiguousClassName compliance.
pub fn check_ambiguous_class_name(name: &str, location: Location) -> Option<Check> {
if is_ambiguous_name(name) {
Some(Check::new(
CheckKind::AmbiguousClassName(name.to_string()),
location,
))
} else {
None
}
}
/// Check AmbiguousFunctionName compliance.
pub fn check_ambiguous_function_name(name: &str, location: Location) -> Option<Check> {
if is_ambiguous_name(name) {
Some(Check::new(
CheckKind::AmbiguousFunctionName(name.to_string()),
location,
))
} else {
None
}
}
/// Check UselessObjectInheritance compliance.
pub fn check_useless_object_inheritance(
stmt: &Stmt,
@@ -116,9 +158,7 @@ pub fn check_useless_object_inheritance(
CheckKind::UselessObjectInheritance(name.to_string()),
expr.location,
);
if matches!(autofix, fixer::Mode::Generate)
|| matches!(autofix, fixer::Mode::Apply)
{
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let Some(fix) = fixes::remove_class_def_base(
locator,
&stmt.location,
@@ -214,9 +254,7 @@ pub fn check_assert_equals(expr: &Expr, autofix: &fixer::Mode) -> Option<Check>
if let ExprKind::Name { id, .. } = &value.node {
if id == "self" {
let mut check = Check::new(CheckKind::NoAssertEquals, expr.location);
if matches!(autofix, fixer::Mode::Generate)
|| matches!(autofix, fixer::Mode::Apply)
{
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix {
content: "assertEqual".to_string(),
start: Location::new(expr.location.row(), expr.location.column() + 1),
@@ -275,7 +313,7 @@ pub fn check_repeated_keys(
(Some(DictionaryKey::Variable(v1)), Some(DictionaryKey::Variable(v2))) => {
if check_repeated_variables && v1 == v2 {
checks.push(Check::new(
CheckKind::MultiValueRepeatedKeyVariable(v2.to_string()),
CheckKind::MultiValueRepeatedKeyVariable((*v2).to_string()),
k2.location,
))
}
@@ -396,6 +434,90 @@ pub fn check_literal_comparisons(
checks
}
fn is_constant(expr: &Expr) -> bool {
match &expr.node {
ExprKind::Constant { .. } => true,
ExprKind::Tuple { elts, .. } => elts.iter().all(is_constant),
_ => false,
}
}
fn is_singleton(expr: &Expr) -> bool {
matches!(
expr.node,
ExprKind::Constant {
value: Constant::None | Constant::Bool(_) | Constant::Ellipsis,
..
}
)
}
fn is_constant_non_singleton(expr: &Expr) -> bool {
is_constant(expr) && !is_singleton(expr)
}
/// Check IsLiteral compliance.
pub fn check_is_literal(
left: &Expr,
ops: &Vec<Cmpop>,
comparators: &Vec<Expr>,
location: Location,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
let mut left = left;
for (op, right) in izip!(ops, comparators) {
if matches!(op, Cmpop::Is | Cmpop::IsNot)
&& (is_constant_non_singleton(left) || is_constant_non_singleton(right))
{
checks.push(Check::new(CheckKind::IsLiteral, location));
}
left = right;
}
checks
}
/// Check TypeComparison compliance.
pub fn check_type_comparison(
ops: &Vec<Cmpop>,
comparators: &Vec<Expr>,
location: Location,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
for (op, right) in izip!(ops, comparators) {
if matches!(op, Cmpop::Is | Cmpop::IsNot | Cmpop::Eq | Cmpop::NotEq) {
match &right.node {
ExprKind::Call { func, args, .. } => {
if let ExprKind::Name { id, .. } = &func.node {
// Ex) type(False)
if id == "type" {
if let Some(arg) = args.first() {
// Allow comparison for types which are not obvious.
if !matches!(arg.node, ExprKind::Name { .. }) {
checks.push(Check::new(CheckKind::TypeComparison, location));
}
}
}
}
}
ExprKind::Attribute { value, .. } => {
if let ExprKind::Name { id, .. } = &value.node {
// Ex) types.IntType
if id == "types" {
checks.push(Check::new(CheckKind::TypeComparison, location));
}
}
}
_ => {}
}
}
}
checks
}
/// Check TwoStarredExpressions and TooManyExpressionsInStarredAssignment compliance.
pub fn check_starred_expressions(
elts: &[Expr],
@@ -428,3 +550,77 @@ pub fn check_starred_expressions(
None
}
/// Check BreakOutsideLoop compliance.
pub fn check_break_outside_loop(
stmt: &Stmt,
parents: &[&Stmt],
parent_stack: &[usize],
) -> Option<Check> {
let mut allowed: bool = false;
let mut parent = stmt;
for index in parent_stack.iter().rev() {
let child = parent;
parent = parents[*index];
match &parent.node {
StmtKind::For { orelse, .. }
| StmtKind::AsyncFor { orelse, .. }
| StmtKind::While { orelse, .. } => {
if !orelse.contains(child) {
allowed = true;
break;
}
}
StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
| StmtKind::ClassDef { .. } => {
break;
}
_ => {}
}
}
if !allowed {
Some(Check::new(CheckKind::BreakOutsideLoop, stmt.location))
} else {
None
}
}
/// Check ContinueOutsideLoop compliance.
pub fn check_continue_outside_loop(
stmt: &Stmt,
parents: &[&Stmt],
parent_stack: &[usize],
) -> Option<Check> {
let mut allowed: bool = false;
let mut parent = stmt;
for index in parent_stack.iter().rev() {
let child = parent;
parent = parents[*index];
match &parent.node {
StmtKind::For { orelse, .. }
| StmtKind::AsyncFor { orelse, .. }
| StmtKind::While { orelse, .. } => {
if !orelse.contains(child) {
allowed = true;
break;
}
}
StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
| StmtKind::ClassDef { .. } => {
break;
}
_ => {}
}
}
if !allowed {
Some(Check::new(CheckKind::ContinueOutsideLoop, stmt.location))
} else {
None
}
}

View File

@@ -22,7 +22,7 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
if let StmtKind::AugAssign { .. } = &stmt.node {
if let Some(binding) = scope.values.get("__all__") {
if let BindingKind::Export(existing) = &binding.kind {
names.extend(existing.clone());
names.extend_from_slice(existing);
}
}
}
@@ -70,9 +70,7 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
pub fn on_conditional_branch(parent_stack: &[usize], parents: &[&Stmt]) -> bool {
for index in parent_stack.iter().rev() {
let parent = parents[*index];
if matches!(parent.node, StmtKind::If { .. })
|| matches!(parent.node, StmtKind::While { .. })
{
if matches!(parent.node, StmtKind::If { .. } | StmtKind::While { .. }) {
return true;
}
if let StmtKind::Expr { value } = &parent.node {
@@ -89,10 +87,10 @@ pub fn on_conditional_branch(parent_stack: &[usize], parents: &[&Stmt]) -> bool
pub fn in_nested_block(parent_stack: &[usize], parents: &[&Stmt]) -> bool {
for index in parent_stack.iter().rev() {
let parent = parents[*index];
if matches!(parent.node, StmtKind::Try { .. })
|| matches!(parent.node, StmtKind::If { .. })
|| matches!(parent.node, StmtKind::With { .. })
{
if matches!(
parent.node,
StmtKind::Try { .. } | StmtKind::If { .. } | StmtKind::With { .. }
) {
return true;
}
}
@@ -100,6 +98,24 @@ pub fn in_nested_block(parent_stack: &[usize], parents: &[&Stmt]) -> bool {
false
}
/// Check if a node represents an unpacking assignment.
pub fn is_unpacking_assignment(stmt: &Stmt) -> bool {
if let StmtKind::Assign { targets, value, .. } = &stmt.node {
for child in targets {
match &child.node {
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. } => {}
_ => return false,
}
}
match &value.node {
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. } => return false,
_ => {}
}
return true;
}
false
}
/// Struct used to efficiently slice source code at (row, column) Locations.
pub struct SourceCodeLocator<'a> {
content: &'a str,

View File

@@ -35,8 +35,10 @@ impl Scope {
#[derive(Clone, Debug)]
pub enum BindingKind {
Annotation,
Argument,
Assignment,
Binding,
Builtin,
ClassDefinition,
Definition,

View File

@@ -1,8 +1,9 @@
use std::ops::Deref;
use std::path::Path;
use rustpython_parser::ast::{
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
KeywordData, Location, Stmt, StmtKind, Suite,
KeywordData, Location, Operator, Stmt, StmtKind, Suite,
};
use rustpython_parser::parser;
@@ -14,6 +15,7 @@ use crate::ast::{checks, operations, visitor};
use crate::autofix::fixer;
use crate::checks::{Check, CheckCode, CheckKind};
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
use crate::python::future::ALL_FEATURE_NAMES;
use crate::python::typing;
use crate::settings::Settings;
@@ -34,7 +36,8 @@ struct Checker<'a> {
scopes: Vec<Scope>,
scope_stack: Vec<usize>,
dead_scopes: Vec<usize>,
deferred_annotations: Vec<(Location, &'a str)>,
deferred_string_annotations: Vec<(Location, &'a str)>,
deferred_annotations: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
deferred_functions: Vec<(&'a Stmt, Vec<usize>, Vec<usize>)>,
deferred_lambdas: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
deferred_assignments: Vec<usize>,
@@ -44,6 +47,8 @@ struct Checker<'a> {
in_literal: bool,
seen_non_import: bool,
seen_docstring: bool,
futures_allowed: bool,
annotations_future_enabled: bool,
}
impl<'a> Checker<'a> {
@@ -64,6 +69,7 @@ impl<'a> Checker<'a> {
scopes: vec![],
scope_stack: vec![],
dead_scopes: vec![],
deferred_string_annotations: vec![],
deferred_annotations: vec![],
deferred_functions: vec![],
deferred_lambdas: vec![],
@@ -73,6 +79,8 @@ impl<'a> Checker<'a> {
in_literal: false,
seen_non_import: false,
seen_docstring: false,
futures_allowed: true,
annotations_future_enabled: false,
}
}
}
@@ -102,32 +110,57 @@ where
// Track whether we've seen docstrings, non-imports, etc.
match &stmt.node {
StmtKind::Import { .. } => {}
StmtKind::ImportFrom { .. } => {}
StmtKind::Expr { value } => {
if !self.seen_docstring
&& stmt.location.column() == 1
&& !operations::in_nested_block(&self.parent_stack, &self.parents)
{
if let ExprKind::Constant {
value: Constant::Str(_),
..
} = &value.node
{
self.seen_docstring = true;
StmtKind::ImportFrom { module, .. } => {
// Allow __future__ imports until we see a non-__future__ import.
if self.futures_allowed {
if let Some(module) = module {
if module != "__future__" {
self.futures_allowed = false;
}
}
}
if !self.seen_non_import
&& stmt.location.column() == 1
}
StmtKind::Import { .. } => {
self.futures_allowed = false;
}
StmtKind::Expr { value } => {
if self.seen_docstring
&& !self.seen_non_import
&& !operations::in_nested_block(&self.parent_stack, &self.parents)
{
self.seen_non_import = true;
}
if !self.seen_docstring
&& !operations::in_nested_block(&self.parent_stack, &self.parents)
&& matches!(
&value.node,
ExprKind::Constant {
value: Constant::Str(_),
..
},
)
{
self.seen_docstring = true;
}
// Allow docstrings to interrupt __future__ imports.
if self.futures_allowed
&& !matches!(
&value.node,
ExprKind::Constant {
value: Constant::Str(_),
..
},
)
{
self.futures_allowed = false;
}
}
_ => {
self.futures_allowed = false;
if !self.seen_non_import
&& stmt.location.column() == 1
&& !operations::in_nested_block(&self.parent_stack, &self.parents)
{
self.seen_non_import = true;
@@ -158,6 +191,30 @@ where
}
}
}
if self.settings.select.contains(&CheckCode::E741) {
self.checks.extend(names.iter().filter_map(|name| {
checks::check_ambiguous_variable_name(name, stmt.location)
}));
}
}
StmtKind::Break => {
if self.settings.select.contains(&CheckCode::F701) {
if let Some(check) =
checks::check_break_outside_loop(stmt, &self.parents, &self.parent_stack)
{
self.checks.push(check);
}
}
}
StmtKind::Continue => {
if self.settings.select.contains(&CheckCode::F702) {
if let Some(check) =
checks::check_continue_outside_loop(stmt, &self.parents, &self.parent_stack)
{
self.checks.push(check);
}
}
}
StmtKind::FunctionDef {
name,
@@ -173,6 +230,12 @@ where
args,
..
} => {
if self.settings.select.contains(&CheckCode::E743) {
if let Some(check) = checks::check_ambiguous_function_name(name, stmt.location)
{
self.checks.push(check);
}
}
for expr in decorator_list {
self.visit_expr(expr);
}
@@ -261,6 +324,12 @@ where
}
}
if self.settings.select.contains(&CheckCode::E742) {
if let Some(check) = checks::check_ambiguous_class_name(name, stmt.location) {
self.checks.push(check);
}
}
for expr in bases {
self.visit_expr(expr)
}
@@ -357,6 +426,25 @@ where
location: stmt.location,
},
);
if alias.node.name == "annotations" {
self.annotations_future_enabled = true;
}
if self.settings.select.contains(&CheckCode::F407)
&& !ALL_FEATURE_NAMES.contains(&alias.node.name.deref())
{
self.checks.push(Check::new(
CheckKind::FutureFeatureNotDefined(alias.node.name.to_string()),
stmt.location,
));
}
if self.settings.select.contains(&CheckCode::F404) && !self.futures_allowed
{
self.checks
.push(Check::new(CheckKind::LateFutureImport, stmt.location));
}
} else if alias.node.name == "*" {
self.add_binding(
name,
@@ -368,14 +456,31 @@ where
);
if self.settings.select.contains(&CheckCode::F403) {
self.checks
.push(Check::new(CheckKind::ImportStarUsage, stmt.location));
self.checks.push(Check::new(
CheckKind::ImportStarUsage(
module.clone().unwrap_or_else(|| "module".to_string()),
),
stmt.location,
));
}
if self.settings.select.contains(&CheckCode::F406) {
let scope = &self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
if !matches!(scope.kind, ScopeKind::Module) {
self.checks.push(Check::new(
CheckKind::ImportStarNotPermitted(
module.clone().unwrap_or_else(|| "module".to_string()),
),
stmt.location,
));
}
}
} else {
let binding = Binding {
kind: BindingKind::Importation(match module {
None => name.clone(),
Some(parent) => format!("{}.{}", parent, name.clone()),
Some(parent) => format!("{}.{}", parent, name),
}),
used: None,
location: stmt.location,
@@ -484,6 +589,17 @@ where
let prev_in_literal = self.in_literal;
let prev_in_annotation = self.in_annotation;
// Important:
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);
return;
}
// Pre-visit.
match &expr.node {
ExprKind::Subscript { value, .. } => {
@@ -491,7 +607,7 @@ where
self.in_literal = true;
}
}
ExprKind::Tuple { elts, ctx } => {
ExprKind::Tuple { elts, ctx } | ExprKind::List { elts, ctx } => {
if matches!(ctx, ExprContext::Store) {
let check_too_many_expressions =
self.settings.select.contains(&CheckCode::F621);
@@ -507,12 +623,19 @@ where
}
}
}
ExprKind::Name { ctx, .. } => match ctx {
ExprKind::Name { id, ctx } => match ctx {
ExprContext::Load => self.handle_node_load(expr),
ExprContext::Store => {
if self.settings.select.contains(&CheckCode::E741) {
if let Some(check) =
checks::check_ambiguous_variable_name(id, expr.location)
{
self.checks.push(check);
}
}
let parent =
self.parents[*(self.parent_stack.last().expect("No parent found."))];
self.handle_node_store(expr, Some(parent));
self.handle_node_store(expr, parent);
}
ExprContext::Del => self.handle_node_delete(expr),
},
@@ -541,8 +664,7 @@ where
.settings
.select
.contains(CheckKind::YieldOutsideFunction.code())
&& matches!(scope.kind, ScopeKind::Class)
|| matches!(scope.kind, ScopeKind::Module)
&& matches!(scope.kind, ScopeKind::Class | ScopeKind::Module)
{
self.checks
.push(Check::new(CheckKind::YieldOutsideFunction, expr.location));
@@ -565,6 +687,28 @@ where
}
self.in_f_string = true;
}
ExprKind::BinOp {
left,
op: Operator::RShift,
..
} => {
if self.settings.select.contains(&CheckCode::F633) {
if let ExprKind::Name { id, .. } = &left.node {
if id == "print" {
let scope = &self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
if let Some(Binding {
kind: BindingKind::Builtin,
..
}) = scope.values.get("print")
{
self.checks
.push(Check::new(CheckKind::InvalidPrintSyntax, left.location));
}
}
}
}
}
ExprKind::UnaryOp { op, operand } => {
let check_not_in = self.settings.select.contains(&CheckCode::E713);
let check_not_is = self.settings.select.contains(&CheckCode::E714);
@@ -593,12 +737,30 @@ where
check_true_false_comparisons,
));
}
if self.settings.select.contains(&CheckCode::F632) {
self.checks.extend(checks::check_is_literal(
left,
ops,
comparators,
expr.location,
));
}
if self.settings.select.contains(&CheckCode::E721) {
self.checks.extend(checks::check_type_comparison(
ops,
comparators,
expr.location,
));
}
}
ExprKind::Constant {
value: Constant::Str(value),
..
} if self.in_annotation && !self.in_literal => {
self.deferred_annotations.push((expr.location, value));
self.deferred_string_annotations
.push((expr.location, value));
}
ExprKind::GeneratorExp { .. }
| ExprKind::ListComp { .. }
@@ -659,7 +821,7 @@ where
} else if match_name_or_attr(func, "NamedTuple") {
self.visit_expr(func);
// NamedTuple("a", [("a", int)])
// Ex) NamedTuple("a", [("a", int)])
if args.len() > 1 {
match &args[1].node {
ExprKind::List { elts, .. } | ExprKind::Tuple { elts, .. } => {
@@ -683,7 +845,7 @@ where
}
}
// NamedTuple("a", a=int)
// Ex) NamedTuple("a", a=int)
for keyword in keywords {
let KeywordData { value, .. } = &keyword.node;
self.visit_annotation(value);
@@ -691,7 +853,7 @@ where
} else if match_name_or_attr(func, "TypedDict") {
self.visit_expr(func);
// TypedDict("a", {"a": int})
// Ex) TypedDict("a", {"a": int})
if args.len() > 1 {
if let ExprKind::Dict { keys, values } = &args[1].node {
for key in keys {
@@ -705,7 +867,7 @@ where
}
}
// TypedDict("a", a=int)
// Ex) TypedDict("a", a=int)
for keyword in keywords {
let KeywordData { value, .. } = &keyword.node;
self.visit_annotation(value);
@@ -744,13 +906,45 @@ where
fn visit_excepthandler(&mut self, excepthandler: &'b Excepthandler) {
match &excepthandler.node {
ExcepthandlerKind::ExceptHandler { name, .. } => match name {
Some(name) => {
let scope =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
if scope.values.contains_key(name) {
ExcepthandlerKind::ExceptHandler { type_, name, .. } => {
if self.settings.select.contains(&CheckCode::E722) && type_.is_none() {
self.checks.push(Check::new(
CheckKind::DoNotUseBareExcept,
excepthandler.location,
));
}
match name {
Some(name) => {
if self.settings.select.contains(&CheckCode::E741) {
if let Some(check) =
checks::check_ambiguous_variable_name(name, excepthandler.location)
{
self.checks.push(check);
}
}
let scope = &self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
if scope.values.contains_key(name) {
let parent = self.parents
[*(self.parent_stack.last().expect("No parent found."))];
self.handle_node_store(
&Expr::new(
excepthandler.location,
ExprKind::Name {
id: name.to_string(),
ctx: ExprContext::Store,
},
),
parent,
);
self.parents.push(parent);
}
let parent =
self.parents[*(self.parent_stack.last().expect("No parent found."))];
let scope = &self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
let definition = scope.values.get(name).cloned();
self.handle_node_store(
&Expr::new(
excepthandler.location,
@@ -759,48 +953,32 @@ where
ctx: ExprContext::Store,
},
),
Some(parent),
parent,
);
self.parents.push(parent);
}
let parent =
self.parents[*(self.parent_stack.last().expect("No parent found."))];
let scope =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
let definition = scope.values.get(name).cloned();
self.handle_node_store(
&Expr::new(
excepthandler.location,
ExprKind::Name {
id: name.to_string(),
ctx: ExprContext::Store,
},
),
Some(parent),
);
self.parents.push(parent);
walk_excepthandler(self, excepthandler);
walk_excepthandler(self, excepthandler);
let scope = &mut self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
if let Some(binding) = &scope.values.remove(name) {
if self.settings.select.contains(&CheckCode::F841)
&& binding.used.is_none()
{
self.checks.push(Check::new(
CheckKind::UnusedVariable(name.to_string()),
excepthandler.location,
));
}
}
let scope = &mut self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
if let Some(binding) = &scope.values.remove(name) {
if self.settings.select.contains(&CheckCode::F841) && binding.used.is_none()
{
self.checks.push(Check::new(
CheckKind::UnusedVariable(name.to_string()),
excepthandler.location,
));
if let Some(binding) = definition {
scope.values.insert(name.to_string(), binding);
}
}
if let Some(binding) = definition {
scope.values.insert(name.to_string(), binding);
}
None => walk_excepthandler(self, excepthandler),
}
None => walk_excepthandler(self, excepthandler),
},
}
}
}
@@ -838,6 +1016,13 @@ where
location: arg.location,
},
);
if self.settings.select.contains(&CheckCode::E741) {
if let Some(check) = checks::check_ambiguous_variable_name(&arg.node.arg, arg.location)
{
self.checks.push(check);
}
}
}
}
@@ -871,7 +1056,7 @@ impl<'a> Checker<'a> {
for builtin in BUILTINS {
scope.values.insert(
builtin.to_string(),
(*builtin).to_string(),
Binding {
kind: BindingKind::Builtin,
location: Default::default(),
@@ -881,7 +1066,7 @@ impl<'a> Checker<'a> {
}
for builtin in MAGIC_GLOBALS {
scope.values.insert(
builtin.to_string(),
(*builtin).to_string(),
Binding {
kind: BindingKind::Builtin,
location: Default::default(),
@@ -940,7 +1125,7 @@ impl<'a> Checker<'a> {
}
}
fn handle_node_store(&mut self, expr: &Expr, parent: Option<&Stmt>) {
fn handle_node_store(&mut self, expr: &Expr, parent: &Stmt) {
if let ExprKind::Name { id, .. } = &expr.node {
let current =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
@@ -950,9 +1135,7 @@ impl<'a> Checker<'a> {
&& !current.values.contains_key(id)
{
for scope in self.scopes.iter().rev().skip(1) {
if matches!(scope.kind, ScopeKind::Function)
|| matches!(scope.kind, ScopeKind::Module)
{
if matches!(scope.kind, ScopeKind::Function | ScopeKind::Module) {
let used = scope
.values
.get(id)
@@ -970,35 +1153,63 @@ impl<'a> Checker<'a> {
}
}
// TODO(charlie): Handle alternate binding types (like `Annotation`).
if id == "__all__"
&& matches!(current.kind, ScopeKind::Module)
&& parent
.map(|stmt| {
matches!(stmt.node, StmtKind::Assign { .. })
|| matches!(stmt.node, StmtKind::AugAssign { .. })
|| matches!(stmt.node, StmtKind::AnnAssign { .. })
})
.unwrap_or_default()
if matches!(parent.node, StmtKind::AnnAssign { value: None, .. }) {
self.add_binding(
id.to_string(),
Binding {
kind: BindingKind::Annotation,
used: None,
location: expr.location,
},
);
return;
}
// TODO(charlie): Include comprehensions here.
if matches!(
parent.node,
StmtKind::For { .. } | StmtKind::AsyncFor { .. }
) || operations::is_unpacking_assignment(parent)
{
self.add_binding(
id.to_string(),
Binding {
kind: BindingKind::Export(extract_all_names(parent.unwrap(), current)),
kind: BindingKind::Binding,
used: None,
location: expr.location,
},
);
} else {
return;
}
if id == "__all__"
&& matches!(current.kind, ScopeKind::Module)
&& matches!(
parent.node,
StmtKind::Assign { .. }
| StmtKind::AugAssign { .. }
| StmtKind::AnnAssign { .. }
)
{
self.add_binding(
id.to_string(),
Binding {
kind: BindingKind::Assignment,
kind: BindingKind::Export(extract_all_names(parent, current)),
used: None,
location: expr.location,
},
);
return;
}
self.add_binding(
id.to_string(),
Binding {
kind: BindingKind::Assignment,
used: None,
location: expr.location,
},
);
}
}
@@ -1021,15 +1232,27 @@ impl<'a> Checker<'a> {
}
}
fn check_deferred_annotations<'b>(&mut self, path: &str, allocator: &'b mut Vec<Expr>)
fn check_deferred_annotations(&mut self) {
while let Some((expr, scopes, parents)) = self.deferred_annotations.pop() {
self.parent_stack = parents;
self.scope_stack = scopes;
self.visit_expr(expr);
}
}
fn check_deferred_string_annotations<'b>(&mut self, path: &str, allocator: &'b mut Vec<Expr>)
where
'b: 'a,
{
while !self.deferred_annotations.is_empty() {
let (location, expression) = self.deferred_annotations.pop().unwrap();
while let Some((location, expression)) = self.deferred_string_annotations.pop() {
if let Ok(mut expr) = parser::parse_expression(expression, path) {
relocate_expr(&mut expr, location);
allocator.push(expr);
} else if self.settings.select.contains(&CheckCode::F722) {
self.checks.push(Check::new(
CheckKind::ForwardAnnotationSyntaxError(expression.to_string()),
location,
));
}
}
for expr in allocator {
@@ -1038,9 +1261,7 @@ impl<'a> Checker<'a> {
}
fn check_deferred_functions(&mut self) {
while !self.deferred_functions.is_empty() {
let (stmt, scopes, parents) = self.deferred_functions.pop().unwrap();
while let Some((stmt, scopes, parents)) = self.deferred_functions.pop() {
self.parent_stack = parents;
self.scope_stack = scopes;
self.push_scope(Scope::new(ScopeKind::Function));
@@ -1064,9 +1285,7 @@ impl<'a> Checker<'a> {
}
fn check_deferred_lambdas(&mut self) {
while !self.deferred_lambdas.is_empty() {
let (expr, scopes, parents) = self.deferred_lambdas.pop().unwrap();
while let Some((expr, scopes, parents)) = self.deferred_lambdas.pop() {
self.parent_stack = parents;
self.scope_stack = scopes;
self.push_scope(Scope::new(ScopeKind::Function));
@@ -1084,8 +1303,7 @@ impl<'a> Checker<'a> {
}
fn check_deferred_assignments(&mut self) {
while !self.deferred_assignments.is_empty() {
let index = self.deferred_assignments.pop().unwrap();
while let Some(index) = self.deferred_assignments.pop() {
if self.settings.select.contains(&CheckCode::F841) {
self.checks
.extend(checks::check_unused_variables(&self.scopes[index]));
@@ -1100,7 +1318,7 @@ impl<'a> Checker<'a> {
return;
}
for index in self.dead_scopes.clone() {
for index in self.dead_scopes.iter().copied() {
let scope = &self.scopes[index];
let all_binding = scope.values.get("__all__");
@@ -1171,8 +1389,9 @@ pub fn check_ast(
checker.check_deferred_functions();
checker.check_deferred_lambdas();
checker.check_deferred_assignments();
checker.check_deferred_annotations();
let mut allocator = vec![];
checker.check_deferred_annotations(path, &mut allocator);
checker.check_deferred_string_annotations(path, &mut allocator);
// Reset the scope to module-level, and check all consumed scopes.
checker.scope_stack = vec![GLOBAL_SCOPE_INDEX];

View File

@@ -1,11 +1,11 @@
use rustpython_parser::ast::Location;
use crate::checks::{Check, CheckKind};
use crate::checks::{Check, CheckCode, CheckKind};
use crate::settings::Settings;
/// Whether the given line is too long and should be reported.
fn should_enforce_line_length(line: &str, limit: usize) -> bool {
if line.len() > limit {
fn should_enforce_line_length(line: &str, length: usize, limit: usize) -> bool {
if length > limit {
let mut chunks = line.split_whitespace();
if let (Some(first), Some(_)) = (chunks.next(), chunks.next()) {
// Do not enforce the line length for commented lines with a single word
@@ -20,7 +20,7 @@ fn should_enforce_line_length(line: &str, limit: usize) -> bool {
}
pub fn check_lines(checks: &mut Vec<Check>, contents: &str, settings: &Settings) {
let enforce_line_too_long = settings.select.contains(CheckKind::LineTooLong.code());
let enforce_line_too_long = settings.select.contains(&CheckCode::E501);
let mut line_checks = vec![];
let mut ignored = vec![];
@@ -34,13 +34,16 @@ pub fn check_lines(checks: &mut Vec<Check>, contents: &str, settings: &Settings)
}
// Enforce line length.
if enforce_line_too_long && should_enforce_line_length(line, settings.line_length) {
let check = Check::new(
CheckKind::LineTooLong,
Location::new(row + 1, settings.line_length + 1),
);
if !check.is_inline_ignored(line) {
line_checks.push(check);
if enforce_line_too_long {
let line_length = line.len();
if should_enforce_line_length(line, line_length, settings.line_length) {
let check = Check::new(
CheckKind::LineTooLong(line_length, settings.line_length),
Location::new(row + 1, settings.line_length + 1),
);
if !check.is_inline_ignored(line) {
line_checks.push(check);
}
}
}
}

View File

@@ -14,20 +14,34 @@ pub enum CheckCode {
E712,
E713,
E714,
E721,
E722,
E731,
E741,
E742,
E743,
E902,
E999,
F401,
F403,
F404,
F406,
F407,
F541,
F601,
F602,
F621,
F622,
F631,
F632,
F633,
F634,
F701,
F702,
F704,
F706,
F707,
F722,
F821,
F822,
F823,
@@ -49,20 +63,34 @@ impl FromStr for CheckCode {
"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),
"F401" => Ok(CheckCode::F401),
"F403" => Ok(CheckCode::F403),
"F404" => Ok(CheckCode::F404),
"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),
@@ -85,20 +113,34 @@ impl CheckCode {
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",
CheckCode::F401 => "F401",
CheckCode::F403 => "F403",
CheckCode::F404 => "F404",
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",
@@ -110,37 +152,12 @@ impl CheckCode {
}
}
/// The source for the check (either the AST, or the physical lines).
/// The source for the check (either the AST, the filesystem, or the physical lines).
pub fn lint_source(&self) -> &'static LintSource {
match self {
CheckCode::E402 => &LintSource::AST,
CheckCode::E501 => &LintSource::Lines,
CheckCode::E711 => &LintSource::AST,
CheckCode::E712 => &LintSource::AST,
CheckCode::E713 => &LintSource::AST,
CheckCode::E714 => &LintSource::AST,
CheckCode::E731 => &LintSource::AST,
CheckCode::E902 => &LintSource::FileSystem,
CheckCode::F401 => &LintSource::AST,
CheckCode::F403 => &LintSource::AST,
CheckCode::F541 => &LintSource::AST,
CheckCode::F601 => &LintSource::AST,
CheckCode::F602 => &LintSource::AST,
CheckCode::F621 => &LintSource::AST,
CheckCode::F622 => &LintSource::AST,
CheckCode::F631 => &LintSource::AST,
CheckCode::F634 => &LintSource::AST,
CheckCode::F704 => &LintSource::AST,
CheckCode::F706 => &LintSource::AST,
CheckCode::F707 => &LintSource::AST,
CheckCode::F821 => &LintSource::AST,
CheckCode::F822 => &LintSource::AST,
CheckCode::F823 => &LintSource::AST,
CheckCode::F831 => &LintSource::AST,
CheckCode::F841 => &LintSource::AST,
CheckCode::F901 => &LintSource::AST,
CheckCode::R001 => &LintSource::AST,
CheckCode::R002 => &LintSource::AST,
CheckCode::E902 | CheckCode::E999 => &LintSource::FileSystem,
_ => &LintSource::AST,
}
}
}
@@ -160,15 +177,27 @@ pub enum RejectedCmpop {
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CheckKind {
AmbiguousClassName(String),
AmbiguousFunctionName(String),
AmbiguousVariableName(String),
AssertTuple,
BreakOutsideLoop,
ContinueOutsideLoop,
DefaultExceptNotLast,
DoNotAssignLambda,
DoNotUseBareExcept,
DuplicateArgumentName,
ForwardAnnotationSyntaxError(String),
FStringMissingPlaceholders,
FutureFeatureNotDefined(String),
IOError(String),
IfTuple,
ImportStarUsage,
LineTooLong,
ImportStarNotPermitted(String),
ImportStarUsage(String),
InvalidPrintSyntax,
IsLiteral,
LateFutureImport,
LineTooLong(usize, usize),
ModuleImportNotAtTopOfFile,
MultiValueRepeatedKeyLiteral,
MultiValueRepeatedKeyVariable(String),
@@ -178,9 +207,11 @@ pub enum CheckKind {
NotIsTest,
RaiseNotImplemented,
ReturnOutsideFunction,
SyntaxError(String),
TooManyExpressionsInStarredAssignment,
TrueFalseComparison(bool, RejectedCmpop),
TwoStarredExpressions,
TypeComparison,
UndefinedExport(String),
UndefinedLocal(String),
UndefinedName(String),
@@ -194,15 +225,27 @@ impl CheckKind {
/// The name of the check.
pub fn name(&self) -> &'static str {
match self {
CheckKind::AmbiguousClassName(_) => "AmbiguousClassName",
CheckKind::AmbiguousFunctionName(_) => "AmbiguousFunctionName",
CheckKind::AmbiguousVariableName(_) => "AmbiguousVariableName",
CheckKind::AssertTuple => "AssertTuple",
CheckKind::BreakOutsideLoop => "BreakOutsideLoop",
CheckKind::ContinueOutsideLoop => "ContinueOutsideLoop",
CheckKind::DefaultExceptNotLast => "DefaultExceptNotLast",
CheckKind::DoNotAssignLambda => "DoNotAssignLambda",
CheckKind::DoNotUseBareExcept => "DoNotUseBareExcept",
CheckKind::DuplicateArgumentName => "DuplicateArgumentName",
CheckKind::FStringMissingPlaceholders => "FStringMissingPlaceholders",
CheckKind::ForwardAnnotationSyntaxError(_) => "ForwardAnnotationSyntaxError",
CheckKind::FutureFeatureNotDefined(_) => "FutureFeatureNotDefined",
CheckKind::IOError(_) => "IOError",
CheckKind::IfTuple => "IfTuple",
CheckKind::ImportStarUsage => "ImportStarUsage",
CheckKind::LineTooLong => "LineTooLong",
CheckKind::DoNotAssignLambda => "DoNotAssignLambda",
CheckKind::ImportStarNotPermitted(_) => "ImportStarNotPermitted",
CheckKind::ImportStarUsage(_) => "ImportStarUsage",
CheckKind::InvalidPrintSyntax => "InvalidPrintSyntax",
CheckKind::IsLiteral => "IsLiteral",
CheckKind::LateFutureImport => "LateFutureImport",
CheckKind::LineTooLong(_, _) => "LineTooLong",
CheckKind::ModuleImportNotAtTopOfFile => "ModuleImportNotAtTopOfFile",
CheckKind::MultiValueRepeatedKeyLiteral => "MultiValueRepeatedKeyLiteral",
CheckKind::MultiValueRepeatedKeyVariable(_) => "MultiValueRepeatedKeyVariable",
@@ -212,11 +255,13 @@ impl CheckKind {
CheckKind::NotIsTest => "NotIsTest",
CheckKind::RaiseNotImplemented => "RaiseNotImplemented",
CheckKind::ReturnOutsideFunction => "ReturnOutsideFunction",
CheckKind::SyntaxError(_) => "SyntaxError",
CheckKind::TooManyExpressionsInStarredAssignment => {
"TooManyExpressionsInStarredAssignment"
}
CheckKind::TrueFalseComparison(_, _) => "TrueFalseComparison",
CheckKind::TwoStarredExpressions => "TwoStarredExpressions",
CheckKind::TypeComparison => "TypeComparison",
CheckKind::UndefinedExport(_) => "UndefinedExport",
CheckKind::UndefinedLocal(_) => "UndefinedLocal",
CheckKind::UndefinedName(_) => "UndefinedName",
@@ -230,15 +275,27 @@ impl CheckKind {
/// A four-letter shorthand code for the check.
pub fn code(&self) -> &'static CheckCode {
match self {
CheckKind::AmbiguousClassName(_) => &CheckCode::E742,
CheckKind::AmbiguousFunctionName(_) => &CheckCode::E743,
CheckKind::AmbiguousVariableName(_) => &CheckCode::E741,
CheckKind::AssertTuple => &CheckCode::F631,
CheckKind::BreakOutsideLoop => &CheckCode::F701,
CheckKind::ContinueOutsideLoop => &CheckCode::F702,
CheckKind::DefaultExceptNotLast => &CheckCode::F707,
CheckKind::DoNotAssignLambda => &CheckCode::E731,
CheckKind::DoNotUseBareExcept => &CheckCode::E722,
CheckKind::DuplicateArgumentName => &CheckCode::F831,
CheckKind::FStringMissingPlaceholders => &CheckCode::F541,
CheckKind::ForwardAnnotationSyntaxError(_) => &CheckCode::F722,
CheckKind::FutureFeatureNotDefined(_) => &CheckCode::F407,
CheckKind::IOError(_) => &CheckCode::E902,
CheckKind::IfTuple => &CheckCode::F634,
CheckKind::ImportStarUsage => &CheckCode::F403,
CheckKind::LineTooLong => &CheckCode::E501,
CheckKind::DoNotAssignLambda => &CheckCode::E731,
CheckKind::ImportStarNotPermitted(_) => &CheckCode::F406,
CheckKind::ImportStarUsage(_) => &CheckCode::F403,
CheckKind::InvalidPrintSyntax => &CheckCode::F633,
CheckKind::IsLiteral => &CheckCode::F632,
CheckKind::LateFutureImport => &CheckCode::F404,
CheckKind::LineTooLong(_, _) => &CheckCode::E501,
CheckKind::ModuleImportNotAtTopOfFile => &CheckCode::E402,
CheckKind::MultiValueRepeatedKeyLiteral => &CheckCode::F601,
CheckKind::MultiValueRepeatedKeyVariable(_) => &CheckCode::F602,
@@ -248,9 +305,11 @@ impl CheckKind {
CheckKind::NotIsTest => &CheckCode::E714,
CheckKind::RaiseNotImplemented => &CheckCode::F901,
CheckKind::ReturnOutsideFunction => &CheckCode::F706,
CheckKind::SyntaxError(_) => &CheckCode::E999,
CheckKind::TooManyExpressionsInStarredAssignment => &CheckCode::F621,
CheckKind::TrueFalseComparison(_, _) => &CheckCode::E712,
CheckKind::TwoStarredExpressions => &CheckCode::F622,
CheckKind::TypeComparison => &CheckCode::E721,
CheckKind::UndefinedExport(_) => &CheckCode::F822,
CheckKind::UndefinedLocal(_) => &CheckCode::F823,
CheckKind::UndefinedName(_) => &CheckCode::F821,
@@ -264,26 +323,56 @@ impl CheckKind {
/// The body text for the check.
pub fn body(&self) -> String {
match self {
CheckKind::AmbiguousClassName(name) => {
format!("ambiguous class name '{}'", name)
}
CheckKind::AmbiguousFunctionName(name) => {
format!("ambiguous function name '{}'", name)
}
CheckKind::AmbiguousVariableName(name) => {
format!("ambiguous variable name '{}'", name)
}
CheckKind::AssertTuple => {
"Assert test is a non-empty tuple, which is always `True`".to_string()
}
CheckKind::BreakOutsideLoop => "`break` outside loop".to_string(),
CheckKind::ContinueOutsideLoop => "`continue` not properly in loop".to_string(),
CheckKind::DefaultExceptNotLast => {
"an `except:` block as not the last exception handler".to_string()
}
CheckKind::DoNotAssignLambda => {
"Do not assign a lambda expression, use a def".to_string()
}
CheckKind::DoNotUseBareExcept => "Do not use bare `except`".to_string(),
CheckKind::DuplicateArgumentName => {
"Duplicate argument name in function definition".to_string()
}
CheckKind::ForwardAnnotationSyntaxError(body) => {
format!("syntax error in forward annotation '{body}'")
}
CheckKind::FStringMissingPlaceholders => {
"f-string without any placeholders".to_string()
}
CheckKind::FutureFeatureNotDefined(name) => {
format!("future feature '{name}' is not defined")
}
CheckKind::IOError(name) => {
format!("No such file or directory: `{name}`")
}
CheckKind::IfTuple => "If test is a tuple, which is always `True`".to_string(),
CheckKind::ImportStarUsage => "Unable to detect undefined names".to_string(),
CheckKind::LineTooLong => "Line too long".to_string(),
CheckKind::DoNotAssignLambda => {
"Do not assign a lambda expression, use a def".to_string()
CheckKind::InvalidPrintSyntax => "use of >> is invalid with print function".to_string(),
CheckKind::ImportStarNotPermitted(name) => {
format!("`from {name} import *` only allowed at module level")
}
CheckKind::ImportStarUsage(name) => {
format!("`from {name} import *` used; unable to detect undefined names")
}
CheckKind::IsLiteral => "use ==/!= to compare constant literals".to_string(),
CheckKind::LateFutureImport => {
"from __future__ imports must occur at the beginning of the file".to_string()
}
CheckKind::LineTooLong(length, limit) => {
format!("Line too long ({length} > {limit} characters)")
}
CheckKind::ModuleImportNotAtTopOfFile => {
"Module level import not at top of file".to_string()
@@ -311,6 +400,7 @@ impl CheckKind {
CheckKind::ReturnOutsideFunction => {
"a `return` statement outside of a function/method".to_string()
}
CheckKind::SyntaxError(message) => format!("SyntaxError: {message}"),
CheckKind::TooManyExpressionsInStarredAssignment => {
"too many expressions in star-unpacking assignment".to_string()
}
@@ -333,15 +423,16 @@ impl CheckKind {
},
},
CheckKind::TwoStarredExpressions => "two starred expressions in assignment".to_string(),
CheckKind::TypeComparison => "do not compare types, use `isinstance()`".to_string(),
CheckKind::UndefinedExport(name) => {
format!("Undefined name `{name}` in `__all__`")
}
CheckKind::UndefinedName(name) => {
format!("Undefined name `{name}`")
}
CheckKind::UndefinedLocal(name) => {
format!("Local variable `{name}` referenced before assignment")
}
CheckKind::UndefinedName(name) => {
format!("Undefined name `{name}`")
}
CheckKind::UnusedImport(name) => format!("`{name}` imported but unused"),
CheckKind::UnusedVariable(name) => {
format!("Local variable `{name}` is assigned to but never used")
@@ -357,36 +448,10 @@ impl CheckKind {
/// Whether the check kind is (potentially) fixable.
pub fn fixable(&self) -> bool {
match self {
CheckKind::AssertTuple => false,
CheckKind::DefaultExceptNotLast => false,
CheckKind::DuplicateArgumentName => false,
CheckKind::FStringMissingPlaceholders => false,
CheckKind::IOError(_) => false,
CheckKind::IfTuple => false,
CheckKind::ImportStarUsage => false,
CheckKind::DoNotAssignLambda => false,
CheckKind::LineTooLong => false,
CheckKind::ModuleImportNotAtTopOfFile => false,
CheckKind::MultiValueRepeatedKeyLiteral => false,
CheckKind::MultiValueRepeatedKeyVariable(_) => false,
CheckKind::NoAssertEquals => true,
CheckKind::NotInTest => false,
CheckKind::NotIsTest => false,
CheckKind::NoneComparison(_) => false,
CheckKind::RaiseNotImplemented => false,
CheckKind::ReturnOutsideFunction => false,
CheckKind::TooManyExpressionsInStarredAssignment => false,
CheckKind::TrueFalseComparison(_, _) => false,
CheckKind::TwoStarredExpressions => false,
CheckKind::UndefinedExport(_) => false,
CheckKind::UndefinedLocal(_) => false,
CheckKind::UndefinedName(_) => false,
CheckKind::UnusedImport(_) => false,
CheckKind::UnusedVariable(_) => false,
CheckKind::UselessObjectInheritance(_) => true,
CheckKind::YieldOutsideFunction => false,
}
matches!(
self,
CheckKind::NoAssertEquals | CheckKind::UselessObjectInheritance(_)
)
}
}

View File

@@ -4,34 +4,60 @@ use std::path::{Path, PathBuf};
use anyhow::Result;
use glob::Pattern;
use log::debug;
use walkdir::{DirEntry, WalkDir};
fn is_not_hidden(entry: &DirEntry) -> bool {
entry
.file_name()
.to_str()
.map(|s| (entry.depth() == 0 || !s.starts_with('.')))
.unwrap_or(false)
fn is_excluded(path: &Path, exclude: &[Pattern]) -> bool {
if let Some(file_name) = path.file_name() {
if let Some(file_name) = file_name.to_str() {
for pattern in exclude {
if pattern.matches(file_name) {
return true;
}
}
false
} else {
false
}
} else {
false
}
}
fn is_not_excluded(entry: &DirEntry, exclude: &[Pattern]) -> bool {
entry
.path()
.to_str()
.map(|s| !exclude.iter().any(|pattern| pattern.matches(s)))
.unwrap_or(false)
fn is_included(path: &Path) -> bool {
let file_name = path.to_string_lossy();
file_name.ends_with(".py") || file_name.ends_with(".pyi")
}
pub fn iter_python_files<'a>(
path: &'a PathBuf,
exclude: &'a [Pattern],
extend_exclude: &'a [Pattern],
) -> impl Iterator<Item = DirEntry> + 'a {
WalkDir::new(path)
.follow_links(true)
.into_iter()
.filter_entry(|entry| is_not_hidden(entry) && is_not_excluded(entry, exclude))
.filter_entry(|entry| {
if exclude.is_empty() && extend_exclude.is_empty() {
return true;
}
let path = entry.path();
if is_excluded(path, exclude) {
debug!("Ignored path via `exclude`: {:?}", path);
false
} else if is_excluded(path, extend_exclude) {
debug!("Ignored path via `extend-exclude`: {:?}", path);
false
} else {
true
}
})
.filter_map(|entry| entry.ok())
.filter(|entry| entry.path().to_string_lossy().ends_with(".py"))
.filter(|entry| {
let path = entry.path();
is_included(path)
})
}
pub fn read_file(path: &Path) -> Result<String> {
@@ -41,3 +67,46 @@ pub fn read_file(path: &Path) -> Result<String> {
buf_reader.read_to_string(&mut contents)?;
Ok(contents)
}
#[cfg(test)]
mod tests {
use std::path::Path;
use glob::Pattern;
use crate::fs::{is_excluded, is_included};
#[test]
fn inclusions() {
let path = Path::new("foo/bar/baz.py");
assert!(is_included(path));
let path = Path::new("foo/bar/baz.pyi");
assert!(is_included(path));
let path = Path::new("foo/bar/baz.js");
assert!(!is_included(path));
let path = Path::new("foo/bar/baz");
assert!(!is_included(path));
}
#[test]
fn exclusions() {
let path = Path::new("foo");
let exclude = vec![Pattern::new("foo").unwrap()];
assert!(is_excluded(path, &exclude));
let path = Path::new("foo/bar");
let exclude = vec![Pattern::new("bar").unwrap()];
assert!(is_excluded(path, &exclude));
let path = Path::new("foo/bar/baz.py");
let exclude = vec![Pattern::new("baz.py").unwrap()];
assert!(is_excluded(path, &exclude));
let path = Path::new("foo/bar/baz.py");
let exclude = vec![Pattern::new("baz").unwrap()];
assert!(!is_excluded(path, &exclude));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::process::ExitCode;
use std::sync::mpsc::channel;
use std::time::Instant;
@@ -54,9 +54,12 @@ struct Cli {
/// List of error codes to ignore.
#[clap(long, multiple = true)]
ignore: Vec<CheckCode>,
/// List of file and/or directory patterns to exclude from checks.
/// List of paths, used to exclude files and/or directories from checks.
#[clap(long, multiple = true)]
exclude: Vec<Pattern>,
/// Like --exclude, but adds additional files and directories on top of the excluded ones.
#[clap(long, multiple = true)]
extend_exclude: Vec<Pattern>,
}
#[cfg(feature = "update-informer")]
@@ -93,7 +96,7 @@ fn run_once(
let start = Instant::now();
let paths: Vec<DirEntry> = files
.iter()
.flat_map(|path| iter_python_files(path, &settings.exclude))
.flat_map(|path| iter_python_files(path, &settings.exclude, &settings.extend_exclude))
.collect();
let duration = start.elapsed();
debug!("Identified files to lint in: {:?}", duration);
@@ -103,8 +106,17 @@ fn run_once(
.par_iter()
.map(|entry| {
lint_path(entry.path(), settings, &cache.into(), &autofix.into()).unwrap_or_else(|e| {
error!("Failed to check {}: {e:?}", entry.path().to_string_lossy());
vec![]
if settings.select.contains(&CheckCode::E999) {
vec![Message {
kind: CheckKind::SyntaxError(e.to_string()),
fixed: false,
location: Default::default(),
filename: entry.path().to_string_lossy().to_string(),
}]
} else {
error!("Failed to check {}: {e:?}", entry.path().to_string_lossy());
vec![]
}
})
})
.flatten()
@@ -183,9 +195,7 @@ fn inner_main() -> Result<ExitCode> {
set_up_logging(cli.verbose)?;
// TODO(charlie): Can we avoid this cast?
let paths: Vec<&Path> = cli.files.iter().map(PathBuf::as_path).collect();
let mut settings = Settings::from_paths(paths)?;
let mut settings = Settings::from_paths(&cli.files);
if !cli.select.is_empty() {
settings.select(cli.select);
}
@@ -193,7 +203,10 @@ fn inner_main() -> Result<ExitCode> {
settings.ignore(&cli.ignore);
}
if !cli.exclude.is_empty() {
settings.exclude(cli.exclude);
settings.exclude = cli.exclude;
}
if !cli.extend_exclude.is_empty() {
settings.extend_exclude = cli.extend_exclude;
}
if cli.watch {

View File

@@ -8,29 +8,26 @@ use serde::Deserialize;
use crate::checks::CheckCode;
use crate::fs;
pub fn load_config<'a>(paths: impl IntoIterator<Item = &'a Path>) -> Result<(PathBuf, Config)> {
pub fn load_config(paths: &[PathBuf]) -> Config {
match find_project_root(paths) {
Some(project_root) => match find_pyproject_toml(&project_root) {
Some(path) => {
debug!("Found pyproject.toml at: {}", path.to_string_lossy());
debug!("Found pyproject.toml at: {:?}", path);
match parse_pyproject_toml(&path) {
Ok(pyproject) => {
let config = pyproject
.tool
.and_then(|tool| tool.ruff)
.unwrap_or_default();
Ok((project_root, config))
}
Ok(pyproject) => pyproject
.tool
.and_then(|tool| tool.ruff)
.unwrap_or_default(),
Err(e) => {
println!("Failed to load pyproject.toml: {:?}", e);
println!("Falling back to default configuration...");
Ok(Default::default())
Default::default()
}
}
}
None => Ok(Default::default()),
None => Default::default(),
},
None => Ok(Default::default()),
None => Default::default(),
}
}
@@ -39,6 +36,7 @@ pub fn load_config<'a>(paths: impl IntoIterator<Item = &'a Path>) -> Result<(Pat
pub struct Config {
pub line_length: Option<usize>,
pub exclude: Option<Vec<PathBuf>>,
pub extend_exclude: Option<Vec<PathBuf>>,
pub select: Option<Vec<CheckCode>>,
pub ignore: Option<Vec<CheckCode>>,
}
@@ -70,8 +68,8 @@ fn find_user_pyproject_toml() -> Option<PathBuf> {
dirs::home_dir().map(|path| path.join(".ruff"))
}
fn find_project_root<'a>(sources: impl IntoIterator<Item = &'a Path>) -> Option<PathBuf> {
if let Some(prefix) = common_path_all(sources) {
fn find_project_root(sources: &[PathBuf]) -> Option<PathBuf> {
if let Some(prefix) = common_path_all(sources.iter().map(PathBuf::as_path)) {
for directory in prefix.ancestors() {
if directory.join(".git").is_dir() {
return Some(directory.to_path_buf());
@@ -90,7 +88,7 @@ fn find_project_root<'a>(sources: impl IntoIterator<Item = &'a Path>) -> Option<
#[cfg(test)]
mod tests {
use std::path::Path;
use std::path::{Path, PathBuf};
use anyhow::Result;
@@ -123,6 +121,7 @@ mod tests {
ruff: Some(Config {
line_length: None,
exclude: None,
extend_exclude: None,
select: None,
ignore: None,
})
@@ -142,6 +141,7 @@ line-length = 79
ruff: Some(Config {
line_length: Some(79),
exclude: None,
extend_exclude: None,
select: None,
ignore: None,
})
@@ -161,6 +161,7 @@ exclude = ["foo.py"]
ruff: Some(Config {
line_length: None,
exclude: Some(vec![Path::new("foo.py").to_path_buf()]),
extend_exclude: None,
select: None,
ignore: None,
})
@@ -180,6 +181,7 @@ select = ["E501"]
ruff: Some(Config {
line_length: None,
exclude: None,
extend_exclude: None,
select: Some(vec![CheckCode::E501]),
ignore: None,
})
@@ -199,6 +201,7 @@ ignore = ["E501"]
ruff: Some(Config {
line_length: None,
exclude: None,
extend_exclude: None,
select: None,
ignore: Some(vec![CheckCode::E501]),
})
@@ -238,8 +241,9 @@ other-attribute = 1
#[test]
fn find_and_parse_pyproject_toml() -> Result<()> {
let project_root = find_project_root([Path::new("resources/test/fixtures/__init__.py")])
.expect("Unable to find project root.");
let project_root =
find_project_root(&[PathBuf::from("resources/test/fixtures/__init__.py")])
.expect("Unable to find project root.");
assert_eq!(project_root, Path::new("resources/test/fixtures"));
let path = find_pyproject_toml(&project_root).expect("Unable to find pyproject.toml.");
@@ -254,9 +258,10 @@ other-attribute = 1
config,
Config {
line_length: Some(88),
exclude: Some(vec![
exclude: None,
extend_exclude: Some(vec![
Path::new("excluded.py").to_path_buf(),
Path::new("**/migrations").to_path_buf()
Path::new("migrations").to_path_buf()
]),
select: Some(vec![
CheckCode::E402,
@@ -265,20 +270,34 @@ other-attribute = 1
CheckCode::E712,
CheckCode::E713,
CheckCode::E714,
CheckCode::E721,
CheckCode::E722,
CheckCode::E731,
CheckCode::E741,
CheckCode::E742,
CheckCode::E743,
CheckCode::E902,
CheckCode::E999,
CheckCode::F401,
CheckCode::F403,
CheckCode::F404,
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,

View File

@@ -1,2 +1,3 @@
pub mod builtins;
pub mod future;
pub mod typing;

13
src/python/future.rs Normal file
View File

@@ -0,0 +1,13 @@
/// A copy of `__future__.all_feature_names`.
pub const ALL_FEATURE_NAMES: &[&str] = &[
"nested_scopes",
"generators",
"division",
"absolute_import",
"with_statement",
"print_function",
"unicode_literals",
"barry_as_FLUFL",
"generator_stop",
"annotations",
];

View File

@@ -1,8 +1,8 @@
use lazy_static::lazy_static;
use once_cell::sync::Lazy;
use std::collections::BTreeSet;
lazy_static! {
static ref ANNOTATED_SUBSCRIPTS: BTreeSet<&'static str> = BTreeSet::from([
static ANNOTATED_SUBSCRIPTS: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
BTreeSet::from([
"AbstractAsyncContextManager",
"AbstractContextManager",
"AbstractSet",
@@ -80,8 +80,8 @@ lazy_static! {
"set",
"tuple",
"type",
]);
}
])
});
pub fn is_annotated_subscript(name: &str) -> bool {
ANNOTATED_SUBSCRIPTS.contains(name)

View File

@@ -1,9 +1,9 @@
use std::collections::BTreeSet;
use std::hash::{Hash, Hasher};
use std::path::Path;
use std::path::PathBuf;
use anyhow::Result;
use glob::Pattern;
use once_cell::sync::Lazy;
use crate::checks::CheckCode;
use crate::pyproject::load_config;
@@ -12,6 +12,7 @@ use crate::pyproject::load_config;
pub struct Settings {
pub line_length: usize,
pub exclude: Vec<Pattern>,
pub extend_exclude: Vec<Pattern>,
pub select: BTreeSet<CheckCode>,
}
@@ -23,25 +24,56 @@ impl Hash for Settings {
}
}
}
static DEFAULT_EXCLUDE: Lazy<Vec<Pattern>> = Lazy::new(|| {
vec![
Pattern::new(".bzr").unwrap(),
Pattern::new(".direnv").unwrap(),
Pattern::new(".eggs").unwrap(),
Pattern::new(".git").unwrap(),
Pattern::new(".hg").unwrap(),
Pattern::new(".mypy_cache").unwrap(),
Pattern::new(".nox").unwrap(),
Pattern::new(".pants.d").unwrap(),
Pattern::new(".svn").unwrap(),
Pattern::new(".tox").unwrap(),
Pattern::new(".venv").unwrap(),
Pattern::new("__pypackages__").unwrap(),
Pattern::new("_build").unwrap(),
Pattern::new("buck-out").unwrap(),
Pattern::new("build").unwrap(),
Pattern::new("dist").unwrap(),
Pattern::new("node_modules").unwrap(),
Pattern::new("venv").unwrap(),
]
});
impl Settings {
pub fn from_paths<'a>(paths: impl IntoIterator<Item = &'a Path>) -> Result<Self> {
let (project_root, config) = load_config(paths)?;
pub fn from_paths(paths: &[PathBuf]) -> Self {
let config = load_config(paths);
let mut settings = Settings {
line_length: config.line_length.unwrap_or(88),
exclude: config
.exclude
.unwrap_or_default()
.into_iter()
.map(|path| {
if path.is_relative() {
project_root.join(path)
} else {
path
}
.map(|paths| {
paths
.into_iter()
.map(|path| {
Pattern::new(&path.to_string_lossy()).expect("Invalid pattern.")
})
.collect()
})
.map(|path| Pattern::new(&path.to_string_lossy()).expect("Invalid pattern."))
.collect(),
.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()),
extend_exclude: config
.extend_exclude
.map(|paths| {
paths
.into_iter()
.map(|path| {
Pattern::new(&path.to_string_lossy()).expect("Invalid pattern.")
})
.collect()
})
.unwrap_or_default(),
select: BTreeSet::from_iter(config.select.unwrap_or_else(|| {
vec![
CheckCode::E402,
@@ -50,20 +82,33 @@ impl Settings {
CheckCode::E712,
CheckCode::E713,
CheckCode::E714,
CheckCode::E721,
CheckCode::E722,
CheckCode::E731,
CheckCode::E741,
CheckCode::E742,
CheckCode::E743,
CheckCode::E902,
CheckCode::E999,
CheckCode::F401,
CheckCode::F403,
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,
@@ -79,7 +124,7 @@ impl Settings {
if let Some(ignore) = &config.ignore {
settings.ignore(ignore);
}
Ok(settings)
settings
}
pub fn select(&mut self, codes: Vec<CheckCode>) {
@@ -94,8 +139,4 @@ impl Settings {
self.select.remove(code);
}
}
pub fn exclude(&mut self, exclude: Vec<Pattern>) {
self.exclude = exclude;
}
}

View File

@@ -0,0 +1,10 @@
---
source: src/linter.rs
expression: checks
---
- kind: ModuleImportNotAtTopOfFile
location:
row: 20
column: 1
fix: ~

View File

@@ -0,0 +1,13 @@
---
source: src/linter.rs
expression: checks
---
- kind:
LineTooLong:
- 123
- 88
location:
row: 5
column: 89
fix: ~

View File

@@ -0,0 +1,53 @@
---
source: src/linter.rs
expression: checks
---
- kind:
NoneComparison: Eq
location:
row: 2
column: 11
fix: ~
- kind:
NoneComparison: NotEq
location:
row: 5
column: 11
fix: ~
- kind:
NoneComparison: Eq
location:
row: 8
column: 4
fix: ~
- kind:
NoneComparison: NotEq
location:
row: 11
column: 4
fix: ~
- kind:
NoneComparison: Eq
location:
row: 14
column: 14
fix: ~
- kind:
NoneComparison: NotEq
location:
row: 17
column: 14
fix: ~
- kind:
NoneComparison: NotEq
location:
row: 20
column: 4
fix: ~
- kind:
NoneComparison: Eq
location:
row: 23
column: 4
fix: ~

View File

@@ -0,0 +1,77 @@
---
source: src/linter.rs
expression: checks
---
- kind:
TrueFalseComparison:
- true
- Eq
location:
row: 2
column: 11
fix: ~
- kind:
TrueFalseComparison:
- false
- NotEq
location:
row: 5
column: 11
fix: ~
- kind:
TrueFalseComparison:
- true
- NotEq
location:
row: 8
column: 4
fix: ~
- kind:
TrueFalseComparison:
- false
- Eq
location:
row: 11
column: 4
fix: ~
- kind:
TrueFalseComparison:
- true
- Eq
location:
row: 14
column: 14
fix: ~
- kind:
TrueFalseComparison:
- false
- NotEq
location:
row: 17
column: 14
fix: ~
- kind:
TrueFalseComparison:
- true
- Eq
location:
row: 20
column: 20
fix: ~
- kind:
TrueFalseComparison:
- false
- Eq
location:
row: 20
column: 44
fix: ~
- kind:
TrueFalseComparison:
- true
- Eq
location:
row: 22
column: 5
fix: ~

View File

@@ -0,0 +1,30 @@
---
source: src/linter.rs
expression: checks
---
- kind: NotInTest
location:
row: 2
column: 10
fix: ~
- kind: NotInTest
location:
row: 5
column: 12
fix: ~
- kind: NotInTest
location:
row: 8
column: 10
fix: ~
- kind: NotInTest
location:
row: 11
column: 25
fix: ~
- kind: NotInTest
location:
row: 14
column: 11
fix: ~

View File

@@ -0,0 +1,20 @@
---
source: src/linter.rs
expression: checks
---
- kind: NotIsTest
location:
row: 2
column: 10
fix: ~
- kind: NotIsTest
location:
row: 5
column: 12
fix: ~
- kind: NotIsTest
location:
row: 8
column: 10
fix: ~

View File

@@ -0,0 +1,85 @@
---
source: src/linter.rs
expression: checks
---
- kind: TypeComparison
location:
row: 2
column: 14
fix: ~
- kind: TypeComparison
location:
row: 5
column: 14
fix: ~
- kind: TypeComparison
location:
row: 10
column: 8
fix: ~
- kind: TypeComparison
location:
row: 15
column: 14
fix: ~
- kind: TypeComparison
location:
row: 18
column: 18
fix: ~
- kind: TypeComparison
location:
row: 18
column: 46
fix: ~
- kind: TypeComparison
location:
row: 20
column: 18
fix: ~
- kind: TypeComparison
location:
row: 22
column: 18
fix: ~
- kind: TypeComparison
location:
row: 24
column: 18
fix: ~
- kind: TypeComparison
location:
row: 26
column: 18
fix: ~
- kind: TypeComparison
location:
row: 28
column: 18
fix: ~
- kind: TypeComparison
location:
row: 30
column: 18
fix: ~
- kind: TypeComparison
location:
row: 32
column: 18
fix: ~
- kind: TypeComparison
location:
row: 34
column: 18
fix: ~
- kind: TypeComparison
location:
row: 40
column: 18
fix: ~
- kind: TypeComparison
location:
row: 42
column: 18
fix: ~

View File

@@ -0,0 +1,20 @@
---
source: src/linter.rs
expression: checks
---
- kind: DoNotUseBareExcept
location:
row: 4
column: 1
fix: ~
- kind: DoNotUseBareExcept
location:
row: 11
column: 1
fix: ~
- kind: DoNotUseBareExcept
location:
row: 16
column: 1
fix: ~

View File

@@ -0,0 +1,30 @@
---
source: src/linter.rs
expression: checks
---
- kind: DoNotAssignLambda
location:
row: 2
column: 1
fix: ~
- kind: DoNotAssignLambda
location:
row: 4
column: 1
fix: ~
- kind: DoNotAssignLambda
location:
row: 7
column: 5
fix: ~
- kind: DoNotAssignLambda
location:
row: 12
column: 1
fix: ~
- kind: DoNotAssignLambda
location:
row: 16
column: 1
fix: ~

View File

@@ -0,0 +1,155 @@
---
source: src/linter.rs
expression: checks
---
- kind:
AmbiguousVariableName: l
location:
row: 3
column: 1
fix: ~
- kind:
AmbiguousVariableName: I
location:
row: 4
column: 1
fix: ~
- kind:
AmbiguousVariableName: O
location:
row: 5
column: 1
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 6
column: 1
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 8
column: 4
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 9
column: 5
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 10
column: 5
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 11
column: 5
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 16
column: 5
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 20
column: 8
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 25
column: 5
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 26
column: 5
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 30
column: 5
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 33
column: 9
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 34
column: 9
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 40
column: 8
fix: ~
- kind:
AmbiguousVariableName: I
location:
row: 40
column: 14
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 44
column: 8
fix: ~
- kind:
AmbiguousVariableName: I
location:
row: 44
column: 16
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 48
column: 9
fix: ~
- kind:
AmbiguousVariableName: I
location:
row: 48
column: 14
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 57
column: 16
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 66
column: 20
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 71
column: 1
fix: ~
- kind:
AmbiguousVariableName: l
location:
row: 74
column: 5
fix: ~

View File

@@ -0,0 +1,23 @@
---
source: src/linter.rs
expression: checks
---
- kind:
AmbiguousClassName: l
location:
row: 1
column: 1
fix: ~
- kind:
AmbiguousClassName: I
location:
row: 5
column: 1
fix: ~
- kind:
AmbiguousClassName: O
location:
row: 9
column: 1
fix: ~

View File

@@ -0,0 +1,23 @@
---
source: src/linter.rs
expression: checks
---
- kind:
AmbiguousFunctionName: l
location:
row: 1
column: 1
fix: ~
- kind:
AmbiguousFunctionName: I
location:
row: 5
column: 1
fix: ~
- kind:
AmbiguousFunctionName: O
location:
row: 10
column: 5
fix: ~

View File

@@ -0,0 +1,23 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UnusedImport: functools
location:
row: 3
column: 1
fix: ~
- kind:
UnusedImport: collections.OrderedDict
location:
row: 5
column: 1
fix: ~
- kind:
UnusedImport: logging.handlers
location:
row: 13
column: 1
fix: ~

View File

@@ -0,0 +1,17 @@
---
source: src/linter.rs
expression: checks
---
- kind:
ImportStarUsage: F634
location:
row: 1
column: 1
fix: ~
- kind:
ImportStarUsage: F634
location:
row: 2
column: 1
fix: ~

View File

@@ -0,0 +1,10 @@
---
source: src/linter.rs
expression: checks
---
- kind: LateFutureImport
location:
row: 7
column: 1
fix: ~

View File

@@ -0,0 +1,17 @@
---
source: src/linter.rs
expression: checks
---
- kind:
ImportStarNotPermitted: F634
location:
row: 5
column: 5
fix: ~
- kind:
ImportStarNotPermitted: F634
location:
row: 9
column: 5
fix: ~

View File

@@ -0,0 +1,11 @@
---
source: src/linter.rs
expression: checks
---
- kind:
FutureFeatureNotDefined: non_existent_feature
location:
row: 2
column: 1
fix: ~

View File

@@ -0,0 +1,20 @@
---
source: src/linter.rs
expression: checks
---
- kind: FStringMissingPlaceholders
location:
row: 4
column: 7
fix: ~
- kind: FStringMissingPlaceholders
location:
row: 5
column: 7
fix: ~
- kind: FStringMissingPlaceholders
location:
row: 7
column: 7
fix: ~

View File

@@ -0,0 +1,20 @@
---
source: src/linter.rs
expression: checks
---
- kind: MultiValueRepeatedKeyLiteral
location:
row: 3
column: 6
fix: ~
- kind: MultiValueRepeatedKeyLiteral
location:
row: 9
column: 5
fix: ~
- kind: MultiValueRepeatedKeyLiteral
location:
row: 11
column: 7
fix: ~

View File

@@ -0,0 +1,11 @@
---
source: src/linter.rs
expression: checks
---
- kind:
MultiValueRepeatedKeyVariable: a
location:
row: 5
column: 5
fix: ~

View File

@@ -0,0 +1,10 @@
---
source: src/linter.rs
expression: checks
---
- kind: TwoStarredExpressions
location:
row: 1
column: 1
fix: ~

View File

@@ -0,0 +1,15 @@
---
source: src/linter.rs
expression: checks
---
- kind: AssertTuple
location:
row: 1
column: 1
fix: ~
- kind: AssertTuple
location:
row: 2
column: 1
fix: ~

View File

@@ -0,0 +1,15 @@
---
source: src/linter.rs
expression: checks
---
- kind: IsLiteral
location:
row: 1
column: 6
fix: ~
- kind: IsLiteral
location:
row: 4
column: 8
fix: ~

View File

@@ -0,0 +1,10 @@
---
source: src/linter.rs
expression: checks
---
- kind: InvalidPrintSyntax
location:
row: 4
column: 1
fix: ~

View File

@@ -0,0 +1,15 @@
---
source: src/linter.rs
expression: checks
---
- kind: IfTuple
location:
row: 1
column: 1
fix: ~
- kind: IfTuple
location:
row: 7
column: 5
fix: ~

View File

@@ -0,0 +1,25 @@
---
source: src/linter.rs
expression: checks
---
- kind: BreakOutsideLoop
location:
row: 4
column: 5
fix: ~
- kind: BreakOutsideLoop
location:
row: 16
column: 5
fix: ~
- kind: BreakOutsideLoop
location:
row: 20
column: 5
fix: ~
- kind: BreakOutsideLoop
location:
row: 23
column: 1
fix: ~

View File

@@ -0,0 +1,25 @@
---
source: src/linter.rs
expression: checks
---
- kind: ContinueOutsideLoop
location:
row: 4
column: 5
fix: ~
- kind: ContinueOutsideLoop
location:
row: 16
column: 5
fix: ~
- kind: ContinueOutsideLoop
location:
row: 20
column: 5
fix: ~
- kind: ContinueOutsideLoop
location:
row: 23
column: 1
fix: ~

View File

@@ -0,0 +1,20 @@
---
source: src/linter.rs
expression: checks
---
- kind: YieldOutsideFunction
location:
row: 6
column: 5
fix: ~
- kind: YieldOutsideFunction
location:
row: 9
column: 1
fix: ~
- kind: YieldOutsideFunction
location:
row: 10
column: 1
fix: ~

View File

@@ -0,0 +1,15 @@
---
source: src/linter.rs
expression: checks
---
- kind: ReturnOutsideFunction
location:
row: 6
column: 5
fix: ~
- kind: ReturnOutsideFunction
location:
row: 9
column: 1
fix: ~

View File

@@ -0,0 +1,20 @@
---
source: src/linter.rs
expression: checks
---
- kind: DefaultExceptNotLast
location:
row: 3
column: 1
fix: ~
- kind: DefaultExceptNotLast
location:
row: 10
column: 1
fix: ~
- kind: DefaultExceptNotLast
location:
row: 19
column: 1
fix: ~

View File

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

View File

@@ -0,0 +1,35 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UndefinedName: self
location:
row: 2
column: 12
fix: ~
- kind:
UndefinedName: self
location:
row: 6
column: 13
fix: ~
- kind:
UndefinedName: self
location:
row: 10
column: 9
fix: ~
- kind:
UndefinedName: numeric_string
location:
row: 21
column: 12
fix: ~
- kind:
UndefinedName: Bar
location:
row: 58
column: 5
fix: ~

View File

@@ -0,0 +1,11 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UndefinedExport: b
location:
row: 3
column: 1
fix: ~

View File

@@ -0,0 +1,11 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UndefinedLocal: my_var
location:
row: 6
column: 5
fix: ~

View File

@@ -0,0 +1,20 @@
---
source: src/linter.rs
expression: checks
---
- kind: DuplicateArgumentName
location:
row: 1
column: 25
fix: ~
- kind: DuplicateArgumentName
location:
row: 5
column: 28
fix: ~
- kind: DuplicateArgumentName
location:
row: 9
column: 27
fix: ~

View File

@@ -0,0 +1,35 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UnusedVariable: e
location:
row: 3
column: 1
fix: ~
- kind:
UnusedVariable: z
location:
row: 16
column: 5
fix: ~
- kind:
UnusedVariable: foo
location:
row: 20
column: 5
fix: ~
- kind:
UnusedVariable: a
location:
row: 21
column: 6
fix: ~
- kind:
UnusedVariable: b
location:
row: 21
column: 9
fix: ~

View File

@@ -0,0 +1,15 @@
---
source: src/linter.rs
expression: checks
---
- kind: RaiseNotImplemented
location:
row: 2
column: 25
fix: ~
- kind: RaiseNotImplemented
location:
row: 6
column: 11
fix: ~

View File

@@ -0,0 +1,17 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UnusedImport: models.Nut
location:
row: 5
column: 1
fix: ~
- kind:
UndefinedName: Bar
location:
row: 22
column: 19
fix: ~

View File

@@ -0,0 +1,285 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UselessObjectInheritance: A
location:
row: 5
column: 9
fix:
content: ""
start:
row: 5
column: 8
end:
row: 5
column: 16
applied: false
- kind:
UselessObjectInheritance: A
location:
row: 10
column: 5
fix:
content: ""
start:
row: 9
column: 8
end:
row: 11
column: 2
applied: false
- kind:
UselessObjectInheritance: A
location:
row: 16
column: 5
fix:
content: ""
start:
row: 15
column: 8
end:
row: 18
column: 2
applied: false
- kind:
UselessObjectInheritance: A
location:
row: 24
column: 5
fix:
content: ""
start:
row: 22
column: 8
end:
row: 25
column: 2
applied: false
- kind:
UselessObjectInheritance: A
location:
row: 31
column: 5
fix:
content: ""
start:
row: 29
column: 8
end:
row: 32
column: 2
applied: false
- kind:
UselessObjectInheritance: A
location:
row: 37
column: 5
fix:
content: ""
start:
row: 36
column: 8
end:
row: 39
column: 2
applied: false
- kind:
UselessObjectInheritance: A
location:
row: 45
column: 5
fix:
content: ""
start:
row: 43
column: 8
end:
row: 47
column: 2
applied: false
- kind:
UselessObjectInheritance: A
location:
row: 53
column: 5
fix:
content: ""
start:
row: 51
column: 8
end:
row: 55
column: 2
applied: false
- kind:
UselessObjectInheritance: A
location:
row: 61
column: 5
fix:
content: ""
start:
row: 59
column: 8
end:
row: 63
column: 2
applied: false
- kind:
UselessObjectInheritance: A
location:
row: 69
column: 5
fix:
content: ""
start:
row: 67
column: 8
end:
row: 71
column: 2
applied: false
- kind:
UselessObjectInheritance: B
location:
row: 75
column: 12
fix:
content: ""
start:
row: 75
column: 10
end:
row: 75
column: 18
applied: false
- kind:
UselessObjectInheritance: B
location:
row: 79
column: 9
fix:
content: ""
start:
row: 79
column: 9
end:
row: 79
column: 17
applied: false
- kind:
UselessObjectInheritance: B
location:
row: 84
column: 5
fix:
content: ""
start:
row: 84
column: 5
end:
row: 85
column: 5
applied: false
- kind:
UselessObjectInheritance: B
location:
row: 92
column: 5
fix:
content: ""
start:
row: 91
column: 6
end:
row: 92
column: 11
applied: false
- kind:
UselessObjectInheritance: B
location:
row: 98
column: 5
fix:
content: ""
start:
row: 98
column: 5
end:
row: 100
column: 5
applied: false
- kind:
UselessObjectInheritance: B
location:
row: 108
column: 5
fix:
content: ""
start:
row: 107
column: 6
end:
row: 108
column: 11
applied: false
- kind:
UselessObjectInheritance: A
location:
row: 114
column: 13
fix:
content: ""
start:
row: 114
column: 12
end:
row: 114
column: 20
applied: false
- kind:
UselessObjectInheritance: A
location:
row: 119
column: 5
fix:
content: ""
start:
row: 118
column: 8
end:
row: 120
column: 2
applied: false
- kind:
UselessObjectInheritance: A
location:
row: 125
column: 5
fix:
content: ""
start:
row: 124
column: 8
end:
row: 126
column: 2
applied: false
- kind:
UselessObjectInheritance: A
location:
row: 131
column: 5
fix:
content: ""
start:
row: 130
column: 8
end:
row: 133
column: 2
applied: false

View File

@@ -0,0 +1,31 @@
---
source: src/linter.rs
expression: checks
---
- kind: NoAssertEquals
location:
row: 1
column: 5
fix:
content: assertEqual
start:
row: 1
column: 6
end:
row: 1
column: 18
applied: false
- kind: NoAssertEquals
location:
row: 2
column: 5
fix:
content: assertEqual
start:
row: 2
column: 6
end:
row: 2
column: 18
applied: false