Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f69be0d41 | ||
|
|
54cb2eb15b | ||
|
|
167992ad48 | ||
|
|
f0dab24079 | ||
|
|
77055faab6 | ||
|
|
e08e1caf71 | ||
|
|
0072dfd81e | ||
|
|
6ffe02ee05 | ||
|
|
688fc0cd02 | ||
|
|
f30e5e45ab | ||
|
|
1a68a38306 | ||
|
|
590aa92ead | ||
|
|
8868f57a74 | ||
|
|
71802f8861 | ||
|
|
5b6fb8cefa | ||
|
|
2ff964107c | ||
|
|
141132d5be | ||
|
|
8ba872ece4 | ||
|
|
209dce2033 | ||
|
|
90d88dfb10 | ||
|
|
4730911b25 | ||
|
|
4e9fb9907a | ||
|
|
b8dce8922d | ||
|
|
30877127bc | ||
|
|
8b66bbdc9b | ||
|
|
71d3a84b14 | ||
|
|
323a5c857c | ||
|
|
42cec3f5a0 | ||
|
|
ee42413e10 | ||
|
|
765db12b84 | ||
|
|
e1b711d9c6 | ||
|
|
35f593846e | ||
|
|
c384fa513b | ||
|
|
022ff64d29 | ||
|
|
5a06fb28fd | ||
|
|
46750a3e17 | ||
|
|
9cc902b802 | ||
|
|
c2a36ebd1e | ||
|
|
34ca225393 | ||
|
|
38c30905e6 |
89
Cargo.lock
generated
89
Cargo.lock
generated
@@ -64,6 +64,20 @@ dependencies = [
|
||||
"term",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert_cmd"
|
||||
version = "2.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"doc-comment",
|
||||
"predicates",
|
||||
"predicates-core",
|
||||
"predicates-tree",
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "1.7.1"
|
||||
@@ -580,6 +594,12 @@ version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "difflib"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.8.1"
|
||||
@@ -658,6 +678,12 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.0"
|
||||
@@ -1089,6 +1115,12 @@ version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
|
||||
|
||||
[[package]]
|
||||
name = "joinery"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.60"
|
||||
@@ -1685,6 +1717,33 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c"
|
||||
dependencies = [
|
||||
"difflib",
|
||||
"itertools",
|
||||
"predicates-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "predicates-core"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb"
|
||||
|
||||
[[package]]
|
||||
name = "predicates-tree"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032"
|
||||
dependencies = [
|
||||
"predicates-core",
|
||||
"termtree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
@@ -1907,9 +1966,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.65"
|
||||
version = "0.0.71"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
"bincode",
|
||||
"cacache",
|
||||
"chrono",
|
||||
@@ -1938,6 +1998,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"titlecase",
|
||||
"toml",
|
||||
"update-informer",
|
||||
"walkdir",
|
||||
@@ -2345,6 +2406,12 @@ dependencies = [
|
||||
"phf_codegen 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termtree"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.37"
|
||||
@@ -2400,6 +2467,17 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "titlecase"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38397a8cdb017cfeb48bf6c154d6de975ac69ffeed35980fde199d2ee0842042"
|
||||
dependencies = [
|
||||
"joinery",
|
||||
"lazy_static",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.9"
|
||||
@@ -2593,6 +2671,15 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8e76fae08f03f96e166d2dfda232190638c10e0383841252416f9cfe2ae60e6"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "waker-fn"
|
||||
version = "1.1.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.65"
|
||||
version = "0.0.71"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -38,8 +38,10 @@ walkdir = { version = "2.3.2" }
|
||||
strum = { version = "0.24.1", features = ["strum_macros"] }
|
||||
strum_macros = "0.24.3"
|
||||
num-bigint = "0.4.3"
|
||||
titlecase = "2.2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.0.4"
|
||||
insta = { version = "1.19.1", features = ["yaml"] }
|
||||
|
||||
[features]
|
||||
|
||||
53
README.md
53
README.md
@@ -57,7 +57,7 @@ ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.48
|
||||
rev: v0.0.71
|
||||
hooks:
|
||||
- id: lint
|
||||
```
|
||||
@@ -215,8 +215,10 @@ ruff also implements some of the most popular Flake8 plugins natively, including
|
||||
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
|
||||
- [`flake8-super`](https://pypi.org/project/flake8-super/)
|
||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) (partial)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (partial)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) (12/16)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (3/32)
|
||||
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) (37/48)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34)
|
||||
|
||||
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
|
||||
|
||||
@@ -278,6 +280,9 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| A001 | BuiltinVariableShadowing | Variable `...` is shadowing a python builtin | | |
|
||||
| A002 | BuiltinArgumentShadowing | Argument `...` is shadowing a python builtin | | |
|
||||
| A003 | BuiltinAttributeShadowing | Class attribute `...` is shadowing a python builtin | | |
|
||||
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | | 🛠 |
|
||||
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | | 🛠 |
|
||||
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` | | |
|
||||
| C400 | UnnecessaryGeneratorList | Unnecessary generator - rewrite as a list comprehension | | |
|
||||
| C401 | UnnecessaryGeneratorSet | Unnecessary generator - rewrite as a set comprehension | | |
|
||||
| C402 | UnnecessaryGeneratorDict | Unnecessary generator - rewrite as a dict comprehension | | |
|
||||
@@ -286,6 +291,9 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| C405 | UnnecessaryLiteralSet | Unnecessary <list/tuple> literal - rewrite as a set literal | | |
|
||||
| C406 | UnnecessaryLiteralDict | Unnecessary <list/tuple> literal - rewrite as a dict literal | | |
|
||||
| C408 | UnnecessaryCollectionCall | Unnecessary <dict/list/tuple> call - rewrite as a literal | | |
|
||||
| C409 | UnnecessaryLiteralWithinTupleCall | Unnecessary <list/tuple> literal passed to tuple() - remove the outer call to tuple() | | |
|
||||
| C410 | UnnecessaryLiteralWithinListCall | Unnecessary <list/tuple> literal passed to list() - rewrite as a list literal | | |
|
||||
| C414 | UnnecessaryDoubleCastOrProcess | Unnecessary <list/reversed/set/sorted/tuple> call within <list/set/sorted/tuple>(). | | |
|
||||
| C415 | UnnecessarySubscriptReversal | Unnecessary subscript reversal of iterable within <reversed/set/sorted>() | | |
|
||||
| T201 | PrintFound | `print` found | | 🛠 |
|
||||
| T203 | PPrintFound | `pprint` found | | 🛠 |
|
||||
@@ -293,10 +301,47 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||
| U002 | UnnecessaryAbspath | `abspath(__file__)` is unnecessary in Python 3.9 and later | | 🛠 |
|
||||
| U003 | TypeOfPrimitive | Use `str` instead of `type(...)` | | 🛠 |
|
||||
| U004 | UselessObjectInheritance | Class `...` inherits from object | | 🛠 |
|
||||
| U005 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead | | 🛠 |
|
||||
| U005 | DeprecatedUnittestAlias | `assertEquals` is deprecated, use `assertEqual` instead | | 🛠 |
|
||||
| U006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | | 🛠 |
|
||||
| U007 | UsePEP604Annotation | Use `X \| Y` for type annotations | | 🛠 |
|
||||
| U008 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | | 🛠 |
|
||||
| D100 | PublicModule | Missing docstring in public module | | |
|
||||
| D101 | PublicClass | Missing docstring in public class | | |
|
||||
| D102 | PublicMethod | Missing docstring in public method | | |
|
||||
| D103 | PublicFunction | Missing docstring in public function | | |
|
||||
| D104 | PublicPackage | Missing docstring in public package | | |
|
||||
| D105 | MagicMethod | Missing docstring in magic method | | |
|
||||
| D106 | PublicNestedClass | Missing docstring in public nested class | | |
|
||||
| D107 | PublicInit | Missing docstring in __init__ | | |
|
||||
| D200 | FitsOnOneLine | One-line docstring should fit on one line | | |
|
||||
| D205 | NoBlankLineAfterSummary | 1 blank line required between summary line and description | | |
|
||||
| D209 | NewLineAfterLastParagraph | Multi-line docstring closing quotes should be on a separate line | | |
|
||||
| D210 | NoSurroundingWhitespace | No whitespaces allowed surrounding docstring text | | |
|
||||
| D212 | MultiLineSummaryFirstLine | Multi-line docstring summary should start at the first line | | |
|
||||
| D213 | MultiLineSummarySecondLine | Multi-line docstring summary should start at the second line | | |
|
||||
| D300 | UsesTripleQuotes | Use """triple double quotes""" | | |
|
||||
| D400 | EndsInPeriod | First line should end with a period | | |
|
||||
| D402 | NoSignature | First line should not be the function's 'signature' | | |
|
||||
| D403 | FirstLineCapitalized | First word of the first line should be properly capitalized | | |
|
||||
| D404 | NoThisPrefix | First word of the docstring should not be `This` | | |
|
||||
| D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point | | |
|
||||
| D418 | SkipDocstring | Function decorated with @overload shouldn't contain a docstring | | |
|
||||
| D419 | NonEmpty | Docstring is empty | | |
|
||||
| D201 | NoBlankLineBeforeFunction | No blank lines allowed before function docstring (found 1) | | |
|
||||
| D202 | NoBlankLineAfterFunction | No blank lines allowed after function docstring (found 1) | | |
|
||||
| D211 | NoBlankLineBeforeClass | No blank lines allowed before class docstring | | |
|
||||
| D203 | OneBlankLineBeforeClass | 1 blank line required before class docstring | | |
|
||||
| D204 | OneBlankLineAfterClass | 1 blank line required after class docstring | | |
|
||||
| D405 | CapitalizeSectionName | Section name should be properly capitalized ("returns") | | |
|
||||
| D413 | BlankLineAfterLastSection | Missing blank line after last section ("Returns") | | |
|
||||
| D410 | BlankLineAfterSection | Missing blank line after section ("Returns") | | |
|
||||
| D411 | BlankLineBeforeSection | Missing blank line before section ("Returns") | | |
|
||||
| D406 | NewLineAfterSectionName | Section name should end with a newline ("Returns") | | |
|
||||
| D407 | DashedUnderlineAfterSection | Missing dashed underline after section ("Returns") | | |
|
||||
| D408 | SectionUnderlineAfterName | Section underline should be in the line following the section's name ("Returns") | | |
|
||||
| D409 | SectionUnderlineMatchesSectionLength | Section underline should match the length of its name ("Returns") | | |
|
||||
| D412 | NoBlankLinesBetweenHeaderAndContent | No blank lines allowed between a section header and its content ("Returns") | | |
|
||||
| D414 | NonEmptySection | Section has no content ("Returns") | | |
|
||||
| M001 | UnusedNOQA | Unused `noqa` directive | | 🛠 |
|
||||
|
||||
## Integrations
|
||||
|
||||
10
resources/test/fixtures/B011.py
vendored
Normal file
10
resources/test/fixtures/B011.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
Should emit:
|
||||
B011 - on line 8
|
||||
B011 - on line 10
|
||||
"""
|
||||
|
||||
assert 1 != 2
|
||||
assert False
|
||||
assert 1 != 2, "message"
|
||||
assert False, "message"
|
||||
76
resources/test/fixtures/B014.py
vendored
Normal file
76
resources/test/fixtures/B014.py
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
Should emit:
|
||||
B014 - on lines 11, 17, 28, 42, 49, 56, and 74.
|
||||
"""
|
||||
|
||||
import binascii
|
||||
import re
|
||||
|
||||
try:
|
||||
pass
|
||||
except (Exception, TypeError):
|
||||
# TypeError is a subclass of Exception, so it doesn't add anything
|
||||
pass
|
||||
|
||||
try:
|
||||
pass
|
||||
except (OSError, OSError) as err:
|
||||
# Duplicate exception types are useless
|
||||
pass
|
||||
|
||||
|
||||
class MyError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (MyError, MyError):
|
||||
# Detect duplicate non-builtin errors
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (MyError, Exception) as e:
|
||||
# Don't assume that we're all subclasses of Exception
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (MyError, BaseException) as e:
|
||||
# But we *can* assume that everything is a subclass of BaseException
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (re.error, re.error):
|
||||
# Duplicate exception types as attributes
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (IOError, EnvironmentError, OSError):
|
||||
# Detect if a primary exception and any its aliases are present.
|
||||
#
|
||||
# Since Python 3.3, IOError, EnvironmentError, WindowsError, mmap.error,
|
||||
# socket.error and select.error are aliases of OSError. See PEP 3151 for
|
||||
# more info.
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (MyException, NotImplemented):
|
||||
# NotImplemented is not an exception, let's not crash on it.
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except (ValueError, binascii.Error):
|
||||
# binascii.Error is a subclass of ValueError.
|
||||
pass
|
||||
38
resources/test/fixtures/B025.py
vendored
Normal file
38
resources/test/fixtures/B025.py
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
Should emit:
|
||||
B025 - on lines 15, 22, 31
|
||||
"""
|
||||
|
||||
import pickle
|
||||
|
||||
try:
|
||||
a = 1
|
||||
except ValueError:
|
||||
a = 2
|
||||
finally:
|
||||
a = 3
|
||||
|
||||
try:
|
||||
a = 1
|
||||
except ValueError:
|
||||
a = 2
|
||||
except ValueError:
|
||||
a = 2
|
||||
|
||||
try:
|
||||
a = 1
|
||||
except pickle.PickleError:
|
||||
a = 2
|
||||
except ValueError:
|
||||
a = 2
|
||||
except pickle.PickleError:
|
||||
a = 2
|
||||
|
||||
try:
|
||||
a = 1
|
||||
except (ValueError, TypeError):
|
||||
a = 2
|
||||
except ValueError:
|
||||
a = 2
|
||||
except (OSError, TypeError):
|
||||
a = 2
|
||||
3
resources/test/fixtures/C409.py
vendored
Normal file
3
resources/test/fixtures/C409.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
t1 = tuple([1, 2])
|
||||
t2 = tuple((1, 2))
|
||||
t3 = tuple([])
|
||||
4
resources/test/fixtures/C410.py
vendored
Normal file
4
resources/test/fixtures/C410.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
l1 = list([1, 2])
|
||||
l2 = list((1, 2))
|
||||
l3 = list([])
|
||||
l4 = list(())
|
||||
14
resources/test/fixtures/C414.py
vendored
Normal file
14
resources/test/fixtures/C414.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
x = [1, 2, 3]
|
||||
list(list(x))
|
||||
list(tuple(x))
|
||||
tuple(list(x))
|
||||
tuple(tuple(x))
|
||||
set(set(x))
|
||||
set(list(x))
|
||||
set(tuple(x))
|
||||
set(sorted(x))
|
||||
set(reversed(x))
|
||||
sorted(list(x))
|
||||
sorted(tuple(x))
|
||||
sorted(sorted(x))
|
||||
sorted(reversed(x))
|
||||
534
resources/test/fixtures/D.py
vendored
Normal file
534
resources/test/fixtures/D.py
vendored
Normal file
@@ -0,0 +1,534 @@
|
||||
# No docstring, so we can test D100
|
||||
from functools import wraps
|
||||
import os
|
||||
from .expected import Expectation
|
||||
from typing import overload
|
||||
|
||||
|
||||
expectation = Expectation()
|
||||
expect = expectation.expect
|
||||
|
||||
expect('class_', 'D101: Missing docstring in public class')
|
||||
|
||||
|
||||
class class_:
|
||||
|
||||
expect('meta', 'D419: Docstring is empty')
|
||||
|
||||
class meta:
|
||||
""""""
|
||||
|
||||
@expect('D102: Missing docstring in public method')
|
||||
def method(self=None):
|
||||
pass
|
||||
|
||||
def _ok_since_private(self=None):
|
||||
pass
|
||||
|
||||
@overload
|
||||
def overloaded_method(self, a: int) -> str:
|
||||
...
|
||||
|
||||
@overload
|
||||
def overloaded_method(self, a: str) -> str:
|
||||
"""Foo bar documentation."""
|
||||
...
|
||||
|
||||
def overloaded_method(a):
|
||||
"""Foo bar documentation."""
|
||||
return str(a)
|
||||
|
||||
expect('overloaded_method',
|
||||
"D418: Function/ Method decorated with @overload"
|
||||
" shouldn't contain a docstring")
|
||||
|
||||
@property
|
||||
def foo(self):
|
||||
"""The foo of the thing, which isn't in imperitive mood."""
|
||||
return "hello"
|
||||
|
||||
@expect('D102: Missing docstring in public method')
|
||||
def __new__(self=None):
|
||||
pass
|
||||
|
||||
@expect('D107: Missing docstring in __init__')
|
||||
def __init__(self=None):
|
||||
pass
|
||||
|
||||
@expect('D105: Missing docstring in magic method')
|
||||
def __str__(self=None):
|
||||
pass
|
||||
|
||||
@expect('D102: Missing docstring in public method')
|
||||
def __call__(self=None, x=None, y=None, z=None):
|
||||
pass
|
||||
|
||||
|
||||
@expect('D419: Docstring is empty')
|
||||
def function():
|
||||
""" """
|
||||
def ok_since_nested():
|
||||
pass
|
||||
|
||||
@expect('D419: Docstring is empty')
|
||||
def nested():
|
||||
''
|
||||
|
||||
|
||||
def function_with_nesting():
|
||||
"""Foo bar documentation."""
|
||||
@overload
|
||||
def nested_overloaded_func(a: int) -> str:
|
||||
...
|
||||
|
||||
@overload
|
||||
def nested_overloaded_func(a: str) -> str:
|
||||
"""Foo bar documentation."""
|
||||
...
|
||||
|
||||
def nested_overloaded_func(a):
|
||||
"""Foo bar documentation."""
|
||||
return str(a)
|
||||
|
||||
|
||||
expect('nested_overloaded_func',
|
||||
"D418: Function/ Method decorated with @overload"
|
||||
" shouldn't contain a docstring")
|
||||
|
||||
|
||||
@overload
|
||||
def overloaded_func(a: int) -> str:
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def overloaded_func(a: str) -> str:
|
||||
"""Foo bar documentation."""
|
||||
...
|
||||
|
||||
|
||||
def overloaded_func(a):
|
||||
"""Foo bar documentation."""
|
||||
return str(a)
|
||||
|
||||
|
||||
expect('overloaded_func',
|
||||
"D418: Function/ Method decorated with @overload"
|
||||
" shouldn't contain a docstring")
|
||||
|
||||
|
||||
@expect('D200: One-line docstring should fit on one line with quotes '
|
||||
'(found 3)')
|
||||
@expect('D212: Multi-line docstring summary should start at the first line')
|
||||
def asdlkfasd():
|
||||
"""
|
||||
Wrong.
|
||||
"""
|
||||
|
||||
|
||||
@expect('D201: No blank lines allowed before function docstring (found 1)')
|
||||
def leading_space():
|
||||
|
||||
"""Leading space."""
|
||||
|
||||
|
||||
@expect('D202: No blank lines allowed after function docstring (found 1)')
|
||||
def trailing_space():
|
||||
"""Leading space."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@expect('D201: No blank lines allowed before function docstring (found 1)')
|
||||
@expect('D202: No blank lines allowed after function docstring (found 1)')
|
||||
def trailing_and_leading_space():
|
||||
|
||||
"""Trailing and leading space."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
expect('LeadingSpaceMissing',
|
||||
'D203: 1 blank line required before class docstring (found 0)')
|
||||
|
||||
|
||||
class LeadingSpaceMissing:
|
||||
"""Leading space missing."""
|
||||
|
||||
|
||||
expect('WithLeadingSpace',
|
||||
'D211: No blank lines allowed before class docstring (found 1)')
|
||||
|
||||
|
||||
class WithLeadingSpace:
|
||||
|
||||
"""With leading space."""
|
||||
|
||||
|
||||
expect('TrailingSpace',
|
||||
'D204: 1 blank line required after class docstring (found 0)')
|
||||
expect('TrailingSpace',
|
||||
'D211: No blank lines allowed before class docstring (found 1)')
|
||||
|
||||
|
||||
class TrailingSpace:
|
||||
|
||||
"""TrailingSpace."""
|
||||
pass
|
||||
|
||||
|
||||
expect('LeadingAndTrailingSpaceMissing',
|
||||
'D203: 1 blank line required before class docstring (found 0)')
|
||||
expect('LeadingAndTrailingSpaceMissing',
|
||||
'D204: 1 blank line required after class docstring (found 0)')
|
||||
|
||||
|
||||
class LeadingAndTrailingSpaceMissing:
|
||||
"""Leading and trailing space missing."""
|
||||
pass
|
||||
|
||||
|
||||
@expect('D205: 1 blank line required between summary line and description '
|
||||
'(found 0)')
|
||||
@expect('D213: Multi-line docstring summary should start at the second line')
|
||||
def multi_line_zero_separating_blanks():
|
||||
"""Summary.
|
||||
Description.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect('D205: 1 blank line required between summary line and description '
|
||||
'(found 2)')
|
||||
@expect('D213: Multi-line docstring summary should start at the second line')
|
||||
def multi_line_two_separating_blanks():
|
||||
"""Summary.
|
||||
|
||||
|
||||
Description.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect('D213: Multi-line docstring summary should start at the second line')
|
||||
def multi_line_one_separating_blanks():
|
||||
"""Summary.
|
||||
|
||||
Description.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect('D207: Docstring is under-indented')
|
||||
@expect('D213: Multi-line docstring summary should start at the second line')
|
||||
def asdfsdf():
|
||||
"""Summary.
|
||||
|
||||
Description.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect('D207: Docstring is under-indented')
|
||||
@expect('D213: Multi-line docstring summary should start at the second line')
|
||||
def asdsdfsdffsdf():
|
||||
"""Summary.
|
||||
|
||||
Description.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect('D208: Docstring is over-indented')
|
||||
@expect('D213: Multi-line docstring summary should start at the second line')
|
||||
def asdfsdsdf24():
|
||||
"""Summary.
|
||||
|
||||
Description.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect('D208: Docstring is over-indented')
|
||||
@expect('D213: Multi-line docstring summary should start at the second line')
|
||||
def asdfsdsdfsdf24():
|
||||
"""Summary.
|
||||
|
||||
Description.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect('D208: Docstring is over-indented')
|
||||
@expect('D213: Multi-line docstring summary should start at the second line')
|
||||
def asdfsdfsdsdsdfsdf24():
|
||||
"""Summary.
|
||||
|
||||
Description.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect('D209: Multi-line docstring closing quotes should be on a separate '
|
||||
'line')
|
||||
@expect('D213: Multi-line docstring summary should start at the second line')
|
||||
def asdfljdf24():
|
||||
"""Summary.
|
||||
|
||||
Description."""
|
||||
|
||||
|
||||
@expect('D210: No whitespaces allowed surrounding docstring text')
|
||||
def endswith():
|
||||
"""Whitespace at the end. """
|
||||
|
||||
|
||||
@expect('D210: No whitespaces allowed surrounding docstring text')
|
||||
def around():
|
||||
""" Whitespace at everywhere. """
|
||||
|
||||
|
||||
@expect('D210: No whitespaces allowed surrounding docstring text')
|
||||
@expect('D213: Multi-line docstring summary should start at the second line')
|
||||
def multiline():
|
||||
""" Whitespace at the beginning.
|
||||
|
||||
This is the end.
|
||||
"""
|
||||
|
||||
|
||||
@expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
|
||||
def triple_single_quotes_raw():
|
||||
r'''Summary.'''
|
||||
|
||||
|
||||
@expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
|
||||
def triple_single_quotes_raw_uppercase():
|
||||
R'''Summary.'''
|
||||
|
||||
|
||||
@expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
def single_quotes_raw():
|
||||
r'Summary.'
|
||||
|
||||
|
||||
@expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
def single_quotes_raw_uppercase():
|
||||
R'Summary.'
|
||||
|
||||
|
||||
@expect('D300: Use """triple double quotes""" (found \'-quotes)')
|
||||
@expect('D301: Use r""" if any backslashes in a docstring')
|
||||
def single_quotes_raw_uppercase_backslash():
|
||||
R'Sum\mary.'
|
||||
|
||||
|
||||
@expect('D301: Use r""" if any backslashes in a docstring')
|
||||
def double_quotes_backslash():
|
||||
"""Sum\\mary."""
|
||||
|
||||
|
||||
@expect('D301: Use r""" if any backslashes in a docstring')
|
||||
def double_quotes_backslash_uppercase():
|
||||
R"""Sum\\mary."""
|
||||
|
||||
|
||||
@expect('D213: Multi-line docstring summary should start at the second line')
|
||||
def exceptions_of_D301():
|
||||
"""Exclude some backslashes from D301.
|
||||
|
||||
In particular, line continuations \
|
||||
and unicode literals \u0394 and \N{GREEK CAPITAL LETTER DELTA}.
|
||||
They are considered to be intentionally unescaped.
|
||||
"""
|
||||
|
||||
|
||||
@expect("D400: First line should end with a period (not 'y')")
|
||||
@expect("D415: First line should end with a period, question mark, "
|
||||
"or exclamation point (not 'y')")
|
||||
def lwnlkjl():
|
||||
"""Summary"""
|
||||
|
||||
|
||||
@expect("D401: First line should be in imperative mood "
|
||||
"(perhaps 'Return', not 'Returns')")
|
||||
def liouiwnlkjl():
|
||||
"""Returns foo."""
|
||||
|
||||
|
||||
@expect("D401: First line should be in imperative mood; try rephrasing "
|
||||
"(found 'Constructor')")
|
||||
def sdgfsdg23245():
|
||||
"""Constructor for a foo."""
|
||||
|
||||
|
||||
@expect("D401: First line should be in imperative mood; try rephrasing "
|
||||
"(found 'Constructor')")
|
||||
def sdgfsdg23245777():
|
||||
"""Constructor."""
|
||||
|
||||
|
||||
@expect('D402: First line should not be the function\'s "signature"')
|
||||
def foobar():
|
||||
"""Signature: foobar()."""
|
||||
|
||||
|
||||
@expect('D213: Multi-line docstring summary should start at the second line')
|
||||
def new_209():
|
||||
"""First line.
|
||||
|
||||
More lines.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@expect('D213: Multi-line docstring summary should start at the second line')
|
||||
def old_209():
|
||||
"""One liner.
|
||||
|
||||
Multi-line comments. OK to have extra blank line
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect("D103: Missing docstring in public function")
|
||||
def oneliner_d102(): return
|
||||
|
||||
|
||||
@expect("D400: First line should end with a period (not 'r')")
|
||||
@expect("D415: First line should end with a period, question mark,"
|
||||
" or exclamation point (not 'r')")
|
||||
def oneliner_withdoc(): """One liner"""
|
||||
|
||||
|
||||
def ignored_decorator(func): # noqa: D400,D401,D415
|
||||
"""Runs something"""
|
||||
func()
|
||||
pass
|
||||
|
||||
|
||||
def decorator_for_test(func): # noqa: D400,D401,D415
|
||||
"""Runs something"""
|
||||
func()
|
||||
pass
|
||||
|
||||
|
||||
@ignored_decorator
|
||||
def oneliner_ignored_decorator(): """One liner"""
|
||||
|
||||
|
||||
@decorator_for_test
|
||||
@expect("D400: First line should end with a period (not 'r')")
|
||||
@expect("D415: First line should end with a period, question mark,"
|
||||
" or exclamation point (not 'r')")
|
||||
def oneliner_with_decorator_expecting_errors(): """One liner"""
|
||||
|
||||
|
||||
@decorator_for_test
|
||||
def valid_oneliner_with_decorator(): """One liner."""
|
||||
|
||||
|
||||
@expect("D207: Docstring is under-indented")
|
||||
@expect('D213: Multi-line docstring summary should start at the second line')
|
||||
def docstring_start_in_same_line(): """First Line.
|
||||
|
||||
Second Line
|
||||
"""
|
||||
|
||||
|
||||
def function_with_lambda_arg(x=lambda y: y):
|
||||
"""Wrap the given lambda."""
|
||||
|
||||
|
||||
@expect('D213: Multi-line docstring summary should start at the second line')
|
||||
def a_following_valid_function(x=None):
|
||||
"""Check for a bug where the previous function caused an assertion.
|
||||
|
||||
The assertion was caused in the next function, so this one is necessary.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def outer_function():
|
||||
"""Do something."""
|
||||
def inner_function():
|
||||
"""Do inner something."""
|
||||
return 0
|
||||
|
||||
|
||||
@expect("D400: First line should end with a period (not 'g')")
|
||||
@expect("D401: First line should be in imperative mood "
|
||||
"(perhaps 'Run', not 'Runs')")
|
||||
@expect("D415: First line should end with a period, question mark, "
|
||||
"or exclamation point (not 'g')")
|
||||
def docstring_bad():
|
||||
"""Runs something"""
|
||||
pass
|
||||
|
||||
|
||||
def docstring_bad_ignore_all(): # noqa
|
||||
"""Runs something"""
|
||||
pass
|
||||
|
||||
|
||||
def docstring_bad_ignore_one(): # noqa: D400,D401,D415
|
||||
"""Runs something"""
|
||||
pass
|
||||
|
||||
|
||||
@expect("D401: First line should be in imperative mood "
|
||||
"(perhaps 'Run', not 'Runs')")
|
||||
def docstring_ignore_some_violations_but_catch_D401(): # noqa: E501,D400,D415
|
||||
"""Runs something"""
|
||||
pass
|
||||
|
||||
|
||||
@expect(
|
||||
"D401: First line should be in imperative mood "
|
||||
"(perhaps 'Initiate', not 'Initiates')"
|
||||
)
|
||||
def docstring_initiates():
|
||||
"""Initiates the process."""
|
||||
|
||||
|
||||
@expect(
|
||||
"D401: First line should be in imperative mood "
|
||||
"(perhaps 'Initialize', not 'Initializes')"
|
||||
)
|
||||
def docstring_initializes():
|
||||
"""Initializes the process."""
|
||||
|
||||
|
||||
@wraps(docstring_bad_ignore_one)
|
||||
def bad_decorated_function():
|
||||
"""Bad (E501) but decorated"""
|
||||
pass
|
||||
|
||||
|
||||
def valid_google_string(): # noqa: D400
|
||||
"""Test a valid something!"""
|
||||
|
||||
|
||||
@expect("D415: First line should end with a period, question mark, "
|
||||
"or exclamation point (not 'g')")
|
||||
def bad_google_string(): # noqa: D400
|
||||
"""Test a valid something"""
|
||||
|
||||
|
||||
# This is reproducing a bug where AttributeError is raised when parsing class
|
||||
# parameters as functions for Google / Numpy conventions.
|
||||
class Blah: # noqa: D203,D213
|
||||
"""A Blah.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : int
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, x):
|
||||
pass
|
||||
|
||||
|
||||
expect(os.path.normcase(__file__ if __file__[-1] != 'c' else __file__[:-1]),
|
||||
'D100: Missing docstring in public module')
|
||||
1
resources/test/fixtures/F404.py
vendored
1
resources/test/fixtures/F404.py
vendored
@@ -1,4 +1,3 @@
|
||||
from __future__ import print_function
|
||||
"""Docstring"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
9
resources/test/fixtures/F821.py
vendored
9
resources/test/fixtures/F821.py
vendored
@@ -122,3 +122,12 @@ class PEP593Test:
|
||||
dict["foo", "bar"], # Expected to fail as undefined.
|
||||
123,
|
||||
]
|
||||
|
||||
|
||||
def in_ipython_notebook() -> bool:
|
||||
try:
|
||||
# autoimported by notebooks
|
||||
get_ipython() # type: ignore[name-defined]
|
||||
except NameError:
|
||||
return False # not in notebook
|
||||
return True
|
||||
|
||||
3
resources/test/fixtures/M001.py
vendored
3
resources/test/fixtures/M001.py
vendored
@@ -15,6 +15,9 @@ def f() -> None:
|
||||
# Invalid
|
||||
d = 1 # noqa: F841, E501
|
||||
|
||||
# Invalid (and unimplemented)
|
||||
d = 1 # noqa: F841, W191
|
||||
|
||||
|
||||
# Valid
|
||||
_ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
497
resources/test/fixtures/sections.py
vendored
Normal file
497
resources/test/fixtures/sections.py
vendored
Normal file
@@ -0,0 +1,497 @@
|
||||
"""A valid module docstring."""
|
||||
|
||||
from .expected import Expectation
|
||||
|
||||
expectation = Expectation()
|
||||
expect = expectation.expect
|
||||
|
||||
|
||||
_D213 = 'D213: Multi-line docstring summary should start at the second line'
|
||||
_D400 = "D400: First line should end with a period (not '!')"
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D405: Section name should be properly capitalized "
|
||||
"('Returns', not 'returns')")
|
||||
def not_capitalized(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
returns
|
||||
-------
|
||||
A value of some sort.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D406: Section name should end with a newline "
|
||||
"('Returns', not 'Returns:')")
|
||||
def superfluous_suffix(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
Returns:
|
||||
-------
|
||||
A value of some sort.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D407: Missing dashed underline after section ('Returns')")
|
||||
def no_underline(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
Returns
|
||||
A value of some sort.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D407: Missing dashed underline after section ('Returns')")
|
||||
@expect("D414: Section has no content ('Returns')")
|
||||
def no_underline_and_no_description(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
Returns
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D410: Missing blank line after section ('Returns')")
|
||||
@expect("D414: Section has no content ('Returns')")
|
||||
@expect("D411: Missing blank line before section ('Yields')")
|
||||
@expect("D414: Section has no content ('Yields')")
|
||||
def consecutive_sections(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Yields
|
||||
------
|
||||
|
||||
Raises
|
||||
------
|
||||
Questions.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D408: Section underline should be in the line following the "
|
||||
"section's name ('Returns')")
|
||||
def blank_line_before_underline(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
Returns
|
||||
|
||||
-------
|
||||
A value of some sort.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D409: Section underline should match the length of its name "
|
||||
"(Expected 7 dashes in section 'Returns', got 2)")
|
||||
def bad_underline_length(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
Returns
|
||||
--
|
||||
A value of some sort.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D413: Missing blank line after last section ('Returns')")
|
||||
def no_blank_line_after_last_section(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A value of some sort.
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D411: Missing blank line before section ('Returns')")
|
||||
def no_blank_line_before_section(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
The function's description.
|
||||
Returns
|
||||
-------
|
||||
A value of some sort.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D214: Section is over-indented ('Returns')")
|
||||
def section_overindented(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A value of some sort.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D215: Section underline is over-indented (in section 'Returns')")
|
||||
def section_underline_overindented(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A value of some sort.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D215: Section underline is over-indented (in section 'Returns')")
|
||||
@expect("D413: Missing blank line after last section ('Returns')")
|
||||
@expect("D414: Section has no content ('Returns')")
|
||||
def section_underline_overindented_and_contentless(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
Returns
|
||||
-------
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
def ignore_non_actual_section(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
This is the function's description, which will also specify what it
|
||||
returns
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D401: First line should be in imperative mood "
|
||||
"(perhaps 'Return', not 'Returns')")
|
||||
@expect("D400: First line should end with a period (not 's')")
|
||||
@expect("D415: First line should end with a period, question "
|
||||
"mark, or exclamation point (not 's')")
|
||||
@expect("D205: 1 blank line required between summary line and description "
|
||||
"(found 0)")
|
||||
def section_name_in_first_line(): # noqa: D416
|
||||
"""Returns
|
||||
-------
|
||||
A value of some sort.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D405: Section name should be properly capitalized "
|
||||
"('Short Summary', not 'Short summary')")
|
||||
@expect("D412: No blank lines allowed between a section header and its "
|
||||
"content ('Short Summary')")
|
||||
@expect("D409: Section underline should match the length of its name "
|
||||
"(Expected 7 dashes in section 'Returns', got 6)")
|
||||
@expect("D410: Missing blank line after section ('Returns')")
|
||||
@expect("D411: Missing blank line before section ('Raises')")
|
||||
@expect("D406: Section name should end with a newline "
|
||||
"('Raises', not 'Raises:')")
|
||||
@expect("D407: Missing dashed underline after section ('Raises')")
|
||||
def multiple_sections(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
Short summary
|
||||
-------------
|
||||
|
||||
This is the function's description, which will also specify what it
|
||||
returns.
|
||||
|
||||
Returns
|
||||
------
|
||||
Many many wonderful things.
|
||||
Raises:
|
||||
My attention.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
def false_positive_section_prefix(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
attributes_are_fun: attributes for the function.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
def section_names_as_parameter_names(): # noqa: D416
|
||||
"""Toggle the gizmo.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
notes : list
|
||||
A list of wonderful notes.
|
||||
examples: list
|
||||
A list of horrible examples.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D414: Section has no content ('Returns')")
|
||||
def valid_google_style_section(): # noqa: D406, D407
|
||||
"""Toggle the gizmo.
|
||||
|
||||
Args:
|
||||
note: A random string.
|
||||
|
||||
Returns:
|
||||
|
||||
Raises:
|
||||
RandomError: A random error that occurs randomly.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D416: Section name should end with a colon "
|
||||
"('Args:', not 'Args')")
|
||||
def missing_colon_google_style_section(): # noqa: D406, D407
|
||||
"""Toggle the gizmo.
|
||||
|
||||
Args
|
||||
note: A random string.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect("D417: Missing argument descriptions in the docstring "
|
||||
"(argument(s) y are missing descriptions in "
|
||||
"'bar' docstring)", func_name="bar")
|
||||
def _test_nested_functions():
|
||||
x = 1
|
||||
|
||||
def bar(y=2): # noqa: D207, D213, D406, D407
|
||||
"""Nested function test for docstrings.
|
||||
|
||||
Will this work when referencing x?
|
||||
|
||||
Args:
|
||||
x: Test something
|
||||
that is broken.
|
||||
|
||||
"""
|
||||
print(x)
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D417: Missing argument descriptions in the docstring "
|
||||
"(argument(s) y are missing descriptions in "
|
||||
"'test_missing_google_args' docstring)")
|
||||
def test_missing_google_args(x=1, y=2, _private=3): # noqa: D406, D407
|
||||
"""Toggle the gizmo.
|
||||
|
||||
Args:
|
||||
x (int): The greatest integer.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class TestGoogle: # noqa: D203
|
||||
"""Test class."""
|
||||
|
||||
def test_method(self, test, another_test, _): # noqa: D213, D407
|
||||
"""Test a valid args section.
|
||||
|
||||
Args:
|
||||
test: A parameter.
|
||||
another_test: Another parameter.
|
||||
|
||||
"""
|
||||
|
||||
@expect("D417: Missing argument descriptions in the docstring "
|
||||
"(argument(s) test, y, z are missing descriptions in "
|
||||
"'test_missing_args' docstring)", arg_count=5)
|
||||
def test_missing_args(self, test, x, y, z=3, _private_arg=3): # noqa: D213, D407
|
||||
"""Test a valid args section.
|
||||
|
||||
Args:
|
||||
x: Another parameter.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@expect("D417: Missing argument descriptions in the docstring "
|
||||
"(argument(s) test, y, z are missing descriptions in "
|
||||
"'test_missing_args_class_method' docstring)", arg_count=5)
|
||||
def test_missing_args_class_method(cls, test, x, y, _, z=3): # noqa: D213, D407
|
||||
"""Test a valid args section.
|
||||
|
||||
Args:
|
||||
x: Another parameter. The parameter below is missing description.
|
||||
y:
|
||||
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
@expect("D417: Missing argument descriptions in the docstring "
|
||||
"(argument(s) a, y, z are missing descriptions in "
|
||||
"'test_missing_args_static_method' docstring)", arg_count=4)
|
||||
def test_missing_args_static_method(a, x, y, _test, z=3): # noqa: D213, D407
|
||||
"""Test a valid args section.
|
||||
|
||||
Args:
|
||||
x: Another parameter.
|
||||
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
@expect("D417: Missing argument descriptions in the docstring "
|
||||
"(argument(s) a, b are missing descriptions in "
|
||||
"'test_missing_docstring' docstring)", arg_count=2)
|
||||
def test_missing_docstring(a, b): # noqa: D213, D407
|
||||
"""Test a valid args section.
|
||||
|
||||
Args:
|
||||
a:
|
||||
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def test_hanging_indent(skip, verbose): # noqa: D213, D407
|
||||
"""Do stuff.
|
||||
|
||||
Args:
|
||||
skip (:attr:`.Skip`):
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Etiam at tellus a tellus faucibus maximus. Curabitur tellus
|
||||
mauris, semper id vehicula ac, feugiat ut tortor.
|
||||
verbose (bool):
|
||||
If True, print out as much infromation as possible.
|
||||
If False, print out concise "one-liner" information.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@expect(_D213)
|
||||
@expect("D417: Missing argument descriptions in the docstring "
|
||||
"(argument(s) y are missing descriptions in "
|
||||
"'test_missing_numpy_args' docstring)")
|
||||
def test_missing_numpy_args(_private_arg=0, x=1, y=2): # noqa: D406, D407
|
||||
"""Toggle the gizmo.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : int
|
||||
The greatest integer in the history \
|
||||
of the entire world.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class TestNumpy: # noqa: D203
|
||||
"""Test class."""
|
||||
|
||||
def test_method(self, test, another_test, z, _, x=1, y=2, _private_arg=1): # noqa: D213, D407
|
||||
"""Test a valid args section.
|
||||
|
||||
Some long string with a \
|
||||
line continuation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
test, another_test
|
||||
Some parameters without type.
|
||||
z : some parameter with a very long type description that requires a \
|
||||
line continuation.
|
||||
But no further description.
|
||||
x, y : int
|
||||
Some integer parameters.
|
||||
|
||||
"""
|
||||
|
||||
@expect("D417: Missing argument descriptions in the docstring "
|
||||
"(argument(s) test, y, z are missing descriptions in "
|
||||
"'test_missing_args' docstring)", arg_count=5)
|
||||
def test_missing_args(self, test, x, y, z=3, t=1, _private=0): # noqa: D213, D407
|
||||
"""Test a valid args section.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x, t : int
|
||||
Some parameters.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@expect("D417: Missing argument descriptions in the docstring "
|
||||
"(argument(s) test, y, z are missing descriptions in "
|
||||
"'test_missing_args_class_method' docstring)", arg_count=4)
|
||||
def test_missing_args_class_method(cls, test, x, y, z=3): # noqa: D213, D407
|
||||
"""Test a valid args section.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
z
|
||||
x
|
||||
Another parameter. The parameters y, test below are
|
||||
missing descriptions. The parameter z above is also missing
|
||||
a description.
|
||||
y
|
||||
test
|
||||
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
@expect("D417: Missing argument descriptions in the docstring "
|
||||
"(argument(s) a, z are missing descriptions in "
|
||||
"'test_missing_args_static_method' docstring)", arg_count=3)
|
||||
def test_missing_args_static_method(a, x, y, z=3, t=1): # noqa: D213, D407
|
||||
"""Test a valid args section.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x, y
|
||||
Another parameter.
|
||||
t : int
|
||||
Yet another parameter.
|
||||
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def test_mixing_numpy_and_google(danger): # noqa: D213
|
||||
"""Repro for #388.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
danger
|
||||
Zoneeeeee!
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class TestIncorrectIndent: # noqa: D203
|
||||
"""Test class."""
|
||||
|
||||
@expect("D417: Missing argument descriptions in the docstring "
|
||||
"(argument(s) y are missing descriptions in "
|
||||
"'test_incorrect_indent' docstring)", arg_count=3)
|
||||
def test_incorrect_indent(self, x=1, y=2): # noqa: D207, D213, D407
|
||||
"""Reproducing issue #437.
|
||||
|
||||
Testing this incorrectly indented docstring.
|
||||
|
||||
Args:
|
||||
x: Test argument.
|
||||
|
||||
"""
|
||||
23
setup.py
Normal file
23
setup.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import sys
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
sys.stderr.write(
|
||||
"""
|
||||
===============================
|
||||
Unsupported installation method
|
||||
===============================
|
||||
ruff no longer supports installation with `python setup.py install`.
|
||||
Please use `python -m pip install .` instead.
|
||||
"""
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# The below code will never execute, however GitHub is particularly
|
||||
# picky about where it finds Python packaging metadata.
|
||||
# See: https://github.com/github/feedback/discussions/6456
|
||||
#
|
||||
# To be removed once GitHub catches up.
|
||||
|
||||
setup(name="ruff", install_requires=[])
|
||||
@@ -1,4 +1,4 @@
|
||||
pub mod checks;
|
||||
pub mod checkers;
|
||||
pub mod helpers;
|
||||
pub mod operations;
|
||||
pub mod relocate;
|
||||
|
||||
@@ -9,6 +9,7 @@ use rustpython_parser::ast::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ast::helpers;
|
||||
use crate::ast::types::{
|
||||
Binding, BindingKind, CheckLocator, FunctionScope, Range, Scope, ScopeKind,
|
||||
};
|
||||
@@ -16,7 +17,7 @@ use crate::checks::{Check, CheckKind, RejectedCmpop};
|
||||
use crate::python::builtins::BUILTINS;
|
||||
|
||||
/// Check IfTuple compliance.
|
||||
pub fn check_if_tuple(test: &Expr, location: Range) -> Option<Check> {
|
||||
pub fn if_tuple(test: &Expr, location: Range) -> Option<Check> {
|
||||
if let ExprKind::Tuple { elts, .. } = &test.node {
|
||||
if !elts.is_empty() {
|
||||
return Some(Check::new(CheckKind::IfTuple, location));
|
||||
@@ -26,7 +27,7 @@ pub fn check_if_tuple(test: &Expr, location: Range) -> Option<Check> {
|
||||
}
|
||||
|
||||
/// Check AssertTuple compliance.
|
||||
pub fn check_assert_tuple(test: &Expr, location: Range) -> Option<Check> {
|
||||
pub fn assert_tuple(test: &Expr, location: Range) -> Option<Check> {
|
||||
if let ExprKind::Tuple { elts, .. } = &test.node {
|
||||
if !elts.is_empty() {
|
||||
return Some(Check::new(CheckKind::AssertTuple, location));
|
||||
@@ -36,7 +37,7 @@ pub fn check_assert_tuple(test: &Expr, location: Range) -> Option<Check> {
|
||||
}
|
||||
|
||||
/// Check NotInTest and NotIsTest compliance.
|
||||
pub fn check_not_tests(
|
||||
pub fn not_tests(
|
||||
op: &Unaryop,
|
||||
operand: &Expr,
|
||||
check_not_in: bool,
|
||||
@@ -75,7 +76,7 @@ pub fn check_not_tests(
|
||||
}
|
||||
|
||||
/// Check UnusedVariable compliance.
|
||||
pub fn check_unused_variables(
|
||||
pub fn unused_variables(
|
||||
scope: &Scope,
|
||||
locator: &dyn CheckLocator,
|
||||
dummy_variable_rgx: &Regex,
|
||||
@@ -108,7 +109,7 @@ pub fn check_unused_variables(
|
||||
}
|
||||
|
||||
/// Check DoNotAssignLambda compliance.
|
||||
pub fn check_do_not_assign_lambda(value: &Expr, location: Range) -> Option<Check> {
|
||||
pub fn do_not_assign_lambda(value: &Expr, location: Range) -> Option<Check> {
|
||||
if let ExprKind::Lambda { .. } = &value.node {
|
||||
Some(Check::new(CheckKind::DoNotAssignLambda, location))
|
||||
} else {
|
||||
@@ -117,11 +118,7 @@ pub fn check_do_not_assign_lambda(value: &Expr, location: Range) -> Option<Check
|
||||
}
|
||||
|
||||
/// Check UselessMetaclassType compliance.
|
||||
pub fn check_useless_metaclass_type(
|
||||
targets: &Vec<Expr>,
|
||||
value: &Expr,
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
pub fn useless_metaclass_type(targets: &Vec<Expr>, value: &Expr, location: Range) -> Option<Check> {
|
||||
if targets.len() == 1 {
|
||||
if let ExprKind::Name { id, .. } = targets.first().map(|expr| &expr.node).unwrap() {
|
||||
if id == "__metaclass__" {
|
||||
@@ -137,7 +134,7 @@ pub fn check_useless_metaclass_type(
|
||||
}
|
||||
|
||||
/// Check UnnecessaryAbspath compliance.
|
||||
pub fn check_unnecessary_abspath(func: &Expr, args: &Vec<Expr>, location: Range) -> Option<Check> {
|
||||
pub fn unnecessary_abspath(func: &Expr, args: &Vec<Expr>, location: Range) -> Option<Check> {
|
||||
// Validate the arguments.
|
||||
if args.len() == 1 {
|
||||
if let ExprKind::Name { id, .. } = &args[0].node {
|
||||
@@ -193,7 +190,7 @@ impl Primitive {
|
||||
}
|
||||
|
||||
/// Check TypeOfPrimitive compliance.
|
||||
pub fn check_type_of_primitive(func: &Expr, args: &Vec<Expr>, location: Range) -> Option<Check> {
|
||||
pub fn type_of_primitive(func: &Expr, args: &Vec<Expr>, location: Range) -> Option<Check> {
|
||||
// Validate the arguments.
|
||||
if args.len() == 1 {
|
||||
match &func.node {
|
||||
@@ -221,7 +218,7 @@ fn is_ambiguous_name(name: &str) -> bool {
|
||||
}
|
||||
|
||||
/// Check AmbiguousVariableName compliance.
|
||||
pub fn check_ambiguous_variable_name(name: &str, location: Range) -> Option<Check> {
|
||||
pub fn ambiguous_variable_name(name: &str, location: Range) -> Option<Check> {
|
||||
if is_ambiguous_name(name) {
|
||||
Some(Check::new(
|
||||
CheckKind::AmbiguousVariableName(name.to_string()),
|
||||
@@ -233,7 +230,7 @@ pub fn check_ambiguous_variable_name(name: &str, location: Range) -> Option<Chec
|
||||
}
|
||||
|
||||
/// Check AmbiguousClassName compliance.
|
||||
pub fn check_ambiguous_class_name(name: &str, location: Range) -> Option<Check> {
|
||||
pub fn ambiguous_class_name(name: &str, location: Range) -> Option<Check> {
|
||||
if is_ambiguous_name(name) {
|
||||
Some(Check::new(
|
||||
CheckKind::AmbiguousClassName(name.to_string()),
|
||||
@@ -245,7 +242,7 @@ pub fn check_ambiguous_class_name(name: &str, location: Range) -> Option<Check>
|
||||
}
|
||||
|
||||
/// Check AmbiguousFunctionName compliance.
|
||||
pub fn check_ambiguous_function_name(name: &str, location: Range) -> Option<Check> {
|
||||
pub fn ambiguous_function_name(name: &str, location: Range) -> Option<Check> {
|
||||
if is_ambiguous_name(name) {
|
||||
Some(Check::new(
|
||||
CheckKind::AmbiguousFunctionName(name.to_string()),
|
||||
@@ -257,11 +254,7 @@ pub fn check_ambiguous_function_name(name: &str, location: Range) -> Option<Chec
|
||||
}
|
||||
|
||||
/// Check UselessObjectInheritance compliance.
|
||||
pub fn check_useless_object_inheritance(
|
||||
name: &str,
|
||||
bases: &[Expr],
|
||||
scope: &Scope,
|
||||
) -> Option<Check> {
|
||||
pub fn useless_object_inheritance(name: &str, bases: &[Expr], scope: &Scope) -> Option<Check> {
|
||||
for expr in bases {
|
||||
if let ExprKind::Name { id, .. } = &expr.node {
|
||||
if id == "object" {
|
||||
@@ -286,7 +279,7 @@ pub fn check_useless_object_inheritance(
|
||||
}
|
||||
|
||||
/// Check DefaultExceptNotLast compliance.
|
||||
pub fn check_default_except_not_last(handlers: &Vec<Excepthandler>) -> Option<Check> {
|
||||
pub fn default_except_not_last(handlers: &Vec<Excepthandler>) -> Option<Check> {
|
||||
for (idx, handler) in handlers.iter().enumerate() {
|
||||
let ExcepthandlerKind::ExceptHandler { type_, .. } = &handler.node;
|
||||
if type_.is_none() && idx < handlers.len() - 1 {
|
||||
@@ -301,7 +294,7 @@ pub fn check_default_except_not_last(handlers: &Vec<Excepthandler>) -> Option<Ch
|
||||
}
|
||||
|
||||
/// Check RaiseNotImplemented compliance.
|
||||
pub fn check_raise_not_implemented(expr: &Expr) -> Option<Check> {
|
||||
pub fn raise_not_implemented(expr: &Expr) -> Option<Check> {
|
||||
match &expr.node {
|
||||
ExprKind::Call { func, .. } => {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
@@ -328,7 +321,7 @@ pub fn check_raise_not_implemented(expr: &Expr) -> Option<Check> {
|
||||
}
|
||||
|
||||
/// Check DuplicateArgumentName compliance.
|
||||
pub fn check_duplicate_arguments(arguments: &Arguments) -> Vec<Check> {
|
||||
pub fn duplicate_arguments(arguments: &Arguments) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
// Collect all the arguments into a single vector.
|
||||
@@ -376,7 +369,7 @@ fn convert_to_value(expr: &Expr) -> Option<DictionaryKey> {
|
||||
}
|
||||
|
||||
/// Check MultiValueRepeatedKeyLiteral and MultiValueRepeatedKeyVariable compliance.
|
||||
pub fn check_repeated_keys(
|
||||
pub fn repeated_keys(
|
||||
keys: &Vec<Expr>,
|
||||
check_repeated_literals: bool,
|
||||
check_repeated_variables: bool,
|
||||
@@ -416,7 +409,7 @@ pub fn check_repeated_keys(
|
||||
}
|
||||
|
||||
/// Check TrueFalseComparison and NoneComparison compliance.
|
||||
pub fn check_literal_comparisons(
|
||||
pub fn literal_comparisons(
|
||||
left: &Expr,
|
||||
ops: &Vec<Cmpop>,
|
||||
comparators: &Vec<Expr>,
|
||||
@@ -547,7 +540,7 @@ fn is_constant_non_singleton(expr: &Expr) -> bool {
|
||||
}
|
||||
|
||||
/// Check IsLiteral compliance.
|
||||
pub fn check_is_literal(
|
||||
pub fn is_literal(
|
||||
left: &Expr,
|
||||
ops: &Vec<Cmpop>,
|
||||
comparators: &Vec<Expr>,
|
||||
@@ -569,11 +562,7 @@ pub fn check_is_literal(
|
||||
}
|
||||
|
||||
/// Check TypeComparison compliance.
|
||||
pub fn check_type_comparison(
|
||||
ops: &Vec<Cmpop>,
|
||||
comparators: &Vec<Expr>,
|
||||
location: Range,
|
||||
) -> Vec<Check> {
|
||||
pub fn type_comparison(ops: &Vec<Cmpop>, comparators: &Vec<Expr>, location: Range) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
for (op, right) in izip!(ops, comparators) {
|
||||
@@ -609,7 +598,7 @@ pub fn check_type_comparison(
|
||||
}
|
||||
|
||||
/// Check TwoStarredExpressions and TooManyExpressionsInStarredAssignment compliance.
|
||||
pub fn check_starred_expressions(
|
||||
pub fn starred_expressions(
|
||||
elts: &[Expr],
|
||||
check_too_many_expressions: bool,
|
||||
check_two_starred_expressions: bool,
|
||||
@@ -639,7 +628,7 @@ pub fn check_starred_expressions(
|
||||
}
|
||||
|
||||
/// Check BreakOutsideLoop compliance.
|
||||
pub fn check_break_outside_loop(
|
||||
pub fn break_outside_loop(
|
||||
stmt: &Stmt,
|
||||
parents: &[&Stmt],
|
||||
parent_stack: &[usize],
|
||||
@@ -680,7 +669,7 @@ pub fn check_break_outside_loop(
|
||||
}
|
||||
|
||||
/// Check ContinueOutsideLoop compliance.
|
||||
pub fn check_continue_outside_loop(
|
||||
pub fn continue_outside_loop(
|
||||
stmt: &Stmt,
|
||||
parents: &[&Stmt],
|
||||
parent_stack: &[usize],
|
||||
@@ -728,11 +717,7 @@ pub enum ShadowingType {
|
||||
}
|
||||
|
||||
/// Check builtin name shadowing
|
||||
pub fn check_builtin_shadowing(
|
||||
name: &str,
|
||||
location: Range,
|
||||
node_type: ShadowingType,
|
||||
) -> Option<Check> {
|
||||
pub fn builtin_shadowing(name: &str, location: Range, node_type: ShadowingType) -> Option<Check> {
|
||||
if BUILTINS.contains(&name) {
|
||||
Some(Check::new(
|
||||
match node_type {
|
||||
@@ -747,17 +732,7 @@ pub fn check_builtin_shadowing(
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if a call is an argumented `super` invocation.
|
||||
pub fn is_super_call_with_arguments(func: &Expr, args: &Vec<Expr>) -> bool {
|
||||
// Check: is this a `super` call?
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
id == "super" && !args.is_empty()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// flakes8-comprehensions
|
||||
// flake8-comprehensions
|
||||
/// Check `list(generator)` compliance.
|
||||
pub fn unnecessary_generator_list(expr: &Expr, func: &Expr, args: &Vec<Expr>) -> Option<Check> {
|
||||
if args.len() == 1 {
|
||||
@@ -969,6 +944,128 @@ pub fn unnecessary_collection_call(
|
||||
None
|
||||
}
|
||||
|
||||
pub fn unnecessary_literal_within_tuple_call(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
) -> Option<Check> {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "tuple" {
|
||||
if let Some(arg) = args.first() {
|
||||
match &arg.node {
|
||||
ExprKind::Tuple { .. } => {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryLiteralWithinTupleCall("tuple".to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
ExprKind::List { .. } => {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryLiteralWithinTupleCall("list".to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn unnecessary_literal_within_list_call(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
) -> Option<Check> {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "list" {
|
||||
if let Some(arg) = args.first() {
|
||||
match &arg.node {
|
||||
ExprKind::Tuple { .. } => {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryLiteralWithinListCall("tuple".to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
ExprKind::List { .. } => {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryLiteralWithinListCall("list".to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn unnecessary_double_cast_or_process(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
) -> Option<Check> {
|
||||
if let ExprKind::Name { id: outer, .. } = &func.node {
|
||||
if outer == "list"
|
||||
|| outer == "tuple"
|
||||
|| outer == "set"
|
||||
|| outer == "reversed"
|
||||
|| outer == "sorted"
|
||||
{
|
||||
if let Some(arg) = args.first() {
|
||||
if let ExprKind::Call { func, .. } = &arg.node {
|
||||
if let ExprKind::Name { id: inner, .. } = &func.node {
|
||||
// Ex) set(tuple(...))
|
||||
if (outer == "set" || outer == "sorted")
|
||||
&& (inner == "list"
|
||||
|| inner == "tuple"
|
||||
|| inner == "reversed"
|
||||
|| inner == "sorted")
|
||||
{
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryDoubleCastOrProcess(
|
||||
inner.to_string(),
|
||||
outer.to_string(),
|
||||
),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
|
||||
// Ex) list(tuple(...))
|
||||
if (outer == "list" || outer == "tuple")
|
||||
&& (inner == "list" || inner == "tuple")
|
||||
{
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryDoubleCastOrProcess(
|
||||
inner.to_string(),
|
||||
outer.to_string(),
|
||||
),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
|
||||
// Ex) set(set(...))
|
||||
if outer == "set" && inner == "set" {
|
||||
return Some(Check::new(
|
||||
CheckKind::UnnecessaryDoubleCastOrProcess(
|
||||
inner.to_string(),
|
||||
outer.to_string(),
|
||||
),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn unnecessary_subscript_reversal(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
|
||||
if let Some(first_arg) = args.first() {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
@@ -1009,14 +1106,14 @@ pub fn unnecessary_subscript_reversal(expr: &Expr, func: &Expr, args: &[Expr]) -
|
||||
|
||||
// flake8-super
|
||||
/// Check that `super()` has no args
|
||||
pub fn check_super_args(
|
||||
pub fn super_args(
|
||||
scope: &Scope,
|
||||
parents: &[&Stmt],
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &Vec<Expr>,
|
||||
) -> Option<Check> {
|
||||
if !is_super_call_with_arguments(func, args) {
|
||||
if !helpers::is_super_call_with_arguments(func, args) {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -1075,7 +1172,7 @@ pub fn check_super_args(
|
||||
|
||||
// flake8-print
|
||||
/// Check whether a function call is a `print` or `pprint` invocation
|
||||
pub fn check_print_call(
|
||||
pub fn print_call(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
check_print: bool,
|
||||
@@ -1,10 +1,42 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_ast::{Expr, ExprKind, StmtKind};
|
||||
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, StmtKind};
|
||||
|
||||
use crate::python::typing;
|
||||
|
||||
static DUNDER_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"__[^\s]+__").unwrap());
|
||||
fn compose_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
|
||||
match &expr.node {
|
||||
ExprKind::Call { func, .. } => {
|
||||
compose_call_path_inner(func, parts);
|
||||
}
|
||||
ExprKind::Attribute { value, attr, .. } => {
|
||||
compose_call_path_inner(value, parts);
|
||||
parts.push(attr);
|
||||
}
|
||||
ExprKind::Name { id, .. } => {
|
||||
parts.push(id);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compose_call_path(expr: &Expr) -> Option<String> {
|
||||
let mut segments = vec![];
|
||||
compose_call_path_inner(expr, &mut segments);
|
||||
if segments.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(segments.join("."))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { attr, .. } => target == attr,
|
||||
ExprKind::Name { id, .. } => target == id,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SubscriptKind {
|
||||
AnnotatedSubscript,
|
||||
@@ -35,6 +67,8 @@ pub fn match_annotated_subscript(expr: &Expr) -> Option<SubscriptKind> {
|
||||
}
|
||||
}
|
||||
|
||||
static DUNDER_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"__[^\s]+__").unwrap());
|
||||
|
||||
pub fn is_assignment_to_a_dunder(node: &StmtKind) -> bool {
|
||||
// Check whether it's an assignment to a dunder, with or without a type annotation.
|
||||
// This is what pycodestyle (as of 2.9.1) does.
|
||||
@@ -65,10 +99,35 @@ pub fn is_assignment_to_a_dunder(node: &StmtKind) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { attr, .. } => target == attr,
|
||||
ExprKind::Name { id, .. } => target == id,
|
||||
_ => false,
|
||||
/// Extract the names of all handled exceptions.
|
||||
pub fn extract_handler_names(handlers: &[Excepthandler]) -> Vec<String> {
|
||||
let mut handler_names = vec![];
|
||||
for handler in handlers {
|
||||
match &handler.node {
|
||||
ExcepthandlerKind::ExceptHandler { type_, .. } => {
|
||||
if let Some(type_) = type_ {
|
||||
if let ExprKind::Tuple { elts, .. } = &type_.node {
|
||||
for type_ in elts {
|
||||
if let Some(name) = compose_call_path(type_) {
|
||||
handler_names.push(name);
|
||||
}
|
||||
}
|
||||
} else if let Some(name) = compose_call_path(type_) {
|
||||
handler_names.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
handler_names
|
||||
}
|
||||
|
||||
/// Returns `true` if a call is an argumented `super` invocation.
|
||||
pub fn is_super_call_with_arguments(func: &Expr, args: &Vec<Expr>) -> bool {
|
||||
// Check: is this a `super` call?
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
id == "super" && !args.is_empty()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ impl<'a> SourceCodeLocator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slice_source_code_at(&mut self, location: &Location) -> &'a str {
|
||||
fn init(&mut self) {
|
||||
if !self.initialized {
|
||||
let mut offset = 0;
|
||||
for i in self.content.lines() {
|
||||
@@ -142,24 +142,40 @@ impl<'a> SourceCodeLocator<'a> {
|
||||
offset += i.len();
|
||||
offset += 1;
|
||||
}
|
||||
self.offsets.push(offset);
|
||||
self.initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slice_source_code_at(&mut self, location: &Location) -> &'a str {
|
||||
self.init();
|
||||
let offset = self.offsets[location.row() - 1] + location.column() - 1;
|
||||
&self.content[offset..]
|
||||
}
|
||||
|
||||
pub fn slice_source_code_range(&mut self, range: &Range) -> &'a str {
|
||||
if !self.initialized {
|
||||
let mut offset = 0;
|
||||
for i in self.content.lines() {
|
||||
self.offsets.push(offset);
|
||||
offset += i.len();
|
||||
offset += 1;
|
||||
}
|
||||
self.initialized = true;
|
||||
}
|
||||
self.init();
|
||||
let start = self.offsets[range.location.row() - 1] + range.location.column() - 1;
|
||||
let end = self.offsets[range.end_location.row() - 1] + range.end_location.column() - 1;
|
||||
&self.content[start..end]
|
||||
}
|
||||
|
||||
pub fn partition_source_code_at(
|
||||
&mut self,
|
||||
outer: &Range,
|
||||
inner: &Range,
|
||||
) -> (&'a str, &'a str, &'a str) {
|
||||
self.init();
|
||||
let outer_start = self.offsets[outer.location.row() - 1] + outer.location.column() - 1;
|
||||
let outer_end =
|
||||
self.offsets[outer.end_location.row() - 1] + outer.end_location.column() - 1;
|
||||
let inner_start = self.offsets[inner.location.row() - 1] + inner.location.column() - 1;
|
||||
let inner_end =
|
||||
self.offsets[inner.end_location.row() - 1] + inner.end_location.column() - 1;
|
||||
(
|
||||
&self.content[outer_start..inner_start],
|
||||
&self.content[inner_start..inner_end],
|
||||
&self.content[inner_end..outer_end],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::ast::helpers::match_name_or_attr;
|
||||
use rustpython_parser::ast::{
|
||||
Alias, Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, Excepthandler,
|
||||
ExcepthandlerKind, Expr, ExprContext, ExprKind, Keyword, MatchCase, Operator, Pattern,
|
||||
PatternKind, Stmt, StmtKind, Unaryop, Withitem,
|
||||
};
|
||||
|
||||
use crate::ast::helpers::match_name_or_attr;
|
||||
|
||||
pub trait Visitor<'a> {
|
||||
fn visit_stmt(&mut self, stmt: &'a Stmt) {
|
||||
walk_stmt(self, stmt);
|
||||
|
||||
382
src/check_ast.rs
382
src/check_ast.rs
@@ -10,7 +10,7 @@ use rustpython_parser::ast::{
|
||||
};
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::ast::helpers::{match_name_or_attr, SubscriptKind};
|
||||
use crate::ast::helpers::{extract_handler_names, match_name_or_attr, SubscriptKind};
|
||||
use crate::ast::operations::{extract_all_names, SourceCodeLocator};
|
||||
use crate::ast::relocate::relocate_expr;
|
||||
use crate::ast::types::{
|
||||
@@ -18,25 +18,30 @@ use crate::ast::types::{
|
||||
ScopeKind,
|
||||
};
|
||||
use crate::ast::visitor::{walk_excepthandler, Visitor};
|
||||
use crate::ast::{checks, helpers, operations, visitor};
|
||||
use crate::ast::{checkers, helpers, operations, visitor};
|
||||
use crate::autofix::{fixer, fixes};
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::docstrings::docstring_checks;
|
||||
use crate::docstrings::types::{Definition, DefinitionKind, Documentable};
|
||||
use crate::plugins;
|
||||
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
|
||||
use crate::python::future::ALL_FEATURE_NAMES;
|
||||
use crate::settings::{PythonVersion, Settings};
|
||||
use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope};
|
||||
|
||||
pub const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
|
||||
pub struct Checker<'a> {
|
||||
// Input data.
|
||||
path: &'a Path,
|
||||
pub(crate) path: &'a Path,
|
||||
// TODO(charlie): Separate immutable from mutable state. (None of these should ever change.)
|
||||
pub(crate) locator: SourceCodeLocator<'a>,
|
||||
pub(crate) settings: &'a Settings,
|
||||
pub(crate) autofix: &'a fixer::Mode,
|
||||
// Computed checks.
|
||||
checks: Vec<Check>,
|
||||
// Docstring tracking.
|
||||
docstrings: Vec<(Definition<'a>, Visibility)>,
|
||||
// Edit tracking.
|
||||
// TODO(charlie): Instead of exposing deletions, wrap in a public API.
|
||||
pub(crate) deletions: BTreeSet<usize>,
|
||||
@@ -49,17 +54,18 @@ pub struct Checker<'a> {
|
||||
dead_scopes: Vec<usize>,
|
||||
deferred_string_annotations: Vec<(Range, &'a str)>,
|
||||
deferred_annotations: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
|
||||
deferred_functions: Vec<(&'a Stmt, Vec<usize>, Vec<usize>)>,
|
||||
deferred_functions: Vec<(&'a Stmt, Vec<usize>, Vec<usize>, VisibleScope)>,
|
||||
deferred_lambdas: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
|
||||
deferred_assignments: Vec<usize>,
|
||||
// Internal, derivative state.
|
||||
visible_scope: VisibleScope,
|
||||
in_f_string: Option<Range>,
|
||||
in_annotation: bool,
|
||||
in_literal: bool,
|
||||
seen_non_import: bool,
|
||||
seen_docstring: bool,
|
||||
seen_import_boundary: bool,
|
||||
futures_allowed: bool,
|
||||
annotations_future_enabled: bool,
|
||||
except_handlers: Vec<Vec<String>>,
|
||||
}
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
@@ -74,25 +80,30 @@ impl<'a> Checker<'a> {
|
||||
autofix,
|
||||
path,
|
||||
locator: SourceCodeLocator::new(content),
|
||||
checks: vec![],
|
||||
parents: vec![],
|
||||
parent_stack: vec![],
|
||||
scopes: vec![],
|
||||
scope_stack: vec![],
|
||||
dead_scopes: vec![],
|
||||
deferred_string_annotations: vec![],
|
||||
deferred_annotations: vec![],
|
||||
deferred_functions: vec![],
|
||||
deferred_lambdas: vec![],
|
||||
deferred_assignments: vec![],
|
||||
in_f_string: None,
|
||||
in_annotation: false,
|
||||
in_literal: false,
|
||||
seen_non_import: false,
|
||||
seen_docstring: false,
|
||||
futures_allowed: true,
|
||||
annotations_future_enabled: false,
|
||||
checks: Default::default(),
|
||||
docstrings: Default::default(),
|
||||
deletions: Default::default(),
|
||||
parents: Default::default(),
|
||||
parent_stack: Default::default(),
|
||||
scopes: Default::default(),
|
||||
scope_stack: Default::default(),
|
||||
dead_scopes: Default::default(),
|
||||
deferred_string_annotations: Default::default(),
|
||||
deferred_annotations: Default::default(),
|
||||
deferred_functions: Default::default(),
|
||||
deferred_lambdas: Default::default(),
|
||||
deferred_assignments: Default::default(),
|
||||
visible_scope: VisibleScope {
|
||||
modifier: Modifier::Module,
|
||||
visibility: module_visibility(path),
|
||||
},
|
||||
in_f_string: None,
|
||||
in_annotation: Default::default(),
|
||||
in_literal: Default::default(),
|
||||
seen_import_boundary: Default::default(),
|
||||
futures_allowed: true,
|
||||
annotations_future_enabled: Default::default(),
|
||||
except_handlers: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,48 +130,13 @@ where
|
||||
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;
|
||||
}
|
||||
}
|
||||
node => {
|
||||
self.futures_allowed = false;
|
||||
|
||||
if !self.seen_non_import
|
||||
if !self.seen_import_boundary
|
||||
&& !helpers::is_assignment_to_a_dunder(node)
|
||||
&& !operations::in_nested_block(&self.parent_stack, &self.parents)
|
||||
{
|
||||
self.seen_non_import = true;
|
||||
self.seen_import_boundary = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,27 +166,24 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::E741) {
|
||||
let location = self.locate_check(Range::from_located(stmt));
|
||||
self.checks.extend(
|
||||
names.iter().filter_map(|name| {
|
||||
checks::check_ambiguous_variable_name(name, location)
|
||||
}),
|
||||
names
|
||||
.iter()
|
||||
.filter_map(|name| checkers::ambiguous_variable_name(name, location)),
|
||||
);
|
||||
}
|
||||
}
|
||||
StmtKind::Break => {
|
||||
if self.settings.enabled.contains(&CheckCode::F701) {
|
||||
if let Some(check) = checks::check_break_outside_loop(
|
||||
stmt,
|
||||
&self.parents,
|
||||
&self.parent_stack,
|
||||
self,
|
||||
) {
|
||||
if let Some(check) =
|
||||
checkers::break_outside_loop(stmt, &self.parents, &self.parent_stack, self)
|
||||
{
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::Continue => {
|
||||
if self.settings.enabled.contains(&CheckCode::F702) {
|
||||
if let Some(check) = checks::check_continue_outside_loop(
|
||||
if let Some(check) = checkers::continue_outside_loop(
|
||||
stmt,
|
||||
&self.parents,
|
||||
&self.parent_stack,
|
||||
@@ -235,7 +208,7 @@ where
|
||||
..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::E743) {
|
||||
if let Some(check) = checks::check_ambiguous_function_name(
|
||||
if let Some(check) = checkers::ambiguous_function_name(
|
||||
name,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
) {
|
||||
@@ -323,7 +296,7 @@ where
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::E742) {
|
||||
if let Some(check) = checks::check_ambiguous_class_name(
|
||||
if let Some(check) = checkers::ambiguous_class_name(
|
||||
name,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
) {
|
||||
@@ -353,7 +326,7 @@ where
|
||||
.settings
|
||||
.enabled
|
||||
.contains(CheckKind::ModuleImportNotAtTopOfFile.code())
|
||||
&& self.seen_non_import
|
||||
&& self.seen_import_boundary
|
||||
&& stmt.location.column() == 1
|
||||
{
|
||||
self.checks.push(Check::new(
|
||||
@@ -414,7 +387,7 @@ where
|
||||
.settings
|
||||
.enabled
|
||||
.contains(CheckKind::ModuleImportNotAtTopOfFile.code())
|
||||
&& self.seen_non_import
|
||||
&& self.seen_import_boundary
|
||||
&& stmt.location.column() == 1
|
||||
{
|
||||
self.checks.push(Check::new(
|
||||
@@ -532,7 +505,7 @@ where
|
||||
StmtKind::Raise { exc, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::F901) {
|
||||
if let Some(expr) = exc {
|
||||
if let Some(check) = checks::check_raise_not_implemented(expr) {
|
||||
if let Some(check) = checkers::raise_not_implemented(expr) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
@@ -546,21 +519,29 @@ where
|
||||
plugins::if_tuple(self, stmt, test);
|
||||
}
|
||||
}
|
||||
StmtKind::Assert { test, .. } => {
|
||||
StmtKind::Assert { test, msg } => {
|
||||
if self.settings.enabled.contains(&CheckCode::F631) {
|
||||
plugins::assert_tuple(self, stmt, test);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B011) {
|
||||
plugins::assert_false(self, stmt, test, msg);
|
||||
}
|
||||
}
|
||||
StmtKind::Try { handlers, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::F707) {
|
||||
if let Some(check) = checks::check_default_except_not_last(handlers) {
|
||||
if let Some(check) = checkers::default_except_not_last(handlers) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B014)
|
||||
|| self.settings.enabled.contains(&CheckCode::B025)
|
||||
{
|
||||
plugins::duplicate_exceptions(self, stmt, handlers);
|
||||
}
|
||||
}
|
||||
StmtKind::Assign { targets, value, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E731) {
|
||||
if let Some(check) = checks::check_do_not_assign_lambda(
|
||||
if let Some(check) = checkers::do_not_assign_lambda(
|
||||
value,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
) {
|
||||
@@ -574,7 +555,7 @@ where
|
||||
StmtKind::AnnAssign { value, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E731) {
|
||||
if let Some(value) = value {
|
||||
if let Some(check) = checks::check_do_not_assign_lambda(
|
||||
if let Some(check) = checkers::do_not_assign_lambda(
|
||||
value,
|
||||
self.locate_check(Range::from_located(stmt)),
|
||||
) {
|
||||
@@ -588,21 +569,65 @@ where
|
||||
}
|
||||
|
||||
// Recurse.
|
||||
let prev_visibile_scope = self.visible_scope.clone();
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
|
||||
StmtKind::FunctionDef { body, .. } | StmtKind::AsyncFunctionDef { body, .. } => {
|
||||
let definition = docstring_checks::extract(
|
||||
&self.visible_scope,
|
||||
stmt,
|
||||
body,
|
||||
&Documentable::Function,
|
||||
);
|
||||
let scope = transition_scope(&self.visible_scope, stmt, &Documentable::Function);
|
||||
self.docstrings.push((definition, scope.visibility.clone()));
|
||||
self.visible_scope = scope;
|
||||
|
||||
self.deferred_functions.push((
|
||||
stmt,
|
||||
self.scope_stack.clone(),
|
||||
self.parent_stack.clone(),
|
||||
self.visible_scope.clone(),
|
||||
));
|
||||
}
|
||||
StmtKind::ClassDef { body, .. } => {
|
||||
let definition = docstring_checks::extract(
|
||||
&self.visible_scope,
|
||||
stmt,
|
||||
body,
|
||||
&Documentable::Class,
|
||||
);
|
||||
let scope = transition_scope(&self.visible_scope, stmt, &Documentable::Class);
|
||||
self.docstrings.push((definition, scope.visibility.clone()));
|
||||
self.visible_scope = scope;
|
||||
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
}
|
||||
StmtKind::Try {
|
||||
body,
|
||||
handlers,
|
||||
orelse,
|
||||
finalbody,
|
||||
} => {
|
||||
self.except_handlers.push(extract_handler_names(handlers));
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.except_handlers.pop();
|
||||
for excepthandler in handlers {
|
||||
self.visit_excepthandler(excepthandler)
|
||||
}
|
||||
for stmt in orelse {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
for stmt in finalbody {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
}
|
||||
_ => visitor::walk_stmt(self, stmt),
|
||||
};
|
||||
self.visible_scope = prev_visibile_scope;
|
||||
|
||||
// Post-visit.
|
||||
if let StmtKind::ClassDef { name, .. } = &stmt.node {
|
||||
@@ -670,7 +695,7 @@ where
|
||||
self.settings.enabled.contains(&CheckCode::F621);
|
||||
let check_two_starred_expressions =
|
||||
self.settings.enabled.contains(&CheckCode::F622);
|
||||
if let Some(check) = checks::check_starred_expressions(
|
||||
if let Some(check) = checkers::starred_expressions(
|
||||
elts,
|
||||
check_too_many_expressions,
|
||||
check_two_starred_expressions,
|
||||
@@ -693,7 +718,7 @@ where
|
||||
}
|
||||
ExprContext::Store => {
|
||||
if self.settings.enabled.contains(&CheckCode::E741) {
|
||||
if let Some(check) = checks::check_ambiguous_variable_name(
|
||||
if let Some(check) = checkers::ambiguous_variable_name(
|
||||
id,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
@@ -743,26 +768,26 @@ where
|
||||
|
||||
// flake8-comprehensions
|
||||
if self.settings.enabled.contains(&CheckCode::C400) {
|
||||
if let Some(check) = checks::unnecessary_generator_list(expr, func, args) {
|
||||
if let Some(check) = checkers::unnecessary_generator_list(expr, func, args) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C401) {
|
||||
if let Some(check) = checks::unnecessary_generator_set(expr, func, args) {
|
||||
if let Some(check) = checkers::unnecessary_generator_set(expr, func, args) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C402) {
|
||||
if let Some(check) = checks::unnecessary_generator_dict(expr, func, args) {
|
||||
if let Some(check) = checkers::unnecessary_generator_dict(expr, func, args) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C403) {
|
||||
if let Some(check) =
|
||||
checks::unnecessary_list_comprehension_set(expr, func, args)
|
||||
checkers::unnecessary_list_comprehension_set(expr, func, args)
|
||||
{
|
||||
self.checks.push(check);
|
||||
};
|
||||
@@ -770,34 +795,59 @@ where
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C404) {
|
||||
if let Some(check) =
|
||||
checks::unnecessary_list_comprehension_dict(expr, func, args)
|
||||
checkers::unnecessary_list_comprehension_dict(expr, func, args)
|
||||
{
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C405) {
|
||||
if let Some(check) = checks::unnecessary_literal_set(expr, func, args) {
|
||||
if let Some(check) = checkers::unnecessary_literal_set(expr, func, args) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C406) {
|
||||
if let Some(check) = checks::unnecessary_literal_dict(expr, func, args) {
|
||||
if let Some(check) = checkers::unnecessary_literal_dict(expr, func, args) {
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C408) {
|
||||
if let Some(check) =
|
||||
checks::unnecessary_collection_call(expr, func, args, keywords)
|
||||
checkers::unnecessary_collection_call(expr, func, args, keywords)
|
||||
{
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C409) {
|
||||
if let Some(check) =
|
||||
checkers::unnecessary_literal_within_tuple_call(expr, func, args)
|
||||
{
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C410) {
|
||||
if let Some(check) =
|
||||
checkers::unnecessary_literal_within_list_call(expr, func, args)
|
||||
{
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C414) {
|
||||
if let Some(check) =
|
||||
checkers::unnecessary_double_cast_or_process(expr, func, args)
|
||||
{
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::C415) {
|
||||
if let Some(check) = checks::unnecessary_subscript_reversal(expr, func, args) {
|
||||
if let Some(check) = checkers::unnecessary_subscript_reversal(expr, func, args)
|
||||
{
|
||||
self.checks.push(check);
|
||||
};
|
||||
}
|
||||
@@ -832,7 +882,7 @@ where
|
||||
let check_repeated_literals = self.settings.enabled.contains(&CheckCode::F601);
|
||||
let check_repeated_variables = self.settings.enabled.contains(&CheckCode::F602);
|
||||
if check_repeated_literals || check_repeated_variables {
|
||||
self.checks.extend(checks::check_repeated_keys(
|
||||
self.checks.extend(checkers::repeated_keys(
|
||||
keys,
|
||||
check_repeated_literals,
|
||||
check_repeated_variables,
|
||||
@@ -884,7 +934,7 @@ where
|
||||
let check_not_in = self.settings.enabled.contains(&CheckCode::E713);
|
||||
let check_not_is = self.settings.enabled.contains(&CheckCode::E714);
|
||||
if check_not_in || check_not_is {
|
||||
self.checks.extend(checks::check_not_tests(
|
||||
self.checks.extend(checkers::not_tests(
|
||||
op,
|
||||
operand,
|
||||
check_not_in,
|
||||
@@ -901,7 +951,7 @@ where
|
||||
let check_none_comparisons = self.settings.enabled.contains(&CheckCode::E711);
|
||||
let check_true_false_comparisons = self.settings.enabled.contains(&CheckCode::E712);
|
||||
if check_none_comparisons || check_true_false_comparisons {
|
||||
self.checks.extend(checks::check_literal_comparisons(
|
||||
self.checks.extend(checkers::literal_comparisons(
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
@@ -912,7 +962,7 @@ where
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::F632) {
|
||||
self.checks.extend(checks::check_is_literal(
|
||||
self.checks.extend(checkers::is_literal(
|
||||
left,
|
||||
ops,
|
||||
comparators,
|
||||
@@ -921,7 +971,7 @@ where
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::E721) {
|
||||
self.checks.extend(checks::check_type_comparison(
|
||||
self.checks.extend(checkers::type_comparison(
|
||||
ops,
|
||||
comparators,
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
@@ -1148,7 +1198,7 @@ where
|
||||
match name {
|
||||
Some(name) => {
|
||||
if self.settings.enabled.contains(&CheckCode::E741) {
|
||||
if let Some(check) = checks::check_ambiguous_variable_name(
|
||||
if let Some(check) = checkers::ambiguous_variable_name(
|
||||
name,
|
||||
self.locate_check(Range::from_located(excepthandler)),
|
||||
) {
|
||||
@@ -1216,8 +1266,7 @@ where
|
||||
|
||||
fn visit_arguments(&mut self, arguments: &'b Arguments) {
|
||||
if self.settings.enabled.contains(&CheckCode::F831) {
|
||||
self.checks
|
||||
.extend(checks::check_duplicate_arguments(arguments));
|
||||
self.checks.extend(checkers::duplicate_arguments(arguments));
|
||||
}
|
||||
|
||||
// Bind, but intentionally avoid walking default expressions, as we handle them upstream.
|
||||
@@ -1250,7 +1299,7 @@ where
|
||||
);
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::E741) {
|
||||
if let Some(check) = checks::check_ambiguous_variable_name(
|
||||
if let Some(check) = checkers::ambiguous_variable_name(
|
||||
&arg.node.arg,
|
||||
self.locate_check(Range::from_located(arg)),
|
||||
) {
|
||||
@@ -1471,6 +1520,14 @@ impl<'a> Checker<'a> {
|
||||
if self.path.ends_with("__init__.py") && id == "__path__" {
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid flagging if NameError is handled.
|
||||
if let Some(handler_names) = self.except_handlers.last() {
|
||||
if handler_names.contains(&"NameError".to_string()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.checks.push(Check::new(
|
||||
CheckKind::UndefinedName(id.clone()),
|
||||
self.locate_check(Range::from_located(expr)),
|
||||
@@ -1594,6 +1651,25 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_docstring<'b>(&mut self, python_ast: &'b Suite) -> bool
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
let docstring = docstring_checks::docstring_from(python_ast);
|
||||
self.docstrings.push((
|
||||
Definition {
|
||||
kind: if self.path.ends_with("__init__.py") {
|
||||
DefinitionKind::Package
|
||||
} else {
|
||||
DefinitionKind::Module
|
||||
},
|
||||
docstring,
|
||||
},
|
||||
self.visible_scope.visibility.clone(),
|
||||
));
|
||||
docstring.is_some()
|
||||
}
|
||||
|
||||
fn check_deferred_annotations(&mut self) {
|
||||
while let Some((expr, scopes, parents)) = self.deferred_annotations.pop() {
|
||||
self.parent_stack = parents;
|
||||
@@ -1646,9 +1722,10 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
fn check_deferred_functions(&mut self) {
|
||||
while let Some((stmt, scopes, parents)) = self.deferred_functions.pop() {
|
||||
while let Some((stmt, scopes, parents, visibility)) = self.deferred_functions.pop() {
|
||||
self.parent_stack = parents;
|
||||
self.scope_stack = scopes;
|
||||
self.visible_scope = visibility;
|
||||
self.push_scope(Scope::new(ScopeKind::Function(Default::default())));
|
||||
|
||||
match &stmt.node {
|
||||
@@ -1690,7 +1767,7 @@ impl<'a> Checker<'a> {
|
||||
fn check_deferred_assignments(&mut self) {
|
||||
if self.settings.enabled.contains(&CheckCode::F841) {
|
||||
while let Some(index) = self.deferred_assignments.pop() {
|
||||
self.checks.extend(checks::check_unused_variables(
|
||||
self.checks.extend(checkers::unused_variables(
|
||||
&self.scopes[index],
|
||||
self,
|
||||
&self.settings.dummy_variable_rgx,
|
||||
@@ -1839,25 +1916,102 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn check_docstrings(&mut self) {
|
||||
while let Some((docstring, visibility)) = self.docstrings.pop() {
|
||||
if !docstring_checks::not_empty(self, &docstring) {
|
||||
continue;
|
||||
}
|
||||
if !docstring_checks::not_missing(self, &docstring, &visibility) {
|
||||
continue;
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D200) {
|
||||
docstring_checks::one_liner(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D201)
|
||||
|| self.settings.enabled.contains(&CheckCode::D202)
|
||||
{
|
||||
docstring_checks::blank_before_after_function(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D203)
|
||||
|| self.settings.enabled.contains(&CheckCode::D204)
|
||||
|| self.settings.enabled.contains(&CheckCode::D211)
|
||||
{
|
||||
docstring_checks::blank_before_after_class(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D205) {
|
||||
docstring_checks::blank_after_summary(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D209) {
|
||||
docstring_checks::newline_after_last_paragraph(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D210) {
|
||||
docstring_checks::no_surrounding_whitespace(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D212)
|
||||
|| self.settings.enabled.contains(&CheckCode::D213)
|
||||
{
|
||||
docstring_checks::multi_line_summary_start(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D300) {
|
||||
docstring_checks::triple_quotes(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D400) {
|
||||
docstring_checks::ends_with_period(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D402) {
|
||||
docstring_checks::no_signature(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D403) {
|
||||
docstring_checks::capitalized(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D404) {
|
||||
docstring_checks::starts_with_this(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D415) {
|
||||
docstring_checks::ends_with_punctuation(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D418) {
|
||||
docstring_checks::if_needed(self, &docstring);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::D407)
|
||||
|| self.settings.enabled.contains(&CheckCode::D414)
|
||||
|| self.settings.enabled.contains(&CheckCode::D407)
|
||||
|| self.settings.enabled.contains(&CheckCode::D212)
|
||||
|| self.settings.enabled.contains(&CheckCode::D408)
|
||||
|| self.settings.enabled.contains(&CheckCode::D409)
|
||||
|| self.settings.enabled.contains(&CheckCode::D414)
|
||||
|| self.settings.enabled.contains(&CheckCode::D412)
|
||||
|| self.settings.enabled.contains(&CheckCode::D414)
|
||||
|| self.settings.enabled.contains(&CheckCode::D405)
|
||||
|| self.settings.enabled.contains(&CheckCode::D413)
|
||||
|| self.settings.enabled.contains(&CheckCode::D410)
|
||||
|| self.settings.enabled.contains(&CheckCode::D411)
|
||||
|| self.settings.enabled.contains(&CheckCode::D406)
|
||||
{
|
||||
docstring_checks::check_sections(self, &docstring);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_builtin_shadowing(&mut self, name: &str, location: Range, is_attribute: bool) {
|
||||
let scope = self.current_scope();
|
||||
|
||||
// flake8-builtins
|
||||
if is_attribute && matches!(scope.kind, ScopeKind::Class) {
|
||||
if self.settings.enabled.contains(&CheckCode::A003) {
|
||||
if let Some(check) = checks::check_builtin_shadowing(
|
||||
if let Some(check) = checkers::builtin_shadowing(
|
||||
name,
|
||||
self.locate_check(location),
|
||||
checks::ShadowingType::Attribute,
|
||||
checkers::ShadowingType::Attribute,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
}
|
||||
} else if self.settings.enabled.contains(&CheckCode::A001) {
|
||||
if let Some(check) = checks::check_builtin_shadowing(
|
||||
if let Some(check) = checkers::builtin_shadowing(
|
||||
name,
|
||||
self.locate_check(location),
|
||||
checks::ShadowingType::Variable,
|
||||
checkers::ShadowingType::Variable,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
@@ -1866,10 +2020,10 @@ impl<'a> Checker<'a> {
|
||||
|
||||
fn check_builtin_arg_shadowing(&mut self, name: &str, location: Range) {
|
||||
if self.settings.enabled.contains(&CheckCode::A002) {
|
||||
if let Some(check) = checks::check_builtin_shadowing(
|
||||
if let Some(check) = checkers::builtin_shadowing(
|
||||
name,
|
||||
self.locate_check(location),
|
||||
checks::ShadowingType::Argument,
|
||||
checkers::ShadowingType::Argument,
|
||||
) {
|
||||
self.checks.push(check);
|
||||
}
|
||||
@@ -1888,6 +2042,13 @@ pub fn check_ast(
|
||||
checker.push_scope(Scope::new(ScopeKind::Module));
|
||||
checker.bind_builtins();
|
||||
|
||||
// Check for module docstring.
|
||||
let python_ast = if checker.visit_docstring(python_ast) {
|
||||
&python_ast[1..]
|
||||
} else {
|
||||
python_ast
|
||||
};
|
||||
|
||||
// Iterate over the AST.
|
||||
for stmt in python_ast {
|
||||
checker.visit_stmt(stmt);
|
||||
@@ -1906,5 +2067,8 @@ pub fn check_ast(
|
||||
checker.pop_scope();
|
||||
checker.check_dead_scopes();
|
||||
|
||||
// Check docstrings.
|
||||
checker.check_docstrings();
|
||||
|
||||
checker.checks
|
||||
}
|
||||
|
||||
@@ -184,15 +184,15 @@ pub fn check_lines(
|
||||
let mut valid_codes = vec![];
|
||||
for code in codes {
|
||||
if !matches.contains(&code) {
|
||||
invalid_codes.push(code);
|
||||
invalid_codes.push(code.to_string());
|
||||
} else {
|
||||
valid_codes.push(code);
|
||||
valid_codes.push(code.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if !invalid_codes.is_empty() {
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedNOQA(Some(invalid_codes.join(", "))),
|
||||
CheckKind::UnusedNOQA(Some(invalid_codes)),
|
||||
Range {
|
||||
location: Location::new(row + 1, start + 1),
|
||||
end_location: Location::new(row + 1, end + 1),
|
||||
|
||||
348
src/checks.rs
348
src/checks.rs
@@ -1,9 +1,11 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use itertools::Itertools;
|
||||
use rustpython_parser::ast::Location;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::{AsRefStr, EnumIter, EnumString};
|
||||
|
||||
use crate::ast::checks::Primitive;
|
||||
use crate::ast::checkers::Primitive;
|
||||
use crate::ast::types::Range;
|
||||
|
||||
pub const DEFAULT_CHECK_CODES: [CheckCode; 43] = [
|
||||
@@ -120,6 +122,10 @@ pub enum CheckCode {
|
||||
A001,
|
||||
A002,
|
||||
A003,
|
||||
// flake8-bugbear
|
||||
B011,
|
||||
B014,
|
||||
B025,
|
||||
// flake8-comprehensions
|
||||
C400,
|
||||
C401,
|
||||
@@ -129,6 +135,9 @@ pub enum CheckCode {
|
||||
C405,
|
||||
C406,
|
||||
C408,
|
||||
C409,
|
||||
C410,
|
||||
C414,
|
||||
C415,
|
||||
// flake8-print
|
||||
T201,
|
||||
@@ -142,6 +151,44 @@ pub enum CheckCode {
|
||||
U006,
|
||||
U007,
|
||||
U008,
|
||||
// pydocstyle
|
||||
D100,
|
||||
D101,
|
||||
D102,
|
||||
D103,
|
||||
D104,
|
||||
D105,
|
||||
D106,
|
||||
D107,
|
||||
D200,
|
||||
D201,
|
||||
D202,
|
||||
D203,
|
||||
D204,
|
||||
D205,
|
||||
D209,
|
||||
D210,
|
||||
D211,
|
||||
D212,
|
||||
D213,
|
||||
D300,
|
||||
D400,
|
||||
D402,
|
||||
D403,
|
||||
D404,
|
||||
D405,
|
||||
D406,
|
||||
D407,
|
||||
D408,
|
||||
D409,
|
||||
D410,
|
||||
D411,
|
||||
D412,
|
||||
D413,
|
||||
D414,
|
||||
D415,
|
||||
D418,
|
||||
D419,
|
||||
// Meta
|
||||
M001,
|
||||
}
|
||||
@@ -210,7 +257,11 @@ pub enum CheckKind {
|
||||
BuiltinVariableShadowing(String),
|
||||
BuiltinArgumentShadowing(String),
|
||||
BuiltinAttributeShadowing(String),
|
||||
// flakes8-comprehensions
|
||||
// flake8-bugbear
|
||||
DoNotAssertFalse,
|
||||
DuplicateHandlerException(Vec<String>),
|
||||
DuplicateTryBlockException(String),
|
||||
// flake8-comprehensions
|
||||
UnnecessaryGeneratorList,
|
||||
UnnecessaryGeneratorSet,
|
||||
UnnecessaryGeneratorDict,
|
||||
@@ -219,6 +270,9 @@ pub enum CheckKind {
|
||||
UnnecessaryLiteralSet(String),
|
||||
UnnecessaryLiteralDict(String),
|
||||
UnnecessaryCollectionCall(String),
|
||||
UnnecessaryLiteralWithinTupleCall(String),
|
||||
UnnecessaryLiteralWithinListCall(String),
|
||||
UnnecessaryDoubleCastOrProcess(String, String),
|
||||
UnnecessarySubscriptReversal(String),
|
||||
// flake8-print
|
||||
PrintFound,
|
||||
@@ -232,8 +286,46 @@ pub enum CheckKind {
|
||||
UsePEP585Annotation(String),
|
||||
UsePEP604Annotation,
|
||||
SuperCallWithParameters,
|
||||
// pydocstyle
|
||||
BlankLineAfterLastSection(String),
|
||||
BlankLineAfterSection(String),
|
||||
BlankLineBeforeSection(String),
|
||||
CapitalizeSectionName(String),
|
||||
DashedUnderlineAfterSection(String),
|
||||
EndsInPeriod,
|
||||
EndsInPunctuation,
|
||||
FirstLineCapitalized,
|
||||
FitsOnOneLine,
|
||||
MagicMethod,
|
||||
MultiLineSummaryFirstLine,
|
||||
MultiLineSummarySecondLine,
|
||||
NewLineAfterLastParagraph,
|
||||
NewLineAfterSectionName(String),
|
||||
NoBlankLineAfterFunction(usize),
|
||||
NoBlankLineAfterSummary,
|
||||
NoBlankLineBeforeClass(usize),
|
||||
NoBlankLineBeforeFunction(usize),
|
||||
NoBlankLinesBetweenHeaderAndContent(String),
|
||||
NoSignature,
|
||||
NoSurroundingWhitespace,
|
||||
NoThisPrefix,
|
||||
NonEmpty,
|
||||
NonEmptySection(String),
|
||||
OneBlankLineAfterClass(usize),
|
||||
OneBlankLineBeforeClass(usize),
|
||||
PublicClass,
|
||||
PublicFunction,
|
||||
PublicInit,
|
||||
PublicMethod,
|
||||
PublicModule,
|
||||
PublicNestedClass,
|
||||
PublicPackage,
|
||||
SectionUnderlineAfterName(String),
|
||||
SectionUnderlineMatchesSectionLength(String),
|
||||
SkipDocstring,
|
||||
UsesTripleQuotes,
|
||||
// Meta
|
||||
UnusedNOQA(Option<String>),
|
||||
UnusedNOQA(Option<Vec<String>>),
|
||||
}
|
||||
|
||||
impl CheckCode {
|
||||
@@ -301,6 +393,10 @@ impl CheckCode {
|
||||
CheckCode::A001 => CheckKind::BuiltinVariableShadowing("...".to_string()),
|
||||
CheckCode::A002 => CheckKind::BuiltinArgumentShadowing("...".to_string()),
|
||||
CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()),
|
||||
// flake8-bugbear
|
||||
CheckCode::B011 => CheckKind::DoNotAssertFalse,
|
||||
CheckCode::B014 => CheckKind::DuplicateHandlerException(vec!["ValueError".to_string()]),
|
||||
CheckCode::B025 => CheckKind::DuplicateTryBlockException("Exception".to_string()),
|
||||
// flake8-comprehensions
|
||||
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
|
||||
CheckCode::C401 => CheckKind::UnnecessaryGeneratorSet,
|
||||
@@ -312,6 +408,16 @@ impl CheckCode {
|
||||
CheckCode::C408 => {
|
||||
CheckKind::UnnecessaryCollectionCall("<dict/list/tuple>".to_string())
|
||||
}
|
||||
CheckCode::C409 => {
|
||||
CheckKind::UnnecessaryLiteralWithinTupleCall("<list/tuple>".to_string())
|
||||
}
|
||||
CheckCode::C410 => {
|
||||
CheckKind::UnnecessaryLiteralWithinListCall("<list/tuple>".to_string())
|
||||
}
|
||||
CheckCode::C414 => CheckKind::UnnecessaryDoubleCastOrProcess(
|
||||
"<list/reversed/set/sorted/tuple>".to_string(),
|
||||
"<list/set/sorted/tuple>".to_string(),
|
||||
),
|
||||
CheckCode::C415 => {
|
||||
CheckKind::UnnecessarySubscriptReversal("<reversed/set/sorted>".to_string())
|
||||
}
|
||||
@@ -330,6 +436,48 @@ impl CheckCode {
|
||||
CheckCode::U006 => CheckKind::UsePEP585Annotation("List".to_string()),
|
||||
CheckCode::U007 => CheckKind::UsePEP604Annotation,
|
||||
CheckCode::U008 => CheckKind::SuperCallWithParameters,
|
||||
// pydocstyle
|
||||
CheckCode::D100 => CheckKind::PublicModule,
|
||||
CheckCode::D101 => CheckKind::PublicClass,
|
||||
CheckCode::D102 => CheckKind::PublicMethod,
|
||||
CheckCode::D103 => CheckKind::PublicFunction,
|
||||
CheckCode::D104 => CheckKind::PublicPackage,
|
||||
CheckCode::D105 => CheckKind::MagicMethod,
|
||||
CheckCode::D106 => CheckKind::PublicNestedClass,
|
||||
CheckCode::D107 => CheckKind::PublicInit,
|
||||
CheckCode::D200 => CheckKind::FitsOnOneLine,
|
||||
CheckCode::D201 => CheckKind::NoBlankLineBeforeFunction(1),
|
||||
CheckCode::D202 => CheckKind::NoBlankLineAfterFunction(1),
|
||||
CheckCode::D203 => CheckKind::OneBlankLineBeforeClass(0),
|
||||
CheckCode::D204 => CheckKind::OneBlankLineAfterClass(0),
|
||||
CheckCode::D205 => CheckKind::NoBlankLineAfterSummary,
|
||||
CheckCode::D209 => CheckKind::NewLineAfterLastParagraph,
|
||||
CheckCode::D210 => CheckKind::NoSurroundingWhitespace,
|
||||
CheckCode::D211 => CheckKind::NoBlankLineBeforeClass(1),
|
||||
CheckCode::D212 => CheckKind::MultiLineSummaryFirstLine,
|
||||
CheckCode::D213 => CheckKind::MultiLineSummarySecondLine,
|
||||
CheckCode::D300 => CheckKind::UsesTripleQuotes,
|
||||
CheckCode::D400 => CheckKind::EndsInPeriod,
|
||||
CheckCode::D402 => CheckKind::NoSignature,
|
||||
CheckCode::D403 => CheckKind::FirstLineCapitalized,
|
||||
CheckCode::D404 => CheckKind::NoThisPrefix,
|
||||
CheckCode::D405 => CheckKind::CapitalizeSectionName("returns".to_string()),
|
||||
CheckCode::D406 => CheckKind::NewLineAfterSectionName("Returns".to_string()),
|
||||
CheckCode::D407 => CheckKind::DashedUnderlineAfterSection("Returns".to_string()),
|
||||
CheckCode::D408 => CheckKind::SectionUnderlineAfterName("Returns".to_string()),
|
||||
CheckCode::D409 => {
|
||||
CheckKind::SectionUnderlineMatchesSectionLength("Returns".to_string())
|
||||
}
|
||||
CheckCode::D410 => CheckKind::BlankLineAfterSection("Returns".to_string()),
|
||||
CheckCode::D411 => CheckKind::BlankLineBeforeSection("Returns".to_string()),
|
||||
CheckCode::D412 => {
|
||||
CheckKind::NoBlankLinesBetweenHeaderAndContent("Returns".to_string())
|
||||
}
|
||||
CheckCode::D413 => CheckKind::BlankLineAfterLastSection("Returns".to_string()),
|
||||
CheckCode::D414 => CheckKind::NonEmptySection("Returns".to_string()),
|
||||
CheckCode::D415 => CheckKind::EndsInPunctuation,
|
||||
CheckCode::D418 => CheckKind::SkipDocstring,
|
||||
CheckCode::D419 => CheckKind::NonEmpty,
|
||||
// Meta
|
||||
CheckCode::M001 => CheckKind::UnusedNOQA(None),
|
||||
}
|
||||
@@ -389,6 +537,10 @@ impl CheckKind {
|
||||
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
|
||||
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
|
||||
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
|
||||
// flake8-bugbear
|
||||
CheckKind::DoNotAssertFalse => &CheckCode::B011,
|
||||
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
|
||||
CheckKind::DuplicateTryBlockException(_) => &CheckCode::B025,
|
||||
// flake8-comprehensions
|
||||
CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
|
||||
CheckKind::UnnecessaryGeneratorSet => &CheckCode::C401,
|
||||
@@ -398,6 +550,9 @@ impl CheckKind {
|
||||
CheckKind::UnnecessaryLiteralSet(_) => &CheckCode::C405,
|
||||
CheckKind::UnnecessaryLiteralDict(_) => &CheckCode::C406,
|
||||
CheckKind::UnnecessaryCollectionCall(_) => &CheckCode::C408,
|
||||
CheckKind::UnnecessaryLiteralWithinTupleCall(..) => &CheckCode::C409,
|
||||
CheckKind::UnnecessaryLiteralWithinListCall(..) => &CheckCode::C410,
|
||||
CheckKind::UnnecessaryDoubleCastOrProcess(..) => &CheckCode::C414,
|
||||
CheckKind::UnnecessarySubscriptReversal(_) => &CheckCode::C415,
|
||||
// flake8-print
|
||||
CheckKind::PrintFound => &CheckCode::T201,
|
||||
@@ -411,6 +566,44 @@ impl CheckKind {
|
||||
CheckKind::UsePEP604Annotation => &CheckCode::U007,
|
||||
CheckKind::UselessObjectInheritance(_) => &CheckCode::U004,
|
||||
CheckKind::SuperCallWithParameters => &CheckCode::U008,
|
||||
// pydocstyle
|
||||
CheckKind::BlankLineAfterLastSection(_) => &CheckCode::D413,
|
||||
CheckKind::BlankLineAfterSection(_) => &CheckCode::D410,
|
||||
CheckKind::BlankLineBeforeSection(_) => &CheckCode::D411,
|
||||
CheckKind::CapitalizeSectionName(_) => &CheckCode::D405,
|
||||
CheckKind::DashedUnderlineAfterSection(_) => &CheckCode::D407,
|
||||
CheckKind::EndsInPeriod => &CheckCode::D400,
|
||||
CheckKind::EndsInPunctuation => &CheckCode::D415,
|
||||
CheckKind::FirstLineCapitalized => &CheckCode::D403,
|
||||
CheckKind::FitsOnOneLine => &CheckCode::D200,
|
||||
CheckKind::MagicMethod => &CheckCode::D105,
|
||||
CheckKind::MultiLineSummaryFirstLine => &CheckCode::D212,
|
||||
CheckKind::MultiLineSummarySecondLine => &CheckCode::D213,
|
||||
CheckKind::NewLineAfterLastParagraph => &CheckCode::D209,
|
||||
CheckKind::NewLineAfterSectionName(_) => &CheckCode::D406,
|
||||
CheckKind::NoBlankLineAfterFunction(_) => &CheckCode::D202,
|
||||
CheckKind::NoBlankLineAfterSummary => &CheckCode::D205,
|
||||
CheckKind::NoBlankLineBeforeClass(_) => &CheckCode::D211,
|
||||
CheckKind::NoBlankLineBeforeFunction(_) => &CheckCode::D201,
|
||||
CheckKind::NoBlankLinesBetweenHeaderAndContent(_) => &CheckCode::D412,
|
||||
CheckKind::NoSignature => &CheckCode::D402,
|
||||
CheckKind::NoSurroundingWhitespace => &CheckCode::D210,
|
||||
CheckKind::NoThisPrefix => &CheckCode::D404,
|
||||
CheckKind::NonEmpty => &CheckCode::D419,
|
||||
CheckKind::NonEmptySection(_) => &CheckCode::D414,
|
||||
CheckKind::OneBlankLineAfterClass(_) => &CheckCode::D204,
|
||||
CheckKind::OneBlankLineBeforeClass(_) => &CheckCode::D203,
|
||||
CheckKind::PublicClass => &CheckCode::D101,
|
||||
CheckKind::PublicFunction => &CheckCode::D103,
|
||||
CheckKind::PublicInit => &CheckCode::D107,
|
||||
CheckKind::PublicMethod => &CheckCode::D102,
|
||||
CheckKind::PublicModule => &CheckCode::D100,
|
||||
CheckKind::PublicNestedClass => &CheckCode::D106,
|
||||
CheckKind::PublicPackage => &CheckCode::D104,
|
||||
CheckKind::SectionUnderlineAfterName(_) => &CheckCode::D408,
|
||||
CheckKind::SectionUnderlineMatchesSectionLength(_) => &CheckCode::D409,
|
||||
CheckKind::SkipDocstring => &CheckCode::D418,
|
||||
CheckKind::UsesTripleQuotes => &CheckCode::D300,
|
||||
// Meta
|
||||
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
|
||||
}
|
||||
@@ -559,6 +752,23 @@ impl CheckKind {
|
||||
CheckKind::BuiltinAttributeShadowing(name) => {
|
||||
format!("Class attribute `{name}` is shadowing a python builtin")
|
||||
}
|
||||
// flake8-bugbear
|
||||
CheckKind::DoNotAssertFalse => {
|
||||
"Do not `assert False` (`python -O` removes these calls), raise `AssertionError()`"
|
||||
.to_string()
|
||||
}
|
||||
CheckKind::DuplicateHandlerException(names) => {
|
||||
if names.len() == 1 {
|
||||
let name = &names[0];
|
||||
format!("Exception handler with duplicate exception: `{name}`")
|
||||
} else {
|
||||
let names = names.iter().map(|name| format!("`{name}`")).join(", ");
|
||||
format!("Exception handler with duplicate exceptions: {names}")
|
||||
}
|
||||
}
|
||||
CheckKind::DuplicateTryBlockException(name) => {
|
||||
format!("try-except block with duplicate exception `{name}`")
|
||||
}
|
||||
// flake8-comprehensions
|
||||
CheckKind::UnnecessaryGeneratorList => {
|
||||
"Unnecessary generator - rewrite as a list comprehension".to_string()
|
||||
@@ -584,6 +794,31 @@ impl CheckKind {
|
||||
CheckKind::UnnecessaryCollectionCall(obj_type) => {
|
||||
format!("Unnecessary {obj_type} call - rewrite as a literal")
|
||||
}
|
||||
CheckKind::UnnecessaryLiteralWithinTupleCall(literal) => {
|
||||
if literal == "list" {
|
||||
format!(
|
||||
"Unnecessary {literal} literal passed to tuple() - rewrite as a tuple literal"
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"Unnecessary {literal} literal passed to tuple() - remove the outer call to tuple()"
|
||||
)
|
||||
}
|
||||
}
|
||||
CheckKind::UnnecessaryLiteralWithinListCall(literal) => {
|
||||
if literal == "list" {
|
||||
format!(
|
||||
"Unnecessary {literal} literal passed to list() - remove the outer call to list()"
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"Unnecessary {literal} literal passed to list() - rewrite as a list literal"
|
||||
)
|
||||
}
|
||||
}
|
||||
CheckKind::UnnecessaryDoubleCastOrProcess(inner, outer) => {
|
||||
format!("Unnecessary {inner} call within {outer}().")
|
||||
}
|
||||
CheckKind::UnnecessarySubscriptReversal(func) => {
|
||||
format!("Unnecessary subscript reversal of iterable within {func}()")
|
||||
}
|
||||
@@ -615,10 +850,111 @@ impl CheckKind {
|
||||
CheckKind::SuperCallWithParameters => {
|
||||
"Use `super()` instead of `super(__class__, self)`".to_string()
|
||||
}
|
||||
// pydocstyle
|
||||
CheckKind::FitsOnOneLine => "One-line docstring should fit on one line".to_string(),
|
||||
CheckKind::NoBlankLineAfterSummary => {
|
||||
"1 blank line required between summary line and description".to_string()
|
||||
}
|
||||
CheckKind::NewLineAfterLastParagraph => {
|
||||
"Multi-line docstring closing quotes should be on a separate line".to_string()
|
||||
}
|
||||
CheckKind::NoSurroundingWhitespace => {
|
||||
"No whitespaces allowed surrounding docstring text".to_string()
|
||||
}
|
||||
CheckKind::EndsInPeriod => "First line should end with a period".to_string(),
|
||||
CheckKind::NonEmpty => "Docstring is empty".to_string(),
|
||||
CheckKind::EndsInPunctuation => {
|
||||
"First line should end with a period, question mark, or exclamation point"
|
||||
.to_string()
|
||||
}
|
||||
CheckKind::FirstLineCapitalized => {
|
||||
"First word of the first line should be properly capitalized".to_string()
|
||||
}
|
||||
CheckKind::UsesTripleQuotes => r#"Use """triple double quotes""""#.to_string(),
|
||||
CheckKind::MultiLineSummaryFirstLine => {
|
||||
"Multi-line docstring summary should start at the first line".to_string()
|
||||
}
|
||||
CheckKind::MultiLineSummarySecondLine => {
|
||||
"Multi-line docstring summary should start at the second line".to_string()
|
||||
}
|
||||
CheckKind::NoSignature => {
|
||||
"First line should not be the function's 'signature'".to_string()
|
||||
}
|
||||
CheckKind::NoBlankLineBeforeFunction(num_lines) => {
|
||||
format!("No blank lines allowed before function docstring (found {num_lines})")
|
||||
}
|
||||
CheckKind::NoBlankLineAfterFunction(num_lines) => {
|
||||
format!("No blank lines allowed after function docstring (found {num_lines})")
|
||||
}
|
||||
CheckKind::NoBlankLineBeforeClass(_) => {
|
||||
"No blank lines allowed before class docstring".to_string()
|
||||
}
|
||||
CheckKind::OneBlankLineBeforeClass(_) => {
|
||||
"1 blank line required before class docstring".to_string()
|
||||
}
|
||||
CheckKind::OneBlankLineAfterClass(_) => {
|
||||
"1 blank line required after class docstring".to_string()
|
||||
}
|
||||
CheckKind::PublicModule => "Missing docstring in public module".to_string(),
|
||||
CheckKind::PublicClass => "Missing docstring in public class".to_string(),
|
||||
CheckKind::PublicMethod => "Missing docstring in public method".to_string(),
|
||||
CheckKind::PublicFunction => "Missing docstring in public function".to_string(),
|
||||
CheckKind::PublicPackage => "Missing docstring in public package".to_string(),
|
||||
CheckKind::MagicMethod => "Missing docstring in magic method".to_string(),
|
||||
CheckKind::PublicNestedClass => "Missing docstring in public nested class".to_string(),
|
||||
CheckKind::PublicInit => "Missing docstring in __init__".to_string(),
|
||||
CheckKind::NoThisPrefix => {
|
||||
"First word of the docstring should not be `This`".to_string()
|
||||
}
|
||||
CheckKind::SkipDocstring => {
|
||||
"Function decorated with @overload shouldn't contain a docstring".to_string()
|
||||
}
|
||||
CheckKind::CapitalizeSectionName(name) => {
|
||||
format!("Section name should be properly capitalized (\"{name}\")")
|
||||
}
|
||||
CheckKind::BlankLineAfterLastSection(name) => {
|
||||
format!("Missing blank line after last section (\"{name}\")")
|
||||
}
|
||||
CheckKind::BlankLineAfterSection(name) => {
|
||||
format!("Missing blank line after section (\"{name}\")")
|
||||
}
|
||||
CheckKind::BlankLineBeforeSection(name) => {
|
||||
format!("Missing blank line before section (\"{name}\")")
|
||||
}
|
||||
CheckKind::NewLineAfterSectionName(name) => {
|
||||
format!("Section name should end with a newline (\"{name}\")")
|
||||
}
|
||||
CheckKind::DashedUnderlineAfterSection(name) => {
|
||||
format!("Missing dashed underline after section (\"{name}\")")
|
||||
}
|
||||
CheckKind::SectionUnderlineAfterName(name) => {
|
||||
format!("Section underline should be in the line following the section's name (\"{name}\")")
|
||||
}
|
||||
CheckKind::SectionUnderlineMatchesSectionLength(name) => {
|
||||
format!("Section underline should match the length of its name (\"{name}\")")
|
||||
}
|
||||
CheckKind::NoBlankLinesBetweenHeaderAndContent(name) => {
|
||||
format!(
|
||||
"No blank lines allowed between a section header and its content (\"{name}\")"
|
||||
)
|
||||
}
|
||||
CheckKind::NonEmptySection(name) => format!("Section has no content (\"{name}\")"),
|
||||
// Meta
|
||||
CheckKind::UnusedNOQA(code) => match code {
|
||||
CheckKind::UnusedNOQA(codes) => match codes {
|
||||
None => "Unused `noqa` directive".to_string(),
|
||||
Some(code) => format!("Unused `noqa` directive for: {code}"),
|
||||
Some(codes) => {
|
||||
let codes = codes
|
||||
.iter()
|
||||
.map(|code| {
|
||||
if CheckCode::from_str(code).is_ok() {
|
||||
code.to_string()
|
||||
} else {
|
||||
format!("{code} (not implemented)")
|
||||
}
|
||||
})
|
||||
.join(", ");
|
||||
format!("Unused `noqa` directive for: {codes}")
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -628,6 +964,8 @@ impl CheckKind {
|
||||
matches!(
|
||||
self,
|
||||
CheckKind::DeprecatedUnittestAlias(_, _)
|
||||
| CheckKind::DoNotAssertFalse
|
||||
| CheckKind::DuplicateHandlerException(_)
|
||||
| CheckKind::PPrintFound
|
||||
| CheckKind::PrintFound
|
||||
| CheckKind::SuperCallWithParameters
|
||||
|
||||
@@ -78,6 +78,9 @@ pub struct Cli {
|
||||
// TODO(charlie): This should be a sub-command.
|
||||
#[arg(long, hide = true)]
|
||||
pub autoformat: bool,
|
||||
/// The name of the file when passing it through stdin.
|
||||
#[arg(long)]
|
||||
pub stdin_filename: Option<String>,
|
||||
}
|
||||
|
||||
pub enum Warnable {
|
||||
|
||||
@@ -115,7 +115,7 @@ impl SourceGenerator {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unparse_stmt<U>(&mut self, ast: &Stmt<U>) -> fmt::Result {
|
||||
pub fn unparse_stmt<U>(&mut self, ast: &Stmt<U>) -> fmt::Result {
|
||||
macro_rules! statement {
|
||||
($body:block) => {{
|
||||
self.newline()?;
|
||||
|
||||
3
src/docstrings.rs
Normal file
3
src/docstrings.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod docstring_checks;
|
||||
pub mod sections;
|
||||
pub mod types;
|
||||
676
src/docstrings/docstring_checks.rs
Normal file
676
src/docstrings/docstring_checks.rs
Normal file
@@ -0,0 +1,676 @@
|
||||
//! Abstractions for tracking and validating docstrings in Python code.
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_ast::{Constant, Expr, ExprKind, Location, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::docstrings::sections::{check_numpy_section, section_contexts};
|
||||
use crate::docstrings::types::{Definition, DefinitionKind, Documentable};
|
||||
use crate::visibility::{is_init, is_magic, is_overload, Modifier, Visibility, VisibleScope};
|
||||
|
||||
/// Extract a docstring from a function or class body.
|
||||
pub fn docstring_from(suite: &[Stmt]) -> Option<&Expr> {
|
||||
if let Some(stmt) = suite.first() {
|
||||
if let StmtKind::Expr { value } = &stmt.node {
|
||||
if matches!(
|
||||
&value.node,
|
||||
ExprKind::Constant {
|
||||
value: Constant::Str(_),
|
||||
..
|
||||
}
|
||||
) {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Extract a `Definition` from the AST node defined by a `Stmt`.
|
||||
pub fn extract<'a>(
|
||||
scope: &VisibleScope,
|
||||
stmt: &'a Stmt,
|
||||
body: &'a [Stmt],
|
||||
kind: &Documentable,
|
||||
) -> Definition<'a> {
|
||||
let expr = docstring_from(body);
|
||||
match kind {
|
||||
Documentable::Function => match scope {
|
||||
VisibleScope {
|
||||
modifier: Modifier::Module,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::Function(stmt),
|
||||
docstring: expr,
|
||||
},
|
||||
VisibleScope {
|
||||
modifier: Modifier::Class,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::Method(stmt),
|
||||
docstring: expr,
|
||||
},
|
||||
VisibleScope {
|
||||
modifier: Modifier::Function,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::NestedFunction(stmt),
|
||||
docstring: expr,
|
||||
},
|
||||
},
|
||||
Documentable::Class => match scope {
|
||||
VisibleScope {
|
||||
modifier: Modifier::Module,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::Class(stmt),
|
||||
docstring: expr,
|
||||
},
|
||||
VisibleScope {
|
||||
modifier: Modifier::Class,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::NestedClass(stmt),
|
||||
docstring: expr,
|
||||
},
|
||||
VisibleScope {
|
||||
modifier: Modifier::Function,
|
||||
..
|
||||
} => Definition {
|
||||
kind: DefinitionKind::NestedClass(stmt),
|
||||
docstring: expr,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the source code range for a docstring.
|
||||
pub fn range_for(docstring: &Expr) -> Range {
|
||||
// RustPython currently omits the first quotation mark in a string, so offset the location.
|
||||
Range {
|
||||
location: Location::new(docstring.location.row(), docstring.location.column() - 1),
|
||||
end_location: docstring.end_location,
|
||||
}
|
||||
}
|
||||
|
||||
/// D100, D101, D102, D103, D104, D105, D106, D107
|
||||
pub fn not_missing(
|
||||
checker: &mut Checker,
|
||||
definition: &Definition,
|
||||
visibility: &Visibility,
|
||||
) -> bool {
|
||||
if matches!(visibility, Visibility::Private) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if definition.docstring.is_some() {
|
||||
return true;
|
||||
}
|
||||
|
||||
match definition.kind {
|
||||
DefinitionKind::Module => {
|
||||
if checker.settings.enabled.contains(&CheckCode::D100) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::PublicModule,
|
||||
Range {
|
||||
location: Location::new(1, 1),
|
||||
end_location: Location::new(1, 1),
|
||||
},
|
||||
));
|
||||
}
|
||||
false
|
||||
}
|
||||
DefinitionKind::Package => {
|
||||
if checker.settings.enabled.contains(&CheckCode::D104) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::PublicPackage,
|
||||
Range {
|
||||
location: Location::new(1, 1),
|
||||
end_location: Location::new(1, 1),
|
||||
},
|
||||
));
|
||||
}
|
||||
false
|
||||
}
|
||||
DefinitionKind::Class(stmt) => {
|
||||
if checker.settings.enabled.contains(&CheckCode::D101) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::PublicClass,
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
false
|
||||
}
|
||||
DefinitionKind::NestedClass(stmt) => {
|
||||
if checker.settings.enabled.contains(&CheckCode::D106) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::PublicNestedClass,
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
false
|
||||
}
|
||||
DefinitionKind::Function(stmt) | DefinitionKind::NestedFunction(stmt) => {
|
||||
if is_overload(stmt) {
|
||||
true
|
||||
} else {
|
||||
if checker.settings.enabled.contains(&CheckCode::D103) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::PublicFunction,
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
DefinitionKind::Method(stmt) => {
|
||||
if is_overload(stmt) {
|
||||
true
|
||||
} else if is_magic(stmt) {
|
||||
if checker.settings.enabled.contains(&CheckCode::D105) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::MagicMethod,
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
true
|
||||
} else if is_init(stmt) {
|
||||
if checker.settings.enabled.contains(&CheckCode::D107) {
|
||||
checker.add_check(Check::new(CheckKind::PublicInit, Range::from_located(stmt)));
|
||||
}
|
||||
true
|
||||
} else {
|
||||
if checker.settings.enabled.contains(&CheckCode::D102) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::PublicMethod,
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// D200
|
||||
pub fn one_liner(checker: &mut Checker, definition: &Definition) {
|
||||
if let Some(docstring) = &definition.docstring {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} = &docstring.node
|
||||
{
|
||||
let mut line_count = 0;
|
||||
let mut non_empty_line_count = 0;
|
||||
for line in string.lines() {
|
||||
line_count += 1;
|
||||
if !line.trim().is_empty() {
|
||||
non_empty_line_count += 1;
|
||||
}
|
||||
if non_empty_line_count > 1 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if non_empty_line_count == 1 && line_count > 1 {
|
||||
checker.add_check(Check::new(CheckKind::FitsOnOneLine, range_for(docstring)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static COMMENT_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\s*#").unwrap());
|
||||
|
||||
static INNER_FUNCTION_OR_CLASS_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r"^\s+(?:(?:class|def|async def)\s|@)").unwrap());
|
||||
|
||||
/// D201, D202
|
||||
pub fn blank_before_after_function(checker: &mut Checker, definition: &Definition) {
|
||||
if let Some(docstring) = definition.docstring {
|
||||
if let DefinitionKind::Function(parent)
|
||||
| DefinitionKind::NestedFunction(parent)
|
||||
| DefinitionKind::Method(parent) = &definition.kind
|
||||
{
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(_),
|
||||
..
|
||||
} = &docstring.node
|
||||
{
|
||||
let (before, _, after) = checker
|
||||
.locator
|
||||
.partition_source_code_at(&Range::from_located(parent), &range_for(docstring));
|
||||
|
||||
if checker.settings.enabled.contains(&CheckCode::D201) {
|
||||
let blank_lines_before = before
|
||||
.lines()
|
||||
.rev()
|
||||
.skip(1)
|
||||
.take_while(|line| line.trim().is_empty())
|
||||
.count();
|
||||
if blank_lines_before != 0 {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::NoBlankLineBeforeFunction(blank_lines_before),
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if checker.settings.enabled.contains(&CheckCode::D202) {
|
||||
let blank_lines_after = after
|
||||
.lines()
|
||||
.skip(1)
|
||||
.take_while(|line| line.trim().is_empty())
|
||||
.count();
|
||||
let all_blank_after = after
|
||||
.lines()
|
||||
.skip(1)
|
||||
.all(|line| line.trim().is_empty() || COMMENT_REGEX.is_match(line));
|
||||
// Report a D202 violation if the docstring is followed by a blank line
|
||||
// and the blank line is not itself followed by an inner function or
|
||||
// class.
|
||||
if !all_blank_after
|
||||
&& blank_lines_after != 0
|
||||
&& !(blank_lines_after == 1
|
||||
&& INNER_FUNCTION_OR_CLASS_REGEX.is_match(after))
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::NoBlankLineAfterFunction(blank_lines_after),
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// D203, D204, D211
|
||||
pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition) {
|
||||
if let Some(docstring) = &definition.docstring {
|
||||
if let DefinitionKind::Class(parent) | DefinitionKind::NestedClass(parent) =
|
||||
&definition.kind
|
||||
{
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(_),
|
||||
..
|
||||
} = &docstring.node
|
||||
{
|
||||
let (before, _, after) = checker
|
||||
.locator
|
||||
.partition_source_code_at(&Range::from_located(parent), &range_for(docstring));
|
||||
|
||||
if checker.settings.enabled.contains(&CheckCode::D203)
|
||||
|| checker.settings.enabled.contains(&CheckCode::D211)
|
||||
{
|
||||
let blank_lines_before = before
|
||||
.lines()
|
||||
.rev()
|
||||
.skip(1)
|
||||
.take_while(|line| line.trim().is_empty())
|
||||
.count();
|
||||
if blank_lines_before != 0
|
||||
&& checker.settings.enabled.contains(&CheckCode::D211)
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::NoBlankLineBeforeClass(blank_lines_before),
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
if blank_lines_before != 1
|
||||
&& checker.settings.enabled.contains(&CheckCode::D203)
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::OneBlankLineBeforeClass(blank_lines_before),
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if checker.settings.enabled.contains(&CheckCode::D204) {
|
||||
let blank_lines_after = after
|
||||
.lines()
|
||||
.skip(1)
|
||||
.take_while(|line| line.trim().is_empty())
|
||||
.count();
|
||||
let all_blank_after = after
|
||||
.lines()
|
||||
.skip(1)
|
||||
.all(|line| line.trim().is_empty() || COMMENT_REGEX.is_match(line));
|
||||
if !all_blank_after && blank_lines_after != 1 {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::OneBlankLineAfterClass(blank_lines_after),
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// D205
|
||||
pub fn blank_after_summary(checker: &mut Checker, definition: &Definition) {
|
||||
if let Some(docstring) = definition.docstring {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} = &docstring.node
|
||||
{
|
||||
let mut lines_count = 1;
|
||||
let mut blanks_count = 0;
|
||||
for line in string.trim().lines().skip(1) {
|
||||
lines_count += 1;
|
||||
if line.trim().is_empty() {
|
||||
blanks_count += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if lines_count > 1 && blanks_count != 1 {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::NoBlankLineAfterSummary,
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// D209
|
||||
pub fn newline_after_last_paragraph(checker: &mut Checker, definition: &Definition) {
|
||||
if let Some(docstring) = definition.docstring {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} = &docstring.node
|
||||
{
|
||||
let mut line_count = 0;
|
||||
for line in string.lines() {
|
||||
if !line.trim().is_empty() {
|
||||
line_count += 1;
|
||||
}
|
||||
if line_count > 1 {
|
||||
let content = checker
|
||||
.locator
|
||||
.slice_source_code_range(&range_for(docstring));
|
||||
if let Some(line) = content.lines().last() {
|
||||
let line = line.trim();
|
||||
if line != "\"\"\"" && line != "'''" {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::NewLineAfterLastParagraph,
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// D210
|
||||
pub fn no_surrounding_whitespace(checker: &mut Checker, definition: &Definition) {
|
||||
if let Some(docstring) = definition.docstring {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} = &docstring.node
|
||||
{
|
||||
let mut lines = string.lines();
|
||||
if let Some(line) = lines.next() {
|
||||
if line.trim().is_empty() {
|
||||
return;
|
||||
}
|
||||
if line.starts_with(' ') || (matches!(lines.next(), None) && line.ends_with(' ')) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::NoSurroundingWhitespace,
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// D212, D213
|
||||
pub fn multi_line_summary_start(checker: &mut Checker, definition: &Definition) {
|
||||
if let Some(docstring) = definition.docstring {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} = &docstring.node
|
||||
{
|
||||
if string.lines().nth(1).is_some() {
|
||||
let content = checker
|
||||
.locator
|
||||
.slice_source_code_range(&range_for(docstring));
|
||||
if let Some(first_line) = content.lines().next() {
|
||||
let first_line = first_line.trim();
|
||||
if first_line == "\"\"\"" || first_line == "'''" {
|
||||
if checker.settings.enabled.contains(&CheckCode::D212) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::MultiLineSummaryFirstLine,
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
} else if checker.settings.enabled.contains(&CheckCode::D213) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::MultiLineSummarySecondLine,
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// D300
|
||||
pub fn triple_quotes(checker: &mut Checker, definition: &Definition) {
|
||||
if let Some(docstring) = definition.docstring {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} = &docstring.node
|
||||
{
|
||||
let content = checker
|
||||
.locator
|
||||
.slice_source_code_range(&range_for(docstring));
|
||||
if string.contains("\"\"\"") {
|
||||
if !content.starts_with("'''") {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::UsesTripleQuotes,
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
} else if !content.starts_with("\"\"\"") {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::UsesTripleQuotes,
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// D400
|
||||
pub fn ends_with_period(checker: &mut Checker, definition: &Definition) {
|
||||
if let Some(docstring) = definition.docstring {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} = &docstring.node
|
||||
{
|
||||
if let Some(string) = string.lines().next() {
|
||||
if !string.ends_with('.') {
|
||||
checker.add_check(Check::new(CheckKind::EndsInPeriod, range_for(docstring)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// D402
|
||||
pub fn no_signature(checker: &mut Checker, definition: &Definition) {
|
||||
if let Some(docstring) = definition.docstring {
|
||||
if let DefinitionKind::Function(parent)
|
||||
| DefinitionKind::NestedFunction(parent)
|
||||
| DefinitionKind::Method(parent) = definition.kind
|
||||
{
|
||||
if let StmtKind::FunctionDef { name, .. } = &parent.node {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} = &docstring.node
|
||||
{
|
||||
if let Some(first_line) = string.lines().next() {
|
||||
if first_line.contains(&format!("{name}(")) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::NoSignature,
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// D403
|
||||
pub fn capitalized(checker: &mut Checker, definition: &Definition) {
|
||||
if !matches!(definition.kind, DefinitionKind::Function(_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(docstring) = definition.docstring {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} = &docstring.node
|
||||
{
|
||||
if let Some(first_word) = string.split(' ').next() {
|
||||
if first_word == first_word.to_uppercase() {
|
||||
return;
|
||||
}
|
||||
for char in first_word.chars() {
|
||||
if !char.is_ascii_alphabetic() && char != '\'' {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if let Some(first_char) = first_word.chars().next() {
|
||||
if !first_char.is_uppercase() {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::FirstLineCapitalized,
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// D404
|
||||
pub fn starts_with_this(checker: &mut Checker, definition: &Definition) {
|
||||
if let Some(docstring) = definition.docstring {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} = &docstring.node
|
||||
{
|
||||
let trimmed = string.trim();
|
||||
if trimmed.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(first_word) = string.split(' ').next() {
|
||||
if first_word
|
||||
.replace(|c: char| !c.is_alphanumeric(), "")
|
||||
.to_lowercase()
|
||||
== "this"
|
||||
{
|
||||
checker.add_check(Check::new(CheckKind::NoThisPrefix, range_for(docstring)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// D415
|
||||
pub fn ends_with_punctuation(checker: &mut Checker, definition: &Definition) {
|
||||
if let Some(docstring) = definition.docstring {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} = &docstring.node
|
||||
{
|
||||
if let Some(string) = string.lines().next() {
|
||||
if !(string.ends_with('.') || string.ends_with('!') || string.ends_with('?')) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::EndsInPunctuation,
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// D418
|
||||
pub fn if_needed(checker: &mut Checker, definition: &Definition) {
|
||||
if definition.docstring.is_some() {
|
||||
if let DefinitionKind::Function(stmt)
|
||||
| DefinitionKind::NestedFunction(stmt)
|
||||
| DefinitionKind::Method(stmt) = definition.kind
|
||||
{
|
||||
if is_overload(stmt) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::SkipDocstring,
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// D419
|
||||
pub fn not_empty(checker: &mut Checker, definition: &Definition) -> bool {
|
||||
if let Some(docstring) = definition.docstring {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} = &docstring.node
|
||||
{
|
||||
if string.trim().is_empty() {
|
||||
if checker.settings.enabled.contains(&CheckCode::D419) {
|
||||
checker.add_check(Check::new(CheckKind::NonEmpty, range_for(docstring)));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn check_sections(checker: &mut Checker, definition: &Definition) {
|
||||
if let Some(docstring) = definition.docstring {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Str(string),
|
||||
..
|
||||
} = &docstring.node
|
||||
{
|
||||
let lines: Vec<&str> = string.lines().collect();
|
||||
if lines.len() < 2 {
|
||||
return;
|
||||
}
|
||||
for context in §ion_contexts(&lines) {
|
||||
check_numpy_section(checker, definition, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
384
src/docstrings/sections.rs
Normal file
384
src/docstrings/sections.rs
Normal file
@@ -0,0 +1,384 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use titlecase::titlecase;
|
||||
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
use crate::docstrings::docstring_checks::range_for;
|
||||
use crate::docstrings::types::Definition;
|
||||
|
||||
static NUMPY_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
BTreeSet::from([
|
||||
"Short Summary",
|
||||
"Extended Summary",
|
||||
"Parameters",
|
||||
"Returns",
|
||||
"Yields",
|
||||
"Other Parameters",
|
||||
"Raises",
|
||||
"See Also",
|
||||
"Notes",
|
||||
"References",
|
||||
"Examples",
|
||||
"Attributes",
|
||||
"Methods",
|
||||
])
|
||||
});
|
||||
|
||||
static NUMPY_SECTION_NAMES_LOWERCASE: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
BTreeSet::from([
|
||||
"short summary",
|
||||
"extended summary",
|
||||
"parameters",
|
||||
"returns",
|
||||
"yields",
|
||||
"other parameters",
|
||||
"raises",
|
||||
"see also",
|
||||
"notes",
|
||||
"references",
|
||||
"examples",
|
||||
"attributes",
|
||||
"methods",
|
||||
])
|
||||
});
|
||||
|
||||
// TODO(charlie): Include Google section names.
|
||||
// static GOOGLE_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
// BTreeSet::from([
|
||||
// "Args",
|
||||
// "Arguments",
|
||||
// "Attention",
|
||||
// "Attributes",
|
||||
// "Caution",
|
||||
// "Danger",
|
||||
// "Error",
|
||||
// "Example",
|
||||
// "Examples",
|
||||
// "Hint",
|
||||
// "Important",
|
||||
// "Keyword Args",
|
||||
// "Keyword Arguments",
|
||||
// "Methods",
|
||||
// "Note",
|
||||
// "Notes",
|
||||
// "Return",
|
||||
// "Returns",
|
||||
// "Raises",
|
||||
// "References",
|
||||
// "See Also",
|
||||
// "Tip",
|
||||
// "Todo",
|
||||
// "Warning",
|
||||
// "Warnings",
|
||||
// "Warns",
|
||||
// "Yield",
|
||||
// "Yields",
|
||||
// ])
|
||||
// });
|
||||
|
||||
fn get_leading_words(line: &str) -> String {
|
||||
line.trim()
|
||||
.chars()
|
||||
.take_while(|char| char.is_alphanumeric() || char.is_whitespace())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn suspected_as_section(line: &str) -> bool {
|
||||
NUMPY_SECTION_NAMES_LOWERCASE.contains(&get_leading_words(line).to_lowercase().as_str())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SectionContext<'a> {
|
||||
section_name: String,
|
||||
previous_line: &'a str,
|
||||
line: &'a str,
|
||||
following_lines: &'a [&'a str],
|
||||
original_index: usize,
|
||||
is_last_section: bool,
|
||||
}
|
||||
|
||||
/// Check if the suspected context is really a section header.
|
||||
fn is_docstring_section(context: &SectionContext) -> bool {
|
||||
let section_name_suffix = context
|
||||
.line
|
||||
.trim()
|
||||
.strip_prefix(&context.section_name)
|
||||
.unwrap()
|
||||
.trim();
|
||||
let this_looks_like_a_section_name =
|
||||
section_name_suffix == ":" || section_name_suffix.is_empty();
|
||||
if !this_looks_like_a_section_name {
|
||||
return false;
|
||||
}
|
||||
|
||||
let prev_line = context.previous_line.trim();
|
||||
let prev_line_ends_with_punctuation = [',', ';', '.', '-', '\\', '/', ']', '}', ')']
|
||||
.into_iter()
|
||||
.any(|char| prev_line.ends_with(char));
|
||||
let prev_line_looks_like_end_of_paragraph =
|
||||
prev_line_ends_with_punctuation || prev_line.is_empty();
|
||||
if !prev_line_looks_like_end_of_paragraph {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Extract all `SectionContext` values from a docstring.
|
||||
pub fn section_contexts<'a>(lines: &'a [&'a str]) -> Vec<SectionContext<'a>> {
|
||||
let suspected_section_indices: Vec<usize> = lines
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(lineno, line)| {
|
||||
if lineno > 0 && suspected_as_section(line) {
|
||||
Some(lineno)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut contexts = vec![];
|
||||
for lineno in suspected_section_indices {
|
||||
let context = SectionContext {
|
||||
section_name: get_leading_words(lines[lineno]),
|
||||
previous_line: lines[lineno - 1],
|
||||
line: lines[lineno],
|
||||
following_lines: &lines[lineno + 1..],
|
||||
original_index: lineno,
|
||||
is_last_section: false,
|
||||
};
|
||||
if is_docstring_section(&context) {
|
||||
contexts.push(context);
|
||||
}
|
||||
}
|
||||
|
||||
let mut truncated_contexts = vec![];
|
||||
let mut end: Option<usize> = None;
|
||||
for context in contexts.into_iter().rev() {
|
||||
let next_end = context.original_index;
|
||||
truncated_contexts.push(SectionContext {
|
||||
section_name: context.section_name,
|
||||
previous_line: context.previous_line,
|
||||
line: context.line,
|
||||
following_lines: if let Some(end) = end {
|
||||
&lines[context.original_index + 1..end]
|
||||
} else {
|
||||
context.following_lines
|
||||
},
|
||||
original_index: context.original_index,
|
||||
is_last_section: end.is_none(),
|
||||
});
|
||||
end = Some(next_end);
|
||||
}
|
||||
truncated_contexts.reverse();
|
||||
truncated_contexts
|
||||
}
|
||||
|
||||
fn check_blanks_and_section_underline(
|
||||
checker: &mut Checker,
|
||||
definition: &Definition,
|
||||
context: &SectionContext,
|
||||
) {
|
||||
let docstring = definition
|
||||
.docstring
|
||||
.expect("Sections are only available for docstrings.");
|
||||
|
||||
let mut blank_lines_after_header = 0;
|
||||
for line in context.following_lines {
|
||||
if !line.trim().is_empty() {
|
||||
break;
|
||||
}
|
||||
blank_lines_after_header += 1;
|
||||
}
|
||||
|
||||
// Nothing but blank lines after the section header.
|
||||
if blank_lines_after_header == context.following_lines.len() {
|
||||
// D407
|
||||
if checker.settings.enabled.contains(&CheckCode::D407) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()),
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
// D414
|
||||
if checker.settings.enabled.contains(&CheckCode::D414) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::NonEmptySection(context.section_name.to_string()),
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let non_empty_line = context.following_lines[blank_lines_after_header];
|
||||
let dash_line_found = non_empty_line
|
||||
.chars()
|
||||
.all(|char| char.is_whitespace() || char == '-');
|
||||
|
||||
if !dash_line_found {
|
||||
// D407
|
||||
if checker.settings.enabled.contains(&CheckCode::D407) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()),
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
if blank_lines_after_header > 0 {
|
||||
// D212
|
||||
if checker.settings.enabled.contains(&CheckCode::D212) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::NoBlankLinesBetweenHeaderAndContent(
|
||||
context.section_name.to_string(),
|
||||
),
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if blank_lines_after_header > 0 {
|
||||
// D408
|
||||
if checker.settings.enabled.contains(&CheckCode::D408) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::SectionUnderlineAfterName(context.section_name.to_string()),
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if non_empty_line
|
||||
.trim()
|
||||
.chars()
|
||||
.filter(|char| *char == '-')
|
||||
.count()
|
||||
!= context.section_name.len()
|
||||
{
|
||||
// D409
|
||||
if checker.settings.enabled.contains(&CheckCode::D409) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::SectionUnderlineMatchesSectionLength(
|
||||
context.section_name.to_string(),
|
||||
),
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(charlie): Implement D215, which requires indentation and leading space tracking.
|
||||
let line_after_dashes_index = blank_lines_after_header + 1;
|
||||
|
||||
if line_after_dashes_index < context.following_lines.len() {
|
||||
let line_after_dashes = context.following_lines[line_after_dashes_index];
|
||||
|
||||
if line_after_dashes.trim().is_empty() {
|
||||
let rest_of_lines = &context.following_lines[line_after_dashes_index..];
|
||||
if rest_of_lines.iter().all(|line| line.trim().is_empty()) {
|
||||
// D414
|
||||
if checker.settings.enabled.contains(&CheckCode::D414) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::NonEmptySection(context.section_name.to_string()),
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
// 412
|
||||
if checker.settings.enabled.contains(&CheckCode::D412) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::NoBlankLinesBetweenHeaderAndContent(
|
||||
context.section_name.to_string(),
|
||||
),
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// D414
|
||||
if checker.settings.enabled.contains(&CheckCode::D414) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::NonEmptySection(context.section_name.to_string()),
|
||||
range_for(docstring),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_common_section(checker: &mut Checker, definition: &Definition, context: &SectionContext) {
|
||||
// TODO(charlie): Implement D214, which requires indentation and leading space tracking.
|
||||
let docstring = definition
|
||||
.docstring
|
||||
.expect("Sections are only available for docstrings.");
|
||||
|
||||
if checker.settings.enabled.contains(&CheckCode::D405) {
|
||||
if !NUMPY_SECTION_NAMES.contains(&context.section_name.as_str())
|
||||
&& NUMPY_SECTION_NAMES.contains(titlecase(&context.section_name).as_str())
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::CapitalizeSectionName(context.section_name.to_string()),
|
||||
range_for(docstring),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if context
|
||||
.following_lines
|
||||
.last()
|
||||
.map(|line| !line.trim().is_empty())
|
||||
.unwrap_or(true)
|
||||
{
|
||||
if context.is_last_section {
|
||||
if checker.settings.enabled.contains(&CheckCode::D413) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::BlankLineAfterLastSection(context.section_name.to_string()),
|
||||
range_for(docstring),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
if checker.settings.enabled.contains(&CheckCode::D410) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::BlankLineAfterSection(context.section_name.to_string()),
|
||||
range_for(docstring),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if checker.settings.enabled.contains(&CheckCode::D411) {
|
||||
if !context.previous_line.is_empty() {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::BlankLineBeforeSection(context.section_name.to_string()),
|
||||
range_for(docstring),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_numpy_section(
|
||||
checker: &mut Checker,
|
||||
definition: &Definition,
|
||||
context: &SectionContext,
|
||||
) {
|
||||
// TODO(charlie): Implement `_check_parameters_section`.
|
||||
check_common_section(checker, definition, context);
|
||||
check_blanks_and_section_underline(checker, definition, context);
|
||||
|
||||
if checker.settings.enabled.contains(&CheckCode::D406) {
|
||||
let suffix = context
|
||||
.line
|
||||
.trim()
|
||||
.strip_prefix(&context.section_name)
|
||||
.unwrap();
|
||||
if !suffix.is_empty() {
|
||||
let docstring = definition
|
||||
.docstring
|
||||
.expect("Sections are only available for docstrings.");
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::NewLineAfterSectionName(context.section_name.to_string()),
|
||||
range_for(docstring),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/docstrings/types.rs
Normal file
23
src/docstrings/types.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use rustpython_ast::{Expr, Stmt};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DefinitionKind<'a> {
|
||||
Module,
|
||||
Package,
|
||||
Class(&'a Stmt),
|
||||
NestedClass(&'a Stmt),
|
||||
Function(&'a Stmt),
|
||||
NestedFunction(&'a Stmt),
|
||||
Method(&'a Stmt),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Definition<'a> {
|
||||
pub kind: DefinitionKind<'a>,
|
||||
pub docstring: Option<&'a Expr>,
|
||||
}
|
||||
|
||||
pub enum Documentable {
|
||||
Class,
|
||||
Function,
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::collapsible_if, clippy::collapsible_else_if)]
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
@@ -17,6 +18,7 @@ mod check_lines;
|
||||
pub mod checks;
|
||||
pub mod cli;
|
||||
pub mod code_gen;
|
||||
mod docstrings;
|
||||
pub mod fs;
|
||||
pub mod linter;
|
||||
pub mod logging;
|
||||
@@ -27,6 +29,7 @@ pub mod printer;
|
||||
pub mod pyproject;
|
||||
mod python;
|
||||
pub mod settings;
|
||||
pub mod visibility;
|
||||
|
||||
/// Run ruff over Python source code directly.
|
||||
pub fn check(path: &Path, contents: &str) -> Result<Vec<Message>> {
|
||||
|
||||
612
src/linter.rs
612
src/linter.rs
@@ -83,6 +83,36 @@ pub(crate) fn check_path(
|
||||
Ok(checks)
|
||||
}
|
||||
|
||||
pub fn lint_stdin(path: &Path, stdin: &str, settings: &Settings) -> Result<Vec<Message>> {
|
||||
// Tokenize once.
|
||||
let tokens: Vec<LexResult> = tokenize(stdin);
|
||||
|
||||
// Determine the noqa line for every line in the source.
|
||||
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
|
||||
|
||||
// Generate checks.
|
||||
let checks = check_path(
|
||||
path,
|
||||
stdin,
|
||||
tokens,
|
||||
&noqa_line_for,
|
||||
settings,
|
||||
&fixer::Mode::None,
|
||||
)?;
|
||||
|
||||
// Convert to messages.
|
||||
Ok(checks
|
||||
.into_iter()
|
||||
.map(|check| Message {
|
||||
kind: check.kind,
|
||||
fixed: check.fix.map(|fix| fix.applied).unwrap_or_default(),
|
||||
location: check.location,
|
||||
end_location: check.end_location,
|
||||
filename: path.to_string_lossy().to_string(),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn lint_path(
|
||||
path: &Path,
|
||||
settings: &Settings,
|
||||
@@ -762,42 +792,6 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn m001() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/M001.py"),
|
||||
&settings::Settings::for_rules(vec![CheckCode::M001, CheckCode::E501, CheckCode::F841]),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/__init__.py"),
|
||||
&settings::Settings::for_rules(vec![CheckCode::F821, CheckCode::F822]),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn future_annotations() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/future_annotations.py"),
|
||||
&settings::Settings::for_rules(vec![CheckCode::F401, CheckCode::F821]),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn e999() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
@@ -846,6 +840,42 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn b011() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/B011.py"),
|
||||
&settings::Settings::for_rule(CheckCode::B011),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn b014() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/B014.py"),
|
||||
&settings::Settings::for_rule(CheckCode::B014),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn b025() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/B025.py"),
|
||||
&settings::Settings::for_rule(CheckCode::B025),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c400() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
@@ -942,6 +972,42 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c409() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/C409.py"),
|
||||
&settings::Settings::for_rule(CheckCode::C409),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c410() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/C410.py"),
|
||||
&settings::Settings::for_rule(CheckCode::C410),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c414() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/C414.py"),
|
||||
&settings::Settings::for_rule(CheckCode::C414),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn c415() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
@@ -955,10 +1021,430 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u008() -> Result<()> {
|
||||
fn d100() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/U008.py"),
|
||||
&settings::Settings::for_rule(CheckCode::U008),
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D100),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d101() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D101),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d102() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D102),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d103() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D103),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d104() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D104),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d105() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D105),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d106() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D106),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d107() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D107),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d201() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D201),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d202() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D202),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d203() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D203),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d204() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D204),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d205() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D205),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d209() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D209),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d210() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D210),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d211() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D211),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d212() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D212),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d213() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D213),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d300() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D300),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d400() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D400),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d402() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D402),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d403() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D403),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d404() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D404),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d405() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/sections.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D405),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d406() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/sections.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D406),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d407() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/sections.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D407),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d408() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/sections.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D408),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d409() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/sections.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D409),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d410() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/sections.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D410),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d411() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/sections.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D411),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d412() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/sections.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D412),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d413() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/sections.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D413),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d414() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/sections.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D414),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d415() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D415),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d418() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D418),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn d419() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/D.py"),
|
||||
&settings::Settings::for_rule(CheckCode::D419),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
@@ -1073,4 +1559,52 @@ mod tests {
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u008() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/U008.py"),
|
||||
&settings::Settings::for_rule(CheckCode::U008),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn m001() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/M001.py"),
|
||||
&settings::Settings::for_rules(vec![CheckCode::M001, CheckCode::E501, CheckCode::F841]),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/__init__.py"),
|
||||
&settings::Settings::for_rules(vec![CheckCode::F821, CheckCode::F822]),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn future_annotations() -> Result<()> {
|
||||
let mut checks = check_path(
|
||||
Path::new("./resources/test/fixtures/future_annotations.py"),
|
||||
&settings::Settings::for_rules(vec![CheckCode::F401, CheckCode::F821]),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
29
src/main.rs
29
src/main.rs
@@ -1,4 +1,4 @@
|
||||
use std::io;
|
||||
use std::io::{self, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
use std::sync::mpsc::channel;
|
||||
@@ -19,7 +19,7 @@ use ruff::cli::{warn_on, Cli, Warnable};
|
||||
use ruff::fs::iter_python_files;
|
||||
use ruff::linter::add_noqa_to_path;
|
||||
use ruff::linter::autoformat_path;
|
||||
use ruff::linter::lint_path;
|
||||
use ruff::linter::{lint_path, lint_stdin};
|
||||
use ruff::logging::set_up_logging;
|
||||
use ruff::message::Message;
|
||||
use ruff::printer::{Printer, SerializationFormat};
|
||||
@@ -75,6 +75,19 @@ fn show_files(files: &[PathBuf], settings: &Settings) {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_from_stdin() -> Result<String> {
|
||||
let mut buffer = String::new();
|
||||
io::stdin().lock().read_to_string(&mut buffer)?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
fn run_once_stdin(settings: &Settings, filename: &Path) -> Result<Vec<Message>> {
|
||||
let stdin = read_from_stdin()?;
|
||||
let mut messages = lint_stdin(filename, &stdin, settings)?;
|
||||
messages.sort_unstable();
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
fn run_once(
|
||||
files: &[PathBuf],
|
||||
settings: &Settings,
|
||||
@@ -352,7 +365,17 @@ fn inner_main() -> Result<ExitCode> {
|
||||
println!("Formatted {modifications} files.");
|
||||
}
|
||||
} else {
|
||||
let messages = run_once(&cli.files, &settings, !cli.no_cache, cli.fix)?;
|
||||
let messages = if cli.files == vec![PathBuf::from("-")] {
|
||||
if cli.fix {
|
||||
eprintln!("Warning: --fix is not enabled when reading from stdin.");
|
||||
}
|
||||
|
||||
let filename = cli.stdin_filename.unwrap_or_else(|| "-".to_string());
|
||||
let path = Path::new(&filename);
|
||||
run_once_stdin(&settings, path)?
|
||||
} else {
|
||||
run_once(&cli.files, &settings, !cli.no_cache, cli.fix)?
|
||||
};
|
||||
if !cli.quiet {
|
||||
printer.write_once(&messages)?;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
pub use assert_false::assert_false;
|
||||
pub use assert_tuple::assert_tuple;
|
||||
pub use deprecated_unittest_alias::deprecated_unittest_alias;
|
||||
pub use duplicate_exceptions::duplicate_exceptions;
|
||||
pub use if_tuple::if_tuple;
|
||||
pub use invalid_print_syntax::invalid_print_syntax;
|
||||
pub use print_call::print_call;
|
||||
@@ -11,8 +13,10 @@ pub use use_pep604_annotation::use_pep604_annotation;
|
||||
pub use useless_metaclass_type::useless_metaclass_type;
|
||||
pub use useless_object_inheritance::useless_object_inheritance;
|
||||
|
||||
mod assert_false;
|
||||
mod assert_tuple;
|
||||
mod deprecated_unittest_alias;
|
||||
mod duplicate_exceptions;
|
||||
mod if_tuple;
|
||||
mod invalid_print_syntax;
|
||||
mod print_call;
|
||||
|
||||
61
src/plugins/assert_false.rs
Normal file
61
src/plugins/assert_false.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::fixer;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind, Fix};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
|
||||
fn assertion_error(msg: &Option<Box<Expr>>) -> Stmt {
|
||||
Stmt::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
StmtKind::Raise {
|
||||
exc: Some(Box::new(Expr::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
ExprKind::Call {
|
||||
func: Box::new(Expr::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
ExprKind::Name {
|
||||
id: "AssertionError".to_string(),
|
||||
ctx: ExprContext::Load,
|
||||
},
|
||||
)),
|
||||
args: if let Some(msg) = msg {
|
||||
vec![*msg.clone()]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
keywords: vec![],
|
||||
},
|
||||
))),
|
||||
cause: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: &Option<Box<Expr>>) {
|
||||
if let ExprKind::Constant {
|
||||
value: Constant::Bool(false),
|
||||
..
|
||||
} = &test.node
|
||||
{
|
||||
let mut check = Check::new(CheckKind::DoNotAssertFalse, Range::from_located(test));
|
||||
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
let mut generator = SourceGenerator::new();
|
||||
if let Ok(()) = generator.unparse_stmt(&assertion_error(msg)) {
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix {
|
||||
content,
|
||||
location: stmt.location,
|
||||
end_location: stmt.end_location,
|
||||
applied: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
use rustpython_ast::{Expr, Stmt};
|
||||
|
||||
use crate::ast::checks;
|
||||
use crate::ast::checkers;
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::check_ast::Checker;
|
||||
|
||||
pub fn assert_tuple(checker: &mut Checker, stmt: &Stmt, test: &Expr) {
|
||||
if let Some(check) =
|
||||
checks::check_assert_tuple(test, checker.locate_check(Range::from_located(stmt)))
|
||||
checkers::assert_tuple(test, checker.locate_check(Range::from_located(stmt)))
|
||||
{
|
||||
checker.add_check(check);
|
||||
}
|
||||
|
||||
114
src/plugins/duplicate_exceptions.rs
Normal file
114
src/plugins/duplicate_exceptions.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use itertools::Itertools;
|
||||
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Stmt};
|
||||
|
||||
use crate::ast::helpers;
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::autofix::fixer;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckCode, CheckKind, Fix};
|
||||
use crate::code_gen::SourceGenerator;
|
||||
|
||||
fn type_pattern(elts: Vec<&Expr>) -> Expr {
|
||||
Expr::new(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
ExprKind::Tuple {
|
||||
elts: elts.into_iter().cloned().collect(),
|
||||
ctx: ExprContext::Load,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn duplicate_handler_exceptions(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
elts: &Vec<Expr>,
|
||||
) -> BTreeSet<String> {
|
||||
let mut seen: BTreeSet<String> = Default::default();
|
||||
let mut duplicates: BTreeSet<String> = Default::default();
|
||||
let mut unique_elts: Vec<&Expr> = Default::default();
|
||||
for type_ in elts {
|
||||
if let Some(name) = helpers::compose_call_path(type_) {
|
||||
if seen.contains(&name) {
|
||||
duplicates.insert(name);
|
||||
} else {
|
||||
seen.insert(name);
|
||||
unique_elts.push(type_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if checker.settings.enabled.contains(&CheckCode::B014) {
|
||||
// TODO(charlie): Handle "BaseException" and redundant exception aliases.
|
||||
if !duplicates.is_empty() {
|
||||
let mut check = Check::new(
|
||||
CheckKind::DuplicateHandlerException(
|
||||
duplicates.into_iter().sorted().collect::<Vec<String>>(),
|
||||
),
|
||||
checker.locate_check(Range::from_located(expr)),
|
||||
);
|
||||
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
// TODO(charlie): If we have a single element, remove the tuple.
|
||||
let mut generator = SourceGenerator::new();
|
||||
if let Ok(()) = generator.unparse_expr(&type_pattern(unique_elts), 0) {
|
||||
if let Ok(content) = generator.generate() {
|
||||
check.amend(Fix {
|
||||
content,
|
||||
location: expr.location,
|
||||
end_location: expr.end_location,
|
||||
applied: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
|
||||
seen
|
||||
}
|
||||
|
||||
pub fn duplicate_exceptions(checker: &mut Checker, stmt: &Stmt, handlers: &[Excepthandler]) {
|
||||
let mut seen: BTreeSet<String> = Default::default();
|
||||
let mut duplicates: BTreeSet<String> = Default::default();
|
||||
for handler in handlers {
|
||||
match &handler.node {
|
||||
ExcepthandlerKind::ExceptHandler { type_, .. } => {
|
||||
if let Some(type_) = type_ {
|
||||
match &type_.node {
|
||||
ExprKind::Attribute { .. } | ExprKind::Name { .. } => {
|
||||
if let Some(name) = helpers::compose_call_path(type_) {
|
||||
if seen.contains(&name) {
|
||||
duplicates.insert(name);
|
||||
} else {
|
||||
seen.insert(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Tuple { elts, .. } => {
|
||||
for name in duplicate_handler_exceptions(checker, type_, elts) {
|
||||
if seen.contains(&name) {
|
||||
duplicates.insert(name);
|
||||
} else {
|
||||
seen.insert(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if checker.settings.enabled.contains(&CheckCode::B025) {
|
||||
for duplicate in duplicates.into_iter().sorted() {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::DuplicateTryBlockException(duplicate),
|
||||
checker.locate_check(Range::from_located(stmt)),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
use rustpython_ast::{Expr, Stmt};
|
||||
|
||||
use crate::ast::checks;
|
||||
use crate::ast::checkers;
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::check_ast::Checker;
|
||||
|
||||
pub fn if_tuple(checker: &mut Checker, stmt: &Stmt, test: &Expr) {
|
||||
if let Some(check) =
|
||||
checks::check_if_tuple(test, checker.locate_check(Range::from_located(stmt)))
|
||||
{
|
||||
if let Some(check) = checkers::if_tuple(test, checker.locate_check(Range::from_located(stmt))) {
|
||||
checker.add_check(check);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use log::error;
|
||||
use rustpython_ast::{Expr, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::checks;
|
||||
use crate::ast::checkers;
|
||||
use crate::autofix::{fixer, fixes};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::CheckCode;
|
||||
|
||||
pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
|
||||
if let Some(mut check) = checks::check_print_call(
|
||||
if let Some(mut check) = checkers::print_call(
|
||||
expr,
|
||||
func,
|
||||
checker.settings.enabled.contains(&CheckCode::T201),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use rustpython_ast::{Expr, Stmt};
|
||||
|
||||
use crate::ast::checks;
|
||||
use crate::ast::{checkers, helpers};
|
||||
use crate::autofix::{fixer, fixes};
|
||||
use crate::check_ast::Checker;
|
||||
|
||||
@@ -12,14 +12,14 @@ pub fn super_call_with_parameters(
|
||||
) {
|
||||
// Only bother going through the super check at all if we're in a `super` call.
|
||||
// (We check this in `check_super_args` too, so this is just an optimization.)
|
||||
if checks::is_super_call_with_arguments(func, args) {
|
||||
if helpers::is_super_call_with_arguments(func, args) {
|
||||
let scope = checker.current_scope();
|
||||
let parents: Vec<&Stmt> = checker
|
||||
.parent_stack
|
||||
.iter()
|
||||
.map(|index| checker.parents[*index])
|
||||
.collect();
|
||||
if let Some(mut check) = checks::check_super_args(scope, &parents, expr, func, args) {
|
||||
if let Some(mut check) = checkers::super_args(scope, &parents, expr, func, args) {
|
||||
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
if let Some(fix) = fixes::remove_super_arguments(&mut checker.locator, expr) {
|
||||
check.amend(fix);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use rustpython_ast::Expr;
|
||||
|
||||
use crate::ast::checks;
|
||||
use crate::ast::checkers;
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::autofix::fixer;
|
||||
use crate::check_ast::Checker;
|
||||
@@ -8,7 +8,7 @@ use crate::checks::{CheckKind, Fix};
|
||||
|
||||
pub fn type_of_primitive(checker: &mut Checker, expr: &Expr, func: &Expr, args: &Vec<Expr>) {
|
||||
if let Some(mut check) =
|
||||
checks::check_type_of_primitive(func, args, checker.locate_check(Range::from_located(expr)))
|
||||
checkers::type_of_primitive(func, args, checker.locate_check(Range::from_located(expr)))
|
||||
{
|
||||
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
if let CheckKind::TypeOfPrimitive(primitive) = &check.kind {
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
use rustpython_ast::Expr;
|
||||
|
||||
use crate::ast::checks;
|
||||
use crate::ast::checkers;
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::autofix::fixer;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::Fix;
|
||||
|
||||
pub fn unnecessary_abspath(checker: &mut Checker, expr: &Expr, func: &Expr, args: &Vec<Expr>) {
|
||||
if let Some(mut check) = checks::check_unnecessary_abspath(
|
||||
func,
|
||||
args,
|
||||
checker.locate_check(Range::from_located(expr)),
|
||||
) {
|
||||
if let Some(mut check) =
|
||||
checkers::unnecessary_abspath(func, args, checker.locate_check(Range::from_located(expr)))
|
||||
{
|
||||
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
check.amend(Fix {
|
||||
content: "__file__".to_string(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use log::error;
|
||||
use rustpython_ast::{Expr, Stmt};
|
||||
|
||||
use crate::ast::checks;
|
||||
use crate::ast::checkers;
|
||||
use crate::ast::types::{CheckLocator, Range};
|
||||
use crate::autofix::{fixer, fixes};
|
||||
use crate::check_ast::Checker;
|
||||
@@ -12,7 +12,7 @@ pub fn useless_metaclass_type(
|
||||
value: &Expr,
|
||||
targets: &Vec<Expr>,
|
||||
) {
|
||||
if let Some(mut check) = checks::check_useless_metaclass_type(
|
||||
if let Some(mut check) = checkers::useless_metaclass_type(
|
||||
targets,
|
||||
value,
|
||||
checker.locate_check(Range::from_located(stmt)),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use rustpython_ast::{Expr, Keyword, Stmt};
|
||||
|
||||
use crate::ast::checks;
|
||||
use crate::ast::checkers;
|
||||
use crate::autofix::{fixer, fixes};
|
||||
use crate::check_ast::Checker;
|
||||
|
||||
@@ -12,7 +12,7 @@ pub fn useless_object_inheritance(
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
let scope = checker.current_scope();
|
||||
if let Some(mut check) = checks::check_useless_object_inheritance(name, bases, scope) {
|
||||
if let Some(mut check) = checkers::useless_object_inheritance(name, bases, scope) {
|
||||
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
|
||||
if let Some(fix) = fixes::remove_class_def_base(
|
||||
&mut checker.locator,
|
||||
|
||||
37
src/snapshots/ruff__linter__tests__b011.snap
Normal file
37
src/snapshots/ruff__linter__tests__b011.snap
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: DoNotAssertFalse
|
||||
location:
|
||||
row: 8
|
||||
column: 8
|
||||
end_location:
|
||||
row: 8
|
||||
column: 13
|
||||
fix:
|
||||
content: raise AssertionError()
|
||||
location:
|
||||
row: 8
|
||||
column: 1
|
||||
end_location:
|
||||
row: 8
|
||||
column: 13
|
||||
applied: false
|
||||
- kind: DoNotAssertFalse
|
||||
location:
|
||||
row: 10
|
||||
column: 8
|
||||
end_location:
|
||||
row: 10
|
||||
column: 13
|
||||
fix:
|
||||
content: "raise AssertionError(\"message\")"
|
||||
location:
|
||||
row: 10
|
||||
column: 1
|
||||
end_location:
|
||||
row: 10
|
||||
column: 24
|
||||
applied: false
|
||||
|
||||
59
src/snapshots/ruff__linter__tests__b014.snap
Normal file
59
src/snapshots/ruff__linter__tests__b014.snap
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
DuplicateHandlerException:
|
||||
- OSError
|
||||
location:
|
||||
row: 17
|
||||
column: 9
|
||||
end_location:
|
||||
row: 17
|
||||
column: 25
|
||||
fix:
|
||||
content: "OSError,"
|
||||
location:
|
||||
row: 17
|
||||
column: 9
|
||||
end_location:
|
||||
row: 17
|
||||
column: 25
|
||||
applied: false
|
||||
- kind:
|
||||
DuplicateHandlerException:
|
||||
- MyError
|
||||
location:
|
||||
row: 28
|
||||
column: 9
|
||||
end_location:
|
||||
row: 28
|
||||
column: 25
|
||||
fix:
|
||||
content: "MyError,"
|
||||
location:
|
||||
row: 28
|
||||
column: 9
|
||||
end_location:
|
||||
row: 28
|
||||
column: 25
|
||||
applied: false
|
||||
- kind:
|
||||
DuplicateHandlerException:
|
||||
- re.error
|
||||
location:
|
||||
row: 49
|
||||
column: 9
|
||||
end_location:
|
||||
row: 49
|
||||
column: 27
|
||||
fix:
|
||||
content: "re.error,"
|
||||
location:
|
||||
row: 49
|
||||
column: 9
|
||||
end_location:
|
||||
row: 49
|
||||
column: 27
|
||||
applied: false
|
||||
|
||||
41
src/snapshots/ruff__linter__tests__b025.snap
Normal file
41
src/snapshots/ruff__linter__tests__b025.snap
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
DuplicateTryBlockException: ValueError
|
||||
location:
|
||||
row: 15
|
||||
column: 1
|
||||
end_location:
|
||||
row: 22
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
DuplicateTryBlockException: pickle.PickleError
|
||||
location:
|
||||
row: 22
|
||||
column: 1
|
||||
end_location:
|
||||
row: 31
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
DuplicateTryBlockException: TypeError
|
||||
location:
|
||||
row: 31
|
||||
column: 1
|
||||
end_location:
|
||||
row: 39
|
||||
column: 1
|
||||
fix: ~
|
||||
- kind:
|
||||
DuplicateTryBlockException: ValueError
|
||||
location:
|
||||
row: 31
|
||||
column: 1
|
||||
end_location:
|
||||
row: 39
|
||||
column: 1
|
||||
fix: ~
|
||||
|
||||
32
src/snapshots/ruff__linter__tests__c409.snap
Normal file
32
src/snapshots/ruff__linter__tests__c409.snap
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnnecessaryLiteralWithinTupleCall: list
|
||||
location:
|
||||
row: 1
|
||||
column: 6
|
||||
end_location:
|
||||
row: 1
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryLiteralWithinTupleCall: tuple
|
||||
location:
|
||||
row: 2
|
||||
column: 6
|
||||
end_location:
|
||||
row: 2
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryLiteralWithinTupleCall: list
|
||||
location:
|
||||
row: 3
|
||||
column: 6
|
||||
end_location:
|
||||
row: 3
|
||||
column: 15
|
||||
fix: ~
|
||||
|
||||
41
src/snapshots/ruff__linter__tests__c410.snap
Normal file
41
src/snapshots/ruff__linter__tests__c410.snap
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnnecessaryLiteralWithinListCall: list
|
||||
location:
|
||||
row: 1
|
||||
column: 6
|
||||
end_location:
|
||||
row: 1
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryLiteralWithinListCall: tuple
|
||||
location:
|
||||
row: 2
|
||||
column: 6
|
||||
end_location:
|
||||
row: 2
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryLiteralWithinListCall: list
|
||||
location:
|
||||
row: 3
|
||||
column: 6
|
||||
end_location:
|
||||
row: 3
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryLiteralWithinListCall: tuple
|
||||
location:
|
||||
row: 4
|
||||
column: 6
|
||||
end_location:
|
||||
row: 4
|
||||
column: 14
|
||||
fix: ~
|
||||
|
||||
148
src/snapshots/ruff__linter__tests__c414.snap
Normal file
148
src/snapshots/ruff__linter__tests__c414.snap
Normal file
@@ -0,0 +1,148 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
- list
|
||||
- list
|
||||
location:
|
||||
row: 2
|
||||
column: 1
|
||||
end_location:
|
||||
row: 2
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
- tuple
|
||||
- list
|
||||
location:
|
||||
row: 3
|
||||
column: 1
|
||||
end_location:
|
||||
row: 3
|
||||
column: 15
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
- list
|
||||
- tuple
|
||||
location:
|
||||
row: 4
|
||||
column: 1
|
||||
end_location:
|
||||
row: 4
|
||||
column: 15
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
- tuple
|
||||
- tuple
|
||||
location:
|
||||
row: 5
|
||||
column: 1
|
||||
end_location:
|
||||
row: 5
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
- set
|
||||
- set
|
||||
location:
|
||||
row: 6
|
||||
column: 1
|
||||
end_location:
|
||||
row: 6
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
- list
|
||||
- set
|
||||
location:
|
||||
row: 7
|
||||
column: 1
|
||||
end_location:
|
||||
row: 7
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
- tuple
|
||||
- set
|
||||
location:
|
||||
row: 8
|
||||
column: 1
|
||||
end_location:
|
||||
row: 8
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
- sorted
|
||||
- set
|
||||
location:
|
||||
row: 9
|
||||
column: 1
|
||||
end_location:
|
||||
row: 9
|
||||
column: 15
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
- reversed
|
||||
- set
|
||||
location:
|
||||
row: 10
|
||||
column: 1
|
||||
end_location:
|
||||
row: 10
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
- list
|
||||
- sorted
|
||||
location:
|
||||
row: 11
|
||||
column: 1
|
||||
end_location:
|
||||
row: 11
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
- tuple
|
||||
- sorted
|
||||
location:
|
||||
row: 12
|
||||
column: 1
|
||||
end_location:
|
||||
row: 12
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
- sorted
|
||||
- sorted
|
||||
location:
|
||||
row: 13
|
||||
column: 1
|
||||
end_location:
|
||||
row: 13
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind:
|
||||
UnnecessaryDoubleCastOrProcess:
|
||||
- reversed
|
||||
- sorted
|
||||
location:
|
||||
row: 14
|
||||
column: 1
|
||||
end_location:
|
||||
row: 14
|
||||
column: 20
|
||||
fix: ~
|
||||
|
||||
13
src/snapshots/ruff__linter__tests__d100.snap
Normal file
13
src/snapshots/ruff__linter__tests__d100.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: PublicModule
|
||||
location:
|
||||
row: 1
|
||||
column: 1
|
||||
end_location:
|
||||
row: 1
|
||||
column: 1
|
||||
fix: ~
|
||||
|
||||
13
src/snapshots/ruff__linter__tests__d101.snap
Normal file
13
src/snapshots/ruff__linter__tests__d101.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: PublicClass
|
||||
location:
|
||||
row: 14
|
||||
column: 1
|
||||
end_location:
|
||||
row: 67
|
||||
column: 1
|
||||
fix: ~
|
||||
|
||||
29
src/snapshots/ruff__linter__tests__d102.snap
Normal file
29
src/snapshots/ruff__linter__tests__d102.snap
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: PublicMethod
|
||||
location:
|
||||
row: 22
|
||||
column: 5
|
||||
end_location:
|
||||
row: 25
|
||||
column: 5
|
||||
fix: ~
|
||||
- kind: PublicMethod
|
||||
location:
|
||||
row: 51
|
||||
column: 5
|
||||
end_location:
|
||||
row: 54
|
||||
column: 5
|
||||
fix: ~
|
||||
- kind: PublicMethod
|
||||
location:
|
||||
row: 63
|
||||
column: 5
|
||||
end_location:
|
||||
row: 67
|
||||
column: 1
|
||||
fix: ~
|
||||
|
||||
13
src/snapshots/ruff__linter__tests__d103.snap
Normal file
13
src/snapshots/ruff__linter__tests__d103.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: PublicFunction
|
||||
location:
|
||||
row: 395
|
||||
column: 1
|
||||
end_location:
|
||||
row: 396
|
||||
column: 1
|
||||
fix: ~
|
||||
|
||||
6
src/snapshots/ruff__linter__tests__d104.snap
Normal file
6
src/snapshots/ruff__linter__tests__d104.snap
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
13
src/snapshots/ruff__linter__tests__d105.snap
Normal file
13
src/snapshots/ruff__linter__tests__d105.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: MagicMethod
|
||||
location:
|
||||
row: 59
|
||||
column: 5
|
||||
end_location:
|
||||
row: 62
|
||||
column: 5
|
||||
fix: ~
|
||||
|
||||
6
src/snapshots/ruff__linter__tests__d106.snap
Normal file
6
src/snapshots/ruff__linter__tests__d106.snap
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
21
src/snapshots/ruff__linter__tests__d107.snap
Normal file
21
src/snapshots/ruff__linter__tests__d107.snap
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: PublicInit
|
||||
location:
|
||||
row: 55
|
||||
column: 5
|
||||
end_location:
|
||||
row: 58
|
||||
column: 5
|
||||
fix: ~
|
||||
- kind: PublicInit
|
||||
location:
|
||||
row: 529
|
||||
column: 5
|
||||
end_location:
|
||||
row: 533
|
||||
column: 1
|
||||
fix: ~
|
||||
|
||||
13
src/snapshots/ruff__linter__tests__d200.snap
Normal file
13
src/snapshots/ruff__linter__tests__d200.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: FitsOnOneLine
|
||||
location:
|
||||
row: 124
|
||||
column: 5
|
||||
end_location:
|
||||
row: 126
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
23
src/snapshots/ruff__linter__tests__d201.snap
Normal file
23
src/snapshots/ruff__linter__tests__d201.snap
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
NoBlankLineBeforeFunction: 1
|
||||
location:
|
||||
row: 132
|
||||
column: 5
|
||||
end_location:
|
||||
row: 132
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind:
|
||||
NoBlankLineBeforeFunction: 1
|
||||
location:
|
||||
row: 146
|
||||
column: 5
|
||||
end_location:
|
||||
row: 146
|
||||
column: 38
|
||||
fix: ~
|
||||
|
||||
23
src/snapshots/ruff__linter__tests__d202.snap
Normal file
23
src/snapshots/ruff__linter__tests__d202.snap
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
NoBlankLineAfterFunction: 1
|
||||
location:
|
||||
row: 137
|
||||
column: 5
|
||||
end_location:
|
||||
row: 137
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind:
|
||||
NoBlankLineAfterFunction: 1
|
||||
location:
|
||||
row: 146
|
||||
column: 5
|
||||
end_location:
|
||||
row: 146
|
||||
column: 38
|
||||
fix: ~
|
||||
|
||||
32
src/snapshots/ruff__linter__tests__d203.snap
Normal file
32
src/snapshots/ruff__linter__tests__d203.snap
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
OneBlankLineBeforeClass: 0
|
||||
location:
|
||||
row: 156
|
||||
column: 5
|
||||
end_location:
|
||||
row: 156
|
||||
column: 33
|
||||
fix: ~
|
||||
- kind:
|
||||
OneBlankLineBeforeClass: 0
|
||||
location:
|
||||
row: 187
|
||||
column: 5
|
||||
end_location:
|
||||
row: 187
|
||||
column: 46
|
||||
fix: ~
|
||||
- kind:
|
||||
OneBlankLineBeforeClass: 0
|
||||
location:
|
||||
row: 521
|
||||
column: 5
|
||||
end_location:
|
||||
row: 527
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
23
src/snapshots/ruff__linter__tests__d204.snap
Normal file
23
src/snapshots/ruff__linter__tests__d204.snap
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
OneBlankLineAfterClass: 0
|
||||
location:
|
||||
row: 176
|
||||
column: 5
|
||||
end_location:
|
||||
row: 176
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind:
|
||||
OneBlankLineAfterClass: 0
|
||||
location:
|
||||
row: 187
|
||||
column: 5
|
||||
end_location:
|
||||
row: 187
|
||||
column: 46
|
||||
fix: ~
|
||||
|
||||
21
src/snapshots/ruff__linter__tests__d205.snap
Normal file
21
src/snapshots/ruff__linter__tests__d205.snap
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: NoBlankLineAfterSummary
|
||||
location:
|
||||
row: 195
|
||||
column: 5
|
||||
end_location:
|
||||
row: 198
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: NoBlankLineAfterSummary
|
||||
location:
|
||||
row: 205
|
||||
column: 5
|
||||
end_location:
|
||||
row: 210
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
13
src/snapshots/ruff__linter__tests__d209.snap
Normal file
13
src/snapshots/ruff__linter__tests__d209.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: NewLineAfterLastParagraph
|
||||
location:
|
||||
row: 276
|
||||
column: 5
|
||||
end_location:
|
||||
row: 278
|
||||
column: 20
|
||||
fix: ~
|
||||
|
||||
29
src/snapshots/ruff__linter__tests__d210.snap
Normal file
29
src/snapshots/ruff__linter__tests__d210.snap
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: NoSurroundingWhitespace
|
||||
location:
|
||||
row: 283
|
||||
column: 5
|
||||
end_location:
|
||||
row: 283
|
||||
column: 34
|
||||
fix: ~
|
||||
- kind: NoSurroundingWhitespace
|
||||
location:
|
||||
row: 288
|
||||
column: 5
|
||||
end_location:
|
||||
row: 288
|
||||
column: 38
|
||||
fix: ~
|
||||
- kind: NoSurroundingWhitespace
|
||||
location:
|
||||
row: 294
|
||||
column: 5
|
||||
end_location:
|
||||
row: 297
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
23
src/snapshots/ruff__linter__tests__d211.snap
Normal file
23
src/snapshots/ruff__linter__tests__d211.snap
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
NoBlankLineBeforeClass: 1
|
||||
location:
|
||||
row: 165
|
||||
column: 5
|
||||
end_location:
|
||||
row: 165
|
||||
column: 30
|
||||
fix: ~
|
||||
- kind:
|
||||
NoBlankLineBeforeClass: 1
|
||||
location:
|
||||
row: 176
|
||||
column: 5
|
||||
end_location:
|
||||
row: 176
|
||||
column: 25
|
||||
fix: ~
|
||||
|
||||
13
src/snapshots/ruff__linter__tests__d212.snap
Normal file
13
src/snapshots/ruff__linter__tests__d212.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: MultiLineSummaryFirstLine
|
||||
location:
|
||||
row: 124
|
||||
column: 5
|
||||
end_location:
|
||||
row: 126
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
133
src/snapshots/ruff__linter__tests__d213.snap
Normal file
133
src/snapshots/ruff__linter__tests__d213.snap
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 195
|
||||
column: 5
|
||||
end_location:
|
||||
row: 198
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 205
|
||||
column: 5
|
||||
end_location:
|
||||
row: 210
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 215
|
||||
column: 5
|
||||
end_location:
|
||||
row: 219
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 225
|
||||
column: 5
|
||||
end_location:
|
||||
row: 229
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 235
|
||||
column: 5
|
||||
end_location:
|
||||
row: 239
|
||||
column: 4
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 245
|
||||
column: 5
|
||||
end_location:
|
||||
row: 249
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 255
|
||||
column: 5
|
||||
end_location:
|
||||
row: 259
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 265
|
||||
column: 5
|
||||
end_location:
|
||||
row: 269
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 276
|
||||
column: 5
|
||||
end_location:
|
||||
row: 278
|
||||
column: 20
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 294
|
||||
column: 5
|
||||
end_location:
|
||||
row: 297
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 338
|
||||
column: 5
|
||||
end_location:
|
||||
row: 343
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 378
|
||||
column: 5
|
||||
end_location:
|
||||
row: 381
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 387
|
||||
column: 5
|
||||
end_location:
|
||||
row: 391
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 433
|
||||
column: 37
|
||||
end_location:
|
||||
row: 436
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 445
|
||||
column: 5
|
||||
end_location:
|
||||
row: 449
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: MultiLineSummarySecondLine
|
||||
location:
|
||||
row: 521
|
||||
column: 5
|
||||
end_location:
|
||||
row: 527
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
45
src/snapshots/ruff__linter__tests__d300.snap
Normal file
45
src/snapshots/ruff__linter__tests__d300.snap
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UsesTripleQuotes
|
||||
location:
|
||||
row: 302
|
||||
column: 6
|
||||
end_location:
|
||||
row: 302
|
||||
column: 20
|
||||
fix: ~
|
||||
- kind: UsesTripleQuotes
|
||||
location:
|
||||
row: 307
|
||||
column: 6
|
||||
end_location:
|
||||
row: 307
|
||||
column: 20
|
||||
fix: ~
|
||||
- kind: UsesTripleQuotes
|
||||
location:
|
||||
row: 312
|
||||
column: 6
|
||||
end_location:
|
||||
row: 312
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind: UsesTripleQuotes
|
||||
location:
|
||||
row: 317
|
||||
column: 6
|
||||
end_location:
|
||||
row: 317
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind: UsesTripleQuotes
|
||||
location:
|
||||
row: 323
|
||||
column: 6
|
||||
end_location:
|
||||
row: 323
|
||||
column: 17
|
||||
fix: ~
|
||||
|
||||
133
src/snapshots/ruff__linter__tests__d400.snap
Normal file
133
src/snapshots/ruff__linter__tests__d400.snap
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 124
|
||||
column: 5
|
||||
end_location:
|
||||
row: 126
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 283
|
||||
column: 5
|
||||
end_location:
|
||||
row: 283
|
||||
column: 34
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 288
|
||||
column: 5
|
||||
end_location:
|
||||
row: 288
|
||||
column: 38
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 350
|
||||
column: 5
|
||||
end_location:
|
||||
row: 350
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 401
|
||||
column: 25
|
||||
end_location:
|
||||
row: 401
|
||||
column: 40
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 405
|
||||
column: 5
|
||||
end_location:
|
||||
row: 405
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 411
|
||||
column: 5
|
||||
end_location:
|
||||
row: 411
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 417
|
||||
column: 35
|
||||
end_location:
|
||||
row: 417
|
||||
column: 50
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 424
|
||||
column: 49
|
||||
end_location:
|
||||
row: 424
|
||||
column: 64
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 465
|
||||
column: 5
|
||||
end_location:
|
||||
row: 465
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 470
|
||||
column: 5
|
||||
end_location:
|
||||
row: 470
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 475
|
||||
column: 5
|
||||
end_location:
|
||||
row: 475
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 482
|
||||
column: 5
|
||||
end_location:
|
||||
row: 482
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 504
|
||||
column: 5
|
||||
end_location:
|
||||
row: 504
|
||||
column: 35
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 509
|
||||
column: 5
|
||||
end_location:
|
||||
row: 509
|
||||
column: 34
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 515
|
||||
column: 5
|
||||
end_location:
|
||||
row: 515
|
||||
column: 33
|
||||
fix: ~
|
||||
|
||||
13
src/snapshots/ruff__linter__tests__d402.snap
Normal file
13
src/snapshots/ruff__linter__tests__d402.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: NoSignature
|
||||
location:
|
||||
row: 373
|
||||
column: 5
|
||||
end_location:
|
||||
row: 373
|
||||
column: 31
|
||||
fix: ~
|
||||
|
||||
6
src/snapshots/ruff__linter__tests__d403.snap
Normal file
6
src/snapshots/ruff__linter__tests__d403.snap
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
6
src/snapshots/ruff__linter__tests__d404.snap
Normal file
6
src/snapshots/ruff__linter__tests__d404.snap
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
23
src/snapshots/ruff__linter__tests__d405.snap
Normal file
23
src/snapshots/ruff__linter__tests__d405.snap
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
CapitalizeSectionName: returns
|
||||
location:
|
||||
row: 17
|
||||
column: 5
|
||||
end_location:
|
||||
row: 23
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
CapitalizeSectionName: Short summary
|
||||
location:
|
||||
row: 207
|
||||
column: 5
|
||||
end_location:
|
||||
row: 221
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
41
src/snapshots/ruff__linter__tests__d406.snap
Normal file
41
src/snapshots/ruff__linter__tests__d406.snap
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
NewLineAfterSectionName: Returns
|
||||
location:
|
||||
row: 30
|
||||
column: 5
|
||||
end_location:
|
||||
row: 36
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
NewLineAfterSectionName: Raises
|
||||
location:
|
||||
row: 207
|
||||
column: 5
|
||||
end_location:
|
||||
row: 221
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
NewLineAfterSectionName: Returns
|
||||
location:
|
||||
row: 252
|
||||
column: 5
|
||||
end_location:
|
||||
row: 262
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
NewLineAfterSectionName: Raises
|
||||
location:
|
||||
row: 252
|
||||
column: 5
|
||||
end_location:
|
||||
row: 262
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
50
src/snapshots/ruff__linter__tests__d407.snap
Normal file
50
src/snapshots/ruff__linter__tests__d407.snap
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
DashedUnderlineAfterSection: Returns
|
||||
location:
|
||||
row: 42
|
||||
column: 5
|
||||
end_location:
|
||||
row: 47
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
DashedUnderlineAfterSection: Returns
|
||||
location:
|
||||
row: 54
|
||||
column: 5
|
||||
end_location:
|
||||
row: 58
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
DashedUnderlineAfterSection: Raises
|
||||
location:
|
||||
row: 207
|
||||
column: 5
|
||||
end_location:
|
||||
row: 221
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
DashedUnderlineAfterSection: Returns
|
||||
location:
|
||||
row: 252
|
||||
column: 5
|
||||
end_location:
|
||||
row: 262
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
DashedUnderlineAfterSection: Raises
|
||||
location:
|
||||
row: 252
|
||||
column: 5
|
||||
end_location:
|
||||
row: 262
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
14
src/snapshots/ruff__linter__tests__d408.snap
Normal file
14
src/snapshots/ruff__linter__tests__d408.snap
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
SectionUnderlineAfterName: Returns
|
||||
location:
|
||||
row: 85
|
||||
column: 5
|
||||
end_location:
|
||||
row: 92
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
23
src/snapshots/ruff__linter__tests__d409.snap
Normal file
23
src/snapshots/ruff__linter__tests__d409.snap
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
SectionUnderlineMatchesSectionLength: Returns
|
||||
location:
|
||||
row: 99
|
||||
column: 5
|
||||
end_location:
|
||||
row: 105
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
SectionUnderlineMatchesSectionLength: Returns
|
||||
location:
|
||||
row: 207
|
||||
column: 5
|
||||
end_location:
|
||||
row: 221
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
23
src/snapshots/ruff__linter__tests__d410.snap
Normal file
23
src/snapshots/ruff__linter__tests__d410.snap
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BlankLineAfterSection: Returns
|
||||
location:
|
||||
row: 67
|
||||
column: 5
|
||||
end_location:
|
||||
row: 78
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
BlankLineAfterSection: Returns
|
||||
location:
|
||||
row: 207
|
||||
column: 5
|
||||
end_location:
|
||||
row: 221
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
32
src/snapshots/ruff__linter__tests__d411.snap
Normal file
32
src/snapshots/ruff__linter__tests__d411.snap
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
BlankLineBeforeSection: Yields
|
||||
location:
|
||||
row: 67
|
||||
column: 5
|
||||
end_location:
|
||||
row: 78
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
BlankLineBeforeSection: Returns
|
||||
location:
|
||||
row: 122
|
||||
column: 5
|
||||
end_location:
|
||||
row: 129
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
BlankLineBeforeSection: Raises
|
||||
location:
|
||||
row: 207
|
||||
column: 5
|
||||
end_location:
|
||||
row: 221
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
14
src/snapshots/ruff__linter__tests__d412.snap
Normal file
14
src/snapshots/ruff__linter__tests__d412.snap
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
NoBlankLinesBetweenHeaderAndContent: Short summary
|
||||
location:
|
||||
row: 207
|
||||
column: 5
|
||||
end_location:
|
||||
row: 221
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
6
src/snapshots/ruff__linter__tests__d413.snap
Normal file
6
src/snapshots/ruff__linter__tests__d413.snap
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
50
src/snapshots/ruff__linter__tests__d414.snap
Normal file
50
src/snapshots/ruff__linter__tests__d414.snap
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
NonEmptySection: Returns
|
||||
location:
|
||||
row: 54
|
||||
column: 5
|
||||
end_location:
|
||||
row: 58
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
NonEmptySection: Returns
|
||||
location:
|
||||
row: 67
|
||||
column: 5
|
||||
end_location:
|
||||
row: 78
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
NonEmptySection: Yields
|
||||
location:
|
||||
row: 67
|
||||
column: 5
|
||||
end_location:
|
||||
row: 78
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
NonEmptySection: Returns
|
||||
location:
|
||||
row: 161
|
||||
column: 5
|
||||
end_location:
|
||||
row: 165
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
NonEmptySection: Returns
|
||||
location:
|
||||
row: 252
|
||||
column: 5
|
||||
end_location:
|
||||
row: 262
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
125
src/snapshots/ruff__linter__tests__d415.snap
Normal file
125
src/snapshots/ruff__linter__tests__d415.snap
Normal file
@@ -0,0 +1,125 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 124
|
||||
column: 5
|
||||
end_location:
|
||||
row: 126
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 283
|
||||
column: 5
|
||||
end_location:
|
||||
row: 283
|
||||
column: 34
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 288
|
||||
column: 5
|
||||
end_location:
|
||||
row: 288
|
||||
column: 38
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 350
|
||||
column: 5
|
||||
end_location:
|
||||
row: 350
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 401
|
||||
column: 25
|
||||
end_location:
|
||||
row: 401
|
||||
column: 40
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 405
|
||||
column: 5
|
||||
end_location:
|
||||
row: 405
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 411
|
||||
column: 5
|
||||
end_location:
|
||||
row: 411
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 417
|
||||
column: 35
|
||||
end_location:
|
||||
row: 417
|
||||
column: 50
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 424
|
||||
column: 49
|
||||
end_location:
|
||||
row: 424
|
||||
column: 64
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 465
|
||||
column: 5
|
||||
end_location:
|
||||
row: 465
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 470
|
||||
column: 5
|
||||
end_location:
|
||||
row: 470
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 475
|
||||
column: 5
|
||||
end_location:
|
||||
row: 475
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 482
|
||||
column: 5
|
||||
end_location:
|
||||
row: 482
|
||||
column: 25
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 504
|
||||
column: 5
|
||||
end_location:
|
||||
row: 504
|
||||
column: 35
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 515
|
||||
column: 5
|
||||
end_location:
|
||||
row: 515
|
||||
column: 33
|
||||
fix: ~
|
||||
|
||||
29
src/snapshots/ruff__linter__tests__d418.snap
Normal file
29
src/snapshots/ruff__linter__tests__d418.snap
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: SkipDocstring
|
||||
location:
|
||||
row: 33
|
||||
column: 5
|
||||
end_location:
|
||||
row: 37
|
||||
column: 5
|
||||
fix: ~
|
||||
- kind: SkipDocstring
|
||||
location:
|
||||
row: 85
|
||||
column: 5
|
||||
end_location:
|
||||
row: 89
|
||||
column: 5
|
||||
fix: ~
|
||||
- kind: SkipDocstring
|
||||
location:
|
||||
row: 105
|
||||
column: 1
|
||||
end_location:
|
||||
row: 110
|
||||
column: 1
|
||||
fix: ~
|
||||
|
||||
29
src/snapshots/ruff__linter__tests__d419.snap
Normal file
29
src/snapshots/ruff__linter__tests__d419.snap
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: NonEmpty
|
||||
location:
|
||||
row: 19
|
||||
column: 9
|
||||
end_location:
|
||||
row: 19
|
||||
column: 15
|
||||
fix: ~
|
||||
- kind: NonEmpty
|
||||
location:
|
||||
row: 69
|
||||
column: 5
|
||||
end_location:
|
||||
row: 69
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind: NonEmpty
|
||||
location:
|
||||
row: 75
|
||||
column: 9
|
||||
end_location:
|
||||
row: 75
|
||||
column: 11
|
||||
fix: ~
|
||||
|
||||
@@ -4,10 +4,10 @@ expression: checks
|
||||
---
|
||||
- kind: LateFutureImport
|
||||
location:
|
||||
row: 7
|
||||
row: 6
|
||||
column: 1
|
||||
end_location:
|
||||
row: 7
|
||||
row: 6
|
||||
column: 38
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ expression: checks
|
||||
column: 18
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedNOQA: E501
|
||||
UnusedNOQA:
|
||||
- E501
|
||||
location:
|
||||
row: 13
|
||||
column: 10
|
||||
@@ -37,7 +38,9 @@ expression: checks
|
||||
column: 24
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedNOQA: E501
|
||||
UnusedNOQA:
|
||||
- F841
|
||||
- E501
|
||||
location:
|
||||
row: 16
|
||||
column: 10
|
||||
@@ -45,7 +48,7 @@ expression: checks
|
||||
row: 16
|
||||
column: 30
|
||||
fix:
|
||||
content: " # noqa: F841"
|
||||
content: ""
|
||||
location:
|
||||
row: 16
|
||||
column: 10
|
||||
@@ -54,54 +57,74 @@ expression: checks
|
||||
column: 30
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedNOQA: F841
|
||||
UnusedNOQA:
|
||||
- W191
|
||||
location:
|
||||
row: 41
|
||||
row: 19
|
||||
column: 10
|
||||
end_location:
|
||||
row: 19
|
||||
column: 30
|
||||
fix:
|
||||
content: " # noqa: F841"
|
||||
location:
|
||||
row: 19
|
||||
column: 10
|
||||
end_location:
|
||||
row: 19
|
||||
column: 30
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedNOQA:
|
||||
- F841
|
||||
location:
|
||||
row: 44
|
||||
column: 4
|
||||
end_location:
|
||||
row: 41
|
||||
row: 44
|
||||
column: 24
|
||||
fix:
|
||||
content: " # noqa: E501"
|
||||
location:
|
||||
row: 41
|
||||
row: 44
|
||||
column: 4
|
||||
end_location:
|
||||
row: 41
|
||||
row: 44
|
||||
column: 24
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedNOQA: E501
|
||||
UnusedNOQA:
|
||||
- E501
|
||||
location:
|
||||
row: 49
|
||||
row: 52
|
||||
column: 4
|
||||
end_location:
|
||||
row: 49
|
||||
row: 52
|
||||
column: 18
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 49
|
||||
row: 52
|
||||
column: 4
|
||||
end_location:
|
||||
row: 49
|
||||
row: 52
|
||||
column: 18
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedNOQA: ~
|
||||
location:
|
||||
row: 57
|
||||
row: 60
|
||||
column: 4
|
||||
end_location:
|
||||
row: 57
|
||||
row: 60
|
||||
column: 12
|
||||
fix:
|
||||
content: ""
|
||||
location:
|
||||
row: 57
|
||||
row: 60
|
||||
column: 4
|
||||
end_location:
|
||||
row: 57
|
||||
row: 60
|
||||
column: 12
|
||||
applied: false
|
||||
|
||||
|
||||
158
src/visibility.rs
Normal file
158
src/visibility.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
//! Abstractions for tracking public and private visibility across modules, classes, and functions.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use rustpython_ast::{Stmt, StmtKind};
|
||||
|
||||
use crate::ast::helpers::match_name_or_attr;
|
||||
use crate::docstrings::types::Documentable;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Modifier {
|
||||
Module,
|
||||
Class,
|
||||
Function,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Visibility {
|
||||
Public,
|
||||
Private,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VisibleScope {
|
||||
pub modifier: Modifier,
|
||||
pub visibility: Visibility,
|
||||
}
|
||||
|
||||
/// Returns `true` if a function definition is an `@overload`.
|
||||
pub fn is_overload(stmt: &Stmt) -> bool {
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { decorator_list, .. }
|
||||
| StmtKind::AsyncFunctionDef { decorator_list, .. } => decorator_list
|
||||
.iter()
|
||||
.any(|expr| match_name_or_attr(expr, "overload")),
|
||||
_ => panic!("Found non-FunctionDef in is_overload"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if a function is a "magic method".
|
||||
pub fn is_magic(stmt: &Stmt) -> bool {
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { name, .. } | StmtKind::AsyncFunctionDef { name, .. } => {
|
||||
name.starts_with("__")
|
||||
&& name.ends_with("__")
|
||||
&& name != "__init__"
|
||||
&& name != "__call__"
|
||||
&& name != "__new__"
|
||||
}
|
||||
_ => panic!("Found non-FunctionDef in is_magic"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if a function is an `__init__`.
|
||||
pub fn is_init(stmt: &Stmt) -> bool {
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { name, .. } | StmtKind::AsyncFunctionDef { name, .. } => {
|
||||
name == "__init__"
|
||||
}
|
||||
_ => panic!("Found non-FunctionDef in is_init"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if a module name indicates private visibility.
|
||||
fn is_private_module(module_name: &str) -> bool {
|
||||
module_name.starts_with('_') || (module_name.starts_with("__") && module_name.ends_with("__"))
|
||||
}
|
||||
|
||||
pub fn module_visibility(path: &Path) -> Visibility {
|
||||
for component in path.iter().rev() {
|
||||
if is_private_module(&component.to_string_lossy()) {
|
||||
return Visibility::Private;
|
||||
}
|
||||
}
|
||||
Visibility::Public
|
||||
}
|
||||
|
||||
fn function_visibility(stmt: &Stmt) -> Visibility {
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { name, .. } | StmtKind::AsyncFunctionDef { name, .. } => {
|
||||
if name.starts_with('_') {
|
||||
Visibility::Private
|
||||
} else {
|
||||
Visibility::Public
|
||||
}
|
||||
}
|
||||
_ => panic!("Found non-FunctionDef in function_visibility"),
|
||||
}
|
||||
}
|
||||
|
||||
fn method_visibility(stmt: &Stmt) -> Visibility {
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { name, .. } | StmtKind::AsyncFunctionDef { name, .. } => {
|
||||
// Is the method non-private?
|
||||
if !name.starts_with('_') {
|
||||
return Visibility::Public;
|
||||
}
|
||||
|
||||
// Is this a magic method?
|
||||
if name.starts_with("__") && name.ends_with("__") {
|
||||
return Visibility::Public;
|
||||
}
|
||||
|
||||
Visibility::Private
|
||||
}
|
||||
_ => panic!("Found non-FunctionDef in method_visibility"),
|
||||
}
|
||||
}
|
||||
|
||||
fn class_visibility(stmt: &Stmt) -> Visibility {
|
||||
match &stmt.node {
|
||||
StmtKind::ClassDef { name, .. } => {
|
||||
if name.starts_with('_') {
|
||||
Visibility::Private
|
||||
} else {
|
||||
Visibility::Public
|
||||
}
|
||||
}
|
||||
_ => panic!("Found non-ClassDef in function_visibility"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Transition a `VisibleScope` based on a new `Documentable` definition.
|
||||
///
|
||||
/// `scope` is the current `VisibleScope`, while `Documentable` and `Stmt` describe the current
|
||||
/// node used to modify visibility.
|
||||
pub fn transition_scope(scope: &VisibleScope, stmt: &Stmt, kind: &Documentable) -> VisibleScope {
|
||||
match kind {
|
||||
Documentable::Function => VisibleScope {
|
||||
modifier: Modifier::Function,
|
||||
visibility: match scope {
|
||||
VisibleScope {
|
||||
modifier: Modifier::Module,
|
||||
visibility: Visibility::Public,
|
||||
} => function_visibility(stmt),
|
||||
VisibleScope {
|
||||
modifier: Modifier::Class,
|
||||
visibility: Visibility::Public,
|
||||
} => method_visibility(stmt),
|
||||
_ => Visibility::Private,
|
||||
},
|
||||
},
|
||||
Documentable::Class => VisibleScope {
|
||||
modifier: Modifier::Class,
|
||||
visibility: match scope {
|
||||
VisibleScope {
|
||||
modifier: Modifier::Module,
|
||||
visibility: Visibility::Public,
|
||||
} => class_visibility(stmt),
|
||||
VisibleScope {
|
||||
modifier: Modifier::Class,
|
||||
visibility: Visibility::Public,
|
||||
} => class_visibility(stmt),
|
||||
_ => Visibility::Private,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
47
tests/integration_test.rs
Normal file
47
tests/integration_test.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use std::str;
|
||||
|
||||
use anyhow::Result;
|
||||
use assert_cmd::{crate_name, Command};
|
||||
|
||||
#[test]
|
||||
fn test_stdin_success() -> Result<()> {
|
||||
let mut cmd = Command::cargo_bin(crate_name!())?;
|
||||
cmd.args(&["-"]).write_stdin("").assert().success();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stdin_error() -> Result<()> {
|
||||
let mut cmd = Command::cargo_bin(crate_name!())?;
|
||||
let output = cmd
|
||||
.args(&["-"])
|
||||
.write_stdin("import os\n")
|
||||
.assert()
|
||||
.failure();
|
||||
assert!(str::from_utf8(&output.get_output().stdout)?.contains("-:1:1: F401"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stdin_filename() -> Result<()> {
|
||||
let mut cmd = Command::cargo_bin(crate_name!())?;
|
||||
let output = cmd
|
||||
.args(&["-", "--stdin-filename", "F401.py"])
|
||||
.write_stdin("import os\n")
|
||||
.assert()
|
||||
.failure();
|
||||
assert!(str::from_utf8(&output.get_output().stdout)?.contains("F401.py:1:1: F401"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stdin_autofix() -> Result<()> {
|
||||
let mut cmd = Command::cargo_bin(crate_name!())?;
|
||||
let output = cmd
|
||||
.args(&["-", "--fix"])
|
||||
.write_stdin("import os\n")
|
||||
.assert()
|
||||
.failure();
|
||||
assert!(str::from_utf8(&output.get_output().stdout)?.contains("-:1:1: F401"));
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user