Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a710e35ebc | ||
|
|
49df43bb78 | ||
|
|
e338d9acbe | ||
|
|
5c8655f479 | ||
|
|
60987888a2 | ||
|
|
a81581c781 | ||
|
|
3152dd7a8e | ||
|
|
528416f07a | ||
|
|
4405a6a903 | ||
|
|
35fa2a3c32 | ||
|
|
bb67fbb73a | ||
|
|
d698c6123e | ||
|
|
9579faffa8 | ||
|
|
9c6e8c7644 | ||
|
|
7abecd4f0e | ||
|
|
b8ff209af8 | ||
|
|
c5451cd8ad | ||
|
|
92b9ab3010 | ||
|
|
f2ac8c4ec2 | ||
|
|
80e2f0c92e | ||
|
|
ea550abd3c | ||
|
|
5c26777e4c | ||
|
|
6eb6b6eede | ||
|
|
f1d3e3698a | ||
|
|
080411bc89 | ||
|
|
f2ad915224 | ||
|
|
71543eeabc | ||
|
|
44025f1c92 | ||
|
|
971bf6d232 | ||
|
|
0acc47386a | ||
|
|
982ac6b0ad | ||
|
|
541440f7a8 | ||
|
|
66dde46e03 | ||
|
|
1339e2a002 | ||
|
|
38ad10f60d | ||
|
|
88e78c5cde | ||
|
|
436aeed20a | ||
|
|
b94169a8bb |
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.162
|
||||
rev: v0.0.170
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
@@ -105,6 +105,8 @@ You may also want to add the new configuration option to the `flake8-to-ruff` to
|
||||
responsible for converting `flake8` configuration files to Ruff's TOML format. This logic
|
||||
lives in `flake8_to_ruff/src/converter.rs`.
|
||||
|
||||
To update the documentation for supported configuration options, run `cargo dev generate-options`.
|
||||
|
||||
## Release process
|
||||
|
||||
As of now, Ruff has an ad hoc release process: releases are cut with high frequency via GitHub
|
||||
|
||||
17
Cargo.lock
generated
17
Cargo.lock
generated
@@ -724,7 +724,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.162-dev.0"
|
||||
version = "0.0.170-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.29",
|
||||
@@ -1821,7 +1821,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.162"
|
||||
version = "0.0.170"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1855,6 +1855,7 @@ dependencies = [
|
||||
"rayon",
|
||||
"regex",
|
||||
"ropey",
|
||||
"ruff_macros",
|
||||
"rustc-hash",
|
||||
"rustpython-ast",
|
||||
"rustpython-common",
|
||||
@@ -1873,7 +1874,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.162"
|
||||
version = "0.0.170"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.29",
|
||||
@@ -1889,6 +1890,16 @@ dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.170"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"textwrap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
|
||||
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.162"
|
||||
version = "0.0.170"
|
||||
edition = "2021"
|
||||
rust-version = "1.65.0"
|
||||
|
||||
@@ -41,6 +41,7 @@ quick-junit = { version = "0.3.2" }
|
||||
rayon = { version = "1.5.3" }
|
||||
regex = { version = "1.6.0" }
|
||||
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
|
||||
ruff_macros = { version = "0.0.170", path = "ruff_macros" }
|
||||
rustc-hash = { version = "1.1.0" }
|
||||
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
|
||||
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
|
||||
|
||||
50
LICENSE
50
LICENSE
@@ -388,6 +388,56 @@ are:
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- flake8-import-conventions, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 João Palmeiro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- flake8-unused-arguments, licensed as follows:
|
||||
"""
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Nathan Hoad
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
"""
|
||||
|
||||
- isort, licensed as follows:
|
||||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
4
flake8_to_ruff/Cargo.lock
generated
4
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.162"
|
||||
version = "0.0.170"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.162"
|
||||
version = "0.0.170"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.162-dev.0"
|
||||
version = "0.0.170-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -270,6 +270,7 @@ mod tests {
|
||||
flake8_bugbear: None,
|
||||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
@@ -314,6 +315,7 @@ mod tests {
|
||||
flake8_bugbear: None,
|
||||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
@@ -358,6 +360,7 @@ mod tests {
|
||||
flake8_bugbear: None,
|
||||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
@@ -402,6 +405,7 @@ mod tests {
|
||||
flake8_bugbear: None,
|
||||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
@@ -451,6 +455,7 @@ mod tests {
|
||||
avoid_escape: None,
|
||||
}),
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
@@ -533,6 +538,7 @@ mod tests {
|
||||
flake8_bugbear: None,
|
||||
flake8_quotes: None,
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
@@ -583,6 +589,7 @@ mod tests {
|
||||
avoid_escape: None,
|
||||
}),
|
||||
flake8_tidy_imports: None,
|
||||
flake8_import_conventions: None,
|
||||
isort: None,
|
||||
mccabe: None,
|
||||
pep8_naming: None,
|
||||
|
||||
49
resources/test/fixtures/flake8_annotations/allow_overload.py
vendored
Normal file
49
resources/test/fixtures/flake8_annotations/allow_overload.py
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
from typing import overload
|
||||
|
||||
|
||||
@overload
|
||||
def foo(i: int) -> "int":
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def foo(i: "str") -> "str":
|
||||
...
|
||||
|
||||
|
||||
def foo(i):
|
||||
return i
|
||||
|
||||
|
||||
@overload
|
||||
def bar(i: int) -> "int":
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def bar(i: "str") -> "str":
|
||||
...
|
||||
|
||||
|
||||
class X:
|
||||
def bar(i):
|
||||
return i
|
||||
|
||||
|
||||
# TODO(charlie): This third case should raise an error (as in Mypy), because we have a
|
||||
# statement between the interfaces and implementation.
|
||||
@overload
|
||||
def baz(i: int) -> "int":
|
||||
...
|
||||
|
||||
|
||||
@overload
|
||||
def baz(i: "str") -> "str":
|
||||
...
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
def baz(i):
|
||||
return i
|
||||
@@ -53,3 +53,11 @@ try:
|
||||
raise e
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
pass
|
||||
except Exception as e:
|
||||
raise bad
|
||||
except BaseException:
|
||||
pass
|
||||
|
||||
10
resources/test/fixtures/flake8_bugbear/B905.py
vendored
Normal file
10
resources/test/fixtures/flake8_bugbear/B905.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
zip()
|
||||
zip(range(3))
|
||||
zip("a", "b")
|
||||
zip("a", "b", *zip("c"))
|
||||
zip(zip("a"), strict=False)
|
||||
zip(zip("a", strict=True))
|
||||
|
||||
zip(range(3), strict=True)
|
||||
zip("a", "b", strict=False)
|
||||
zip("a", "b", "c", strict=True)
|
||||
25
resources/test/fixtures/flake8_import_conventions/custom.py
vendored
Normal file
25
resources/test/fixtures/flake8_import_conventions/custom.py
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
import math # not checked
|
||||
|
||||
import altair # unconventional
|
||||
import dask.array # unconventional
|
||||
import dask.dataframe # unconventional
|
||||
import matplotlib.pyplot # unconventional
|
||||
import numpy # unconventional
|
||||
import pandas # unconventional
|
||||
import seaborn # unconventional
|
||||
|
||||
import altair as altr # unconventional
|
||||
import matplotlib.pyplot as plot # unconventional
|
||||
import dask.array as darray # unconventional
|
||||
import dask.dataframe as ddf # unconventional
|
||||
import numpy as nmp # unconventional
|
||||
import pandas as pdas # unconventional
|
||||
import seaborn as sbrn # unconventional
|
||||
|
||||
import altair as alt # conventional
|
||||
import dask.array as da # conventional
|
||||
import dask.dataframe as dd # conventional
|
||||
import matplotlib.pyplot as plt # conventional
|
||||
import numpy as np # conventional
|
||||
import pandas as pd # conventional
|
||||
import seaborn as sns # conventional
|
||||
19
resources/test/fixtures/flake8_import_conventions/defaults.py
vendored
Normal file
19
resources/test/fixtures/flake8_import_conventions/defaults.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import math # not checked
|
||||
|
||||
import altair # unconventional
|
||||
import matplotlib.pyplot # unconventional
|
||||
import numpy # unconventional
|
||||
import pandas # unconventional
|
||||
import seaborn # unconventional
|
||||
|
||||
import altair as altr # unconventional
|
||||
import matplotlib.pyplot as plot # unconventional
|
||||
import numpy as nmp # unconventional
|
||||
import pandas as pdas # unconventional
|
||||
import seaborn as sbrn # unconventional
|
||||
|
||||
import altair as alt # conventional
|
||||
import matplotlib.pyplot as plt # conventional
|
||||
import numpy as np # conventional
|
||||
import pandas as pd # conventional
|
||||
import seaborn as sns # conventional
|
||||
19
resources/test/fixtures/flake8_import_conventions/override_default.py
vendored
Normal file
19
resources/test/fixtures/flake8_import_conventions/override_default.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import math # not checked
|
||||
|
||||
import altair # unconventional
|
||||
import matplotlib.pyplot # unconventional
|
||||
import numpy # unconventional
|
||||
import pandas # unconventional
|
||||
import seaborn # unconventional
|
||||
|
||||
import altair as altr # unconventional
|
||||
import matplotlib.pyplot as plot # unconventional
|
||||
import numpy as np # unconventional
|
||||
import pandas as pdas # unconventional
|
||||
import seaborn as sbrn # unconventional
|
||||
|
||||
import altair as alt # conventional
|
||||
import matplotlib.pyplot as plt # conventional
|
||||
import numpy as nmp # conventional
|
||||
import pandas as pd # conventional
|
||||
import seaborn as sns # conventional
|
||||
19
resources/test/fixtures/flake8_import_conventions/remove_default.py
vendored
Normal file
19
resources/test/fixtures/flake8_import_conventions/remove_default.py
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import math # not checked
|
||||
|
||||
import altair # unconventional
|
||||
import matplotlib.pyplot # unconventional
|
||||
import numpy # not checked
|
||||
import pandas # unconventional
|
||||
import seaborn # unconventional
|
||||
|
||||
import altair as altr # unconventional
|
||||
import matplotlib.pyplot as plot # unconventional
|
||||
import numpy as nmp # not checked
|
||||
import pandas as pdas # unconventional
|
||||
import seaborn as sbrn # unconventional
|
||||
|
||||
import altair as alt # conventional
|
||||
import matplotlib.pyplot as plt # conventional
|
||||
import numpy as np # not checked
|
||||
import pandas as pd # conventional
|
||||
import seaborn as sns # conventional
|
||||
137
resources/test/fixtures/flake8_unused_arguments/ARG.py
vendored
Normal file
137
resources/test/fixtures/flake8_unused_arguments/ARG.py
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
from abc import abstractmethod
|
||||
from typing_extensions import override
|
||||
|
||||
|
||||
###
|
||||
# Unused arguments on functions.
|
||||
###
|
||||
def f(self, x):
|
||||
print("Hello, world!")
|
||||
|
||||
|
||||
def f(cls, x):
|
||||
print("Hello, world!")
|
||||
|
||||
|
||||
def f(self, x):
|
||||
...
|
||||
|
||||
|
||||
def f(cls, x):
|
||||
...
|
||||
|
||||
|
||||
###
|
||||
# Unused arguments on lambdas.
|
||||
###
|
||||
lambda x: print("Hello, world!")
|
||||
|
||||
|
||||
class X:
|
||||
###
|
||||
# Unused arguments.
|
||||
###
|
||||
def f(self, x):
|
||||
print("Hello, world!")
|
||||
|
||||
def f(self, /, x):
|
||||
print("Hello, world!")
|
||||
|
||||
def f(cls, x):
|
||||
print("Hello, world!")
|
||||
|
||||
@classmethod
|
||||
def f(cls, x):
|
||||
print("Hello, world!")
|
||||
|
||||
@staticmethod
|
||||
def f(cls, x):
|
||||
print("Hello, world!")
|
||||
|
||||
@staticmethod
|
||||
def f(x):
|
||||
print("Hello, world!")
|
||||
|
||||
###
|
||||
# Unused arguments attached to empty functions (OK).
|
||||
###
|
||||
def f(self, x):
|
||||
...
|
||||
|
||||
def f(self, /, x):
|
||||
...
|
||||
|
||||
def f(cls, x):
|
||||
...
|
||||
|
||||
@classmethod
|
||||
def f(cls, x):
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def f(cls, x):
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def f(x):
|
||||
...
|
||||
|
||||
###
|
||||
# Unused functions attached to abstract methods (OK).
|
||||
###
|
||||
@abstractmethod
|
||||
def f(self, x):
|
||||
print("Hello, world!")
|
||||
|
||||
@abstractmethod
|
||||
def f(self, /, x):
|
||||
print("Hello, world!")
|
||||
|
||||
@abstractmethod
|
||||
def f(cls, x):
|
||||
print("Hello, world!")
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def f(cls, x):
|
||||
print("Hello, world!")
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def f(cls, x):
|
||||
print("Hello, world!")
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def f(x):
|
||||
print("Hello, world!")
|
||||
|
||||
###
|
||||
# Unused functions attached to overrides (OK).
|
||||
###
|
||||
@override
|
||||
def f(self, x):
|
||||
print("Hello, world!")
|
||||
|
||||
@override
|
||||
def f(self, /, x):
|
||||
print("Hello, world!")
|
||||
|
||||
@override
|
||||
def f(cls, x):
|
||||
print("Hello, world!")
|
||||
|
||||
@classmethod
|
||||
@override
|
||||
def f(cls, x):
|
||||
print("Hello, world!")
|
||||
|
||||
@staticmethod
|
||||
@override
|
||||
def f(cls, x):
|
||||
print("Hello, world!")
|
||||
|
||||
@staticmethod
|
||||
@override
|
||||
def f(x):
|
||||
print("Hello, world!")
|
||||
@@ -27,3 +27,15 @@ if True:
|
||||
import os
|
||||
def f():
|
||||
pass
|
||||
|
||||
if True:
|
||||
x = 1
|
||||
import collections
|
||||
import typing
|
||||
class X: pass
|
||||
|
||||
if True:
|
||||
x = 1
|
||||
import collections
|
||||
import typing
|
||||
def f(): pass
|
||||
|
||||
41
resources/test/fixtures/isort/insert_empty_lines.pyi
vendored
Normal file
41
resources/test/fixtures/isort/insert_empty_lines.pyi
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
import a
|
||||
import b
|
||||
x = 1
|
||||
import os
|
||||
import sys
|
||||
def f():
|
||||
pass
|
||||
if True:
|
||||
x = 1
|
||||
import collections
|
||||
import typing
|
||||
class X: pass
|
||||
y = 1
|
||||
import os
|
||||
import sys
|
||||
"""Docstring"""
|
||||
|
||||
if True:
|
||||
import os
|
||||
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
|
||||
if True:
|
||||
import os
|
||||
def f():
|
||||
pass
|
||||
|
||||
if True:
|
||||
x = 1
|
||||
import collections
|
||||
import typing
|
||||
class X: pass
|
||||
|
||||
if True:
|
||||
x = 1
|
||||
import collections
|
||||
import typing
|
||||
def f(): pass
|
||||
8
resources/test/fixtures/pydocstyle/D.py
vendored
8
resources/test/fixtures/pydocstyle/D.py
vendored
@@ -571,3 +571,11 @@ def multiline_trailing_and_leading_space():
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@expect('D210: No whitespaces allowed surrounding docstring text')
|
||||
@expect("D400: First line should end with a period (not '\"')")
|
||||
@expect("D415: First line should end with a period, question mark, "
|
||||
"or exclamation point (not '\"')")
|
||||
def endswith_quote():
|
||||
"""Whitespace at the end, but also a quote" """
|
||||
|
||||
73
resources/test/fixtures/pydocstyle/D400.py
vendored
Normal file
73
resources/test/fixtures/pydocstyle/D400.py
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
def f():
|
||||
"Here's a line without a period"
|
||||
...
|
||||
|
||||
|
||||
def f():
|
||||
"""Here's a line without a period"""
|
||||
...
|
||||
|
||||
|
||||
def f():
|
||||
"""
|
||||
Here's a line without a period,
|
||||
but here's the next line
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
def f():
|
||||
"""Here's a line without a period"""
|
||||
...
|
||||
|
||||
|
||||
def f():
|
||||
"""
|
||||
Here's a line without a period,
|
||||
but here's the next line"""
|
||||
...
|
||||
|
||||
|
||||
def f():
|
||||
"""
|
||||
Here's a line without a period,
|
||||
but here's the next line with trailing space """
|
||||
...
|
||||
|
||||
|
||||
|
||||
def f():
|
||||
r"Here's a line without a period"
|
||||
...
|
||||
|
||||
|
||||
def f():
|
||||
r"""Here's a line without a period"""
|
||||
...
|
||||
|
||||
|
||||
def f():
|
||||
r"""
|
||||
Here's a line without a period,
|
||||
but here's the next line
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
def f():
|
||||
r"""Here's a line without a period"""
|
||||
...
|
||||
|
||||
|
||||
def f():
|
||||
r"""
|
||||
Here's a line without a period,
|
||||
but here's the next line"""
|
||||
...
|
||||
|
||||
|
||||
def f():
|
||||
r"""
|
||||
Here's a line without a period,
|
||||
but here's the next line with trailing space """
|
||||
...
|
||||
@@ -65,3 +65,8 @@ def f7():
|
||||
|
||||
with connect() as (connection, cursor):
|
||||
cursor.execute("SELECT * FROM users")
|
||||
|
||||
|
||||
def f8():
|
||||
with open("file") as f, open("") as ((a, b)):
|
||||
print("hello")
|
||||
24
resources/test/fixtures/pyflakes/F841_1.py
vendored
Normal file
24
resources/test/fixtures/pyflakes/F841_1.py
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
def f(tup):
|
||||
x, y = tup # this does NOT trigger F841
|
||||
|
||||
|
||||
def f():
|
||||
x, y = 1, 2 # this triggers F841 as it's just a simple assignment where unpacking isn't needed
|
||||
|
||||
|
||||
def f():
|
||||
(x, y) = coords = 1, 2 # this does NOT trigger F841
|
||||
if x > 1:
|
||||
print(coords)
|
||||
|
||||
|
||||
def f():
|
||||
(x, y) = coords = 1, 2 # this triggers F841 on coords
|
||||
|
||||
|
||||
def f():
|
||||
coords = (x, y) = 1, 2 # this triggers F841 on coords
|
||||
|
||||
|
||||
def f():
|
||||
(a, b) = (x, y) = 1, 2 # this triggers F841 on everything
|
||||
6
resources/test/fixtures/pyproject.toml
vendored
6
resources/test/fixtures/pyproject.toml
vendored
@@ -41,3 +41,9 @@ staticmethod-decorators = ["staticmethod"]
|
||||
|
||||
[tool.ruff.flake8-tidy-imports]
|
||||
ban-relative-imports = "parents"
|
||||
|
||||
[tool.ruff.flake8-import-conventions.aliases]
|
||||
pandas = "pd"
|
||||
|
||||
[tool.ruff.flake8-import-conventions.extend-aliases]
|
||||
"dask.dataframe" = "dd"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.162"
|
||||
version = "0.0.170"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -8,7 +8,7 @@ use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use codegen::{Scope, Type, Variant};
|
||||
use itertools::Itertools;
|
||||
use ruff::checks::{CheckCode, REDIRECTS};
|
||||
use ruff::checks::{CheckCode, CODE_REDIRECTS, PREFIX_REDIRECTS};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
const FILE: &str = "src/checks_gen.rs";
|
||||
@@ -39,34 +39,26 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Add any aliases (e.g., "U001" to "UP001").
|
||||
for (alias, check_code) in REDIRECTS.iter() {
|
||||
// Compute the length of the prefix and suffix for both codes.
|
||||
let code_str: String = check_code.as_ref().to_string();
|
||||
let code_prefix_len = code_str
|
||||
.chars()
|
||||
.take_while(|char| char.is_alphabetic())
|
||||
.count();
|
||||
let code_suffix_len = code_str.len() - code_prefix_len;
|
||||
let alias_prefix_len = alias
|
||||
.chars()
|
||||
.take_while(|char| char.is_alphabetic())
|
||||
.count();
|
||||
let alias_suffix_len = alias.len() - alias_prefix_len;
|
||||
assert_eq!(code_suffix_len, alias_suffix_len);
|
||||
for i in 0..=code_suffix_len {
|
||||
let source = code_str[..code_prefix_len + i].to_string();
|
||||
let destination = alias[..alias_prefix_len + i].to_string();
|
||||
if source != destination {
|
||||
prefix_to_codes.insert(
|
||||
destination,
|
||||
prefix_to_codes
|
||||
.get(&source)
|
||||
.unwrap_or_else(|| panic!("Unknown CheckCode: {source:?}"))
|
||||
.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Add any prefix aliases (e.g., "U" to "UP").
|
||||
for (alias, source) in PREFIX_REDIRECTS.iter() {
|
||||
prefix_to_codes.insert(
|
||||
(*alias).to_string(),
|
||||
prefix_to_codes
|
||||
.get(&(*source).to_string())
|
||||
.unwrap_or_else(|| panic!("Unknown CheckCode: {source:?}"))
|
||||
.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
// Add any check code aliases (e.g., "U001" to "UP001").
|
||||
for (alias, check_code) in CODE_REDIRECTS.iter() {
|
||||
prefix_to_codes.insert(
|
||||
(*alias).to_string(),
|
||||
prefix_to_codes
|
||||
.get(&check_code.as_ref().to_string())
|
||||
.unwrap_or_else(|| panic!("Unknown CheckCode: {alias:?}"))
|
||||
.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut scope = Scope::new();
|
||||
@@ -76,6 +68,7 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
.new_enum("CheckCodePrefix")
|
||||
.vis("pub")
|
||||
.derive("EnumString")
|
||||
.derive("AsRefStr")
|
||||
.derive("Debug")
|
||||
.derive("PartialEq")
|
||||
.derive("Eq")
|
||||
@@ -112,10 +105,10 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
.line("#[allow(clippy::match_same_arms)]")
|
||||
.line("match self {");
|
||||
for (prefix, codes) in &prefix_to_codes {
|
||||
if let Some(target) = REDIRECTS.get(&prefix.as_str()) {
|
||||
if let Some(target) = CODE_REDIRECTS.get(&prefix.as_str()) {
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => {{ eprintln!(\"{{}}{{}} {{}}\", \
|
||||
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been renamed to \
|
||||
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been remapped to \
|
||||
`{}`\".bold()); \n vec![{}] }}",
|
||||
prefix,
|
||||
target.as_ref(),
|
||||
@@ -124,6 +117,18 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
.map(|code| format!("CheckCode::{}", code.as_ref()))
|
||||
.join(", ")
|
||||
));
|
||||
} else if let Some(target) = PREFIX_REDIRECTS.get(&prefix.as_str()) {
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => {{ eprintln!(\"{{}}{{}} {{}}\", \
|
||||
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been remapped to \
|
||||
`{}`\".bold()); \n vec![{}] }}",
|
||||
prefix,
|
||||
target,
|
||||
codes
|
||||
.iter()
|
||||
.map(|code| format!("CheckCode::{}", code.as_ref()))
|
||||
.join(", ")
|
||||
));
|
||||
} else {
|
||||
gen = gen.line(format!(
|
||||
"CheckCodePrefix::{prefix} => vec![{}],",
|
||||
@@ -170,9 +175,9 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
output.push('\n');
|
||||
output.push_str("use colored::Colorize;");
|
||||
output.push('\n');
|
||||
output.push_str("use serde::{{Serialize, Deserialize}};");
|
||||
output.push_str("use serde::{Deserialize, Serialize};");
|
||||
output.push('\n');
|
||||
output.push_str("use strum_macros::EnumString;");
|
||||
output.push_str("use strum_macros::{AsRefStr, EnumString};");
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
output.push_str("use crate::checks::CheckCode;");
|
||||
@@ -186,7 +191,9 @@ pub fn main(cli: &Cli) -> Result<()> {
|
||||
output.push_str("pub const CATEGORIES: &[CheckCodePrefix] = &[");
|
||||
output.push('\n');
|
||||
for prefix in prefix_to_codes.keys() {
|
||||
if prefix.chars().all(char::is_alphabetic) {
|
||||
if prefix.chars().all(char::is_alphabetic)
|
||||
&& !PREFIX_REDIRECTS.contains_key(&prefix.as_str())
|
||||
{
|
||||
output.push_str(&format!("CheckCodePrefix::{prefix},"));
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
125
ruff_dev/src/generate_options.rs
Normal file
125
ruff_dev/src/generate_options.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
//! Generate a Markdown-compatible listing of configuration options.
|
||||
|
||||
use std::fs;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use itertools::Itertools;
|
||||
use ruff::settings::options::Options;
|
||||
use ruff::settings::options_base::{ConfigurationOptions, OptionEntry, OptionField};
|
||||
|
||||
const BEGIN_PRAGMA: &str = "<!-- Begin auto-generated options sections. -->";
|
||||
const END_PRAGMA: &str = "<!-- End auto-generated options sections. -->";
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct Cli {
|
||||
/// Write the generated table to stdout (rather than to `README.md`).
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
}
|
||||
|
||||
fn emit_field(output: &mut String, field: &OptionField, group_name: Option<&str>) {
|
||||
output.push_str(&format!("#### [`{0}`](#{0})\n", field.name));
|
||||
output.push('\n');
|
||||
output.push_str(field.doc);
|
||||
output.push_str("\n\n");
|
||||
output.push_str(&format!("**Default value**: `{}`\n", field.default));
|
||||
output.push('\n');
|
||||
output.push_str(&format!("**Type**: `{}`\n", field.value_type));
|
||||
output.push('\n');
|
||||
output.push_str(&format!(
|
||||
"**Example usage**:\n\n```toml\n[tool.ruff{}]\n{}\n```\n",
|
||||
if group_name.is_some() {
|
||||
format!(".{}", group_name.unwrap())
|
||||
} else {
|
||||
String::new()
|
||||
},
|
||||
field.example
|
||||
));
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
let mut output = String::new();
|
||||
|
||||
// Generate all the top-level fields.
|
||||
for field in Options::get_available_options()
|
||||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
if let OptionEntry::Field(field) = entry {
|
||||
Some(field)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.sorted_by_key(|field| field.name)
|
||||
{
|
||||
emit_field(&mut output, &field, None);
|
||||
output.push_str("---\n\n");
|
||||
}
|
||||
|
||||
// Generate all the sub-groups.
|
||||
for group in Options::get_available_options()
|
||||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
if let OptionEntry::Group(group) = entry {
|
||||
Some(group)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.sorted_by_key(|group| group.name)
|
||||
{
|
||||
output.push_str(&format!("### `{}`\n", group.name));
|
||||
output.push('\n');
|
||||
for field in group
|
||||
.fields
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
if let OptionEntry::Field(field) = entry {
|
||||
Some(field)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.sorted_by_key(|field| field.name)
|
||||
{
|
||||
emit_field(&mut output, field, Some(group.name));
|
||||
output.push_str("---\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
if cli.dry_run {
|
||||
print!("{output}");
|
||||
} else {
|
||||
// Read the existing file.
|
||||
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.expect("Failed to find root directory")
|
||||
.join("README.md");
|
||||
let existing = fs::read_to_string(&file)?;
|
||||
|
||||
// Extract the prefix.
|
||||
let index = existing
|
||||
.find(BEGIN_PRAGMA)
|
||||
.expect("Unable to find begin pragma");
|
||||
let prefix = &existing[..index + BEGIN_PRAGMA.len()];
|
||||
|
||||
// Extract the suffix.
|
||||
let index = existing
|
||||
.find(END_PRAGMA)
|
||||
.expect("Unable to find end pragma");
|
||||
let suffix = &existing[index..];
|
||||
|
||||
// Write the prefix, new contents, and suffix.
|
||||
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
|
||||
write!(f, "{prefix}\n\n")?;
|
||||
write!(f, "{output}")?;
|
||||
write!(f, "{suffix}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -7,11 +7,15 @@ use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Args;
|
||||
use itertools::Itertools;
|
||||
use ruff::checks::{CheckCategory, CheckCode};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
const BEGIN_PRAGMA: &str = "<!-- Begin auto-generated sections. -->";
|
||||
const END_PRAGMA: &str = "<!-- End auto-generated sections. -->";
|
||||
const TABLE_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated sections. -->";
|
||||
const TABLE_END_PRAGMA: &str = "<!-- End auto-generated sections. -->";
|
||||
|
||||
const TOC_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated table of contents. -->";
|
||||
const TOC_END_PRAGMA: &str = "<!-- End auto-generated table of contents. -->";
|
||||
|
||||
#[derive(Args)]
|
||||
pub struct Cli {
|
||||
@@ -22,73 +26,91 @@ pub struct Cli {
|
||||
|
||||
pub fn main(cli: &Cli) -> Result<()> {
|
||||
// Generate the table string.
|
||||
let mut output = String::new();
|
||||
let mut table_out = String::new();
|
||||
let mut toc_out = String::new();
|
||||
for check_category in CheckCategory::iter() {
|
||||
output.push_str(&format!("### {}", check_category.title()));
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
let codes_csv: String = check_category.codes().iter().map(AsRef::as_ref).join(", ");
|
||||
table_out.push_str(&format!("### {} ({codes_csv})", check_category.title()));
|
||||
table_out.push('\n');
|
||||
table_out.push('\n');
|
||||
|
||||
toc_out.push_str(&format!(
|
||||
" 1. [{} ({})](#{}-{})\n",
|
||||
check_category.title(),
|
||||
codes_csv,
|
||||
check_category.title().to_lowercase().replace(' ', "-"),
|
||||
codes_csv.to_lowercase().replace(',', "-").replace(' ', "")
|
||||
));
|
||||
|
||||
if let Some((url, platform)) = check_category.url() {
|
||||
output.push_str(&format!(
|
||||
table_out.push_str(&format!(
|
||||
"For more, see [{}]({}) on {}.",
|
||||
check_category.title(),
|
||||
url,
|
||||
platform
|
||||
));
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
table_out.push('\n');
|
||||
table_out.push('\n');
|
||||
}
|
||||
|
||||
output.push_str("| Code | Name | Message | Fix |");
|
||||
output.push('\n');
|
||||
output.push_str("| ---- | ---- | ------- | --- |");
|
||||
output.push('\n');
|
||||
table_out.push_str("| Code | Name | Message | Fix |");
|
||||
table_out.push('\n');
|
||||
table_out.push_str("| ---- | ---- | ------- | --- |");
|
||||
table_out.push('\n');
|
||||
|
||||
for check_code in CheckCode::iter() {
|
||||
if check_code.category() == check_category {
|
||||
let check_kind = check_code.kind();
|
||||
let fix_token = if check_kind.fixable() { "🛠" } else { "" };
|
||||
output.push_str(&format!(
|
||||
table_out.push_str(&format!(
|
||||
"| {} | {} | {} | {} |",
|
||||
check_kind.code().as_ref(),
|
||||
check_kind.as_ref(),
|
||||
check_kind.summary().replace('|', r"\|"),
|
||||
fix_token
|
||||
));
|
||||
output.push('\n');
|
||||
table_out.push('\n');
|
||||
}
|
||||
}
|
||||
output.push('\n');
|
||||
table_out.push('\n');
|
||||
}
|
||||
|
||||
if cli.dry_run {
|
||||
print!("{output}");
|
||||
print!("Table of Contents: {toc_out}\n Rules Tables: {table_out}");
|
||||
} else {
|
||||
// Read the existing file.
|
||||
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.expect("Failed to find root directory")
|
||||
.join("README.md");
|
||||
let existing = fs::read_to_string(&file)?;
|
||||
|
||||
// Extract the prefix.
|
||||
let index = existing
|
||||
.find(BEGIN_PRAGMA)
|
||||
.expect("Unable to find begin pragma");
|
||||
let prefix = &existing[..index + BEGIN_PRAGMA.len()];
|
||||
|
||||
// Extract the suffix.
|
||||
let index = existing
|
||||
.find(END_PRAGMA)
|
||||
.expect("Unable to find end pragma");
|
||||
let suffix = &existing[index..];
|
||||
|
||||
// Write the prefix, new contents, and suffix.
|
||||
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
|
||||
write!(f, "{prefix}\n\n")?;
|
||||
write!(f, "{output}")?;
|
||||
write!(f, "{suffix}")?;
|
||||
// Extra newline in the markdown numbered list looks weird
|
||||
replace_readme_section(toc_out.trim_end(), TOC_BEGIN_PRAGMA, TOC_END_PRAGMA)?;
|
||||
replace_readme_section(&table_out, TABLE_BEGIN_PRAGMA, TABLE_END_PRAGMA)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn replace_readme_section(content: &str, begin_pragma: &str, end_pragma: &str) -> Result<()> {
|
||||
// Read the existing file.
|
||||
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.expect("Failed to find root directory")
|
||||
.join("README.md");
|
||||
let existing = fs::read_to_string(&file)?;
|
||||
|
||||
// Extract the prefix.
|
||||
let index = existing
|
||||
.find(begin_pragma)
|
||||
.expect("Unable to find begin pragma");
|
||||
let prefix = &existing[..index + begin_pragma.len()];
|
||||
|
||||
// Extract the suffix.
|
||||
let index = existing
|
||||
.find(end_pragma)
|
||||
.expect("Unable to find end pragma");
|
||||
let suffix = &existing[index..];
|
||||
|
||||
// Write the prefix, new contents, and suffix.
|
||||
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
|
||||
writeln!(f, "{prefix}")?;
|
||||
write!(f, "{content}")?;
|
||||
write!(f, "{suffix}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
)]
|
||||
|
||||
pub mod generate_check_code_prefix;
|
||||
pub mod generate_options;
|
||||
pub mod generate_rules_table;
|
||||
pub mod generate_source_code;
|
||||
pub mod print_ast;
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use ruff_dev::{
|
||||
generate_check_code_prefix, generate_rules_table, generate_source_code, print_ast, print_cst,
|
||||
print_tokens,
|
||||
generate_check_code_prefix, generate_options, generate_rules_table, generate_source_code,
|
||||
print_ast, print_cst, print_tokens,
|
||||
};
|
||||
|
||||
#[derive(Parser)]
|
||||
@@ -32,6 +32,8 @@ enum Commands {
|
||||
GenerateCheckCodePrefix(generate_check_code_prefix::Cli),
|
||||
/// Generate a Markdown-compatible table of supported lint rules.
|
||||
GenerateRulesTable(generate_rules_table::Cli),
|
||||
/// Generate a Markdown-compatible listing of configuration options.
|
||||
GenerateOptions(generate_options::Cli),
|
||||
/// Run round-trip source code generation on a given Python file.
|
||||
GenerateSourceCode(generate_source_code::Cli),
|
||||
/// Print the AST for a given Python file.
|
||||
@@ -48,6 +50,7 @@ fn main() -> Result<()> {
|
||||
Commands::GenerateCheckCodePrefix(args) => generate_check_code_prefix::main(args)?,
|
||||
Commands::GenerateRulesTable(args) => generate_rules_table::main(args)?,
|
||||
Commands::GenerateSourceCode(args) => generate_source_code::main(args)?,
|
||||
Commands::GenerateOptions(args) => generate_options::main(args)?,
|
||||
Commands::PrintAST(args) => print_ast::main(args)?,
|
||||
Commands::PrintCST(args) => print_cst::main(args)?,
|
||||
Commands::PrintTokens(args) => print_tokens::main(args)?,
|
||||
|
||||
13
ruff_macros/Cargo.toml
Normal file
13
ruff_macros/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.170"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = { version = "1.0.47" }
|
||||
quote = { version = "1.0.21" }
|
||||
syn = { version = "1.0.103", features = ["derive", "parsing"] }
|
||||
textwrap = { version = "0.16.0" }
|
||||
177
ruff_macros/src/lib.rs
Normal file
177
ruff_macros/src/lib.rs
Normal file
@@ -0,0 +1,177 @@
|
||||
#![allow(
|
||||
clippy::collapsible_else_if,
|
||||
clippy::collapsible_if,
|
||||
clippy::implicit_hasher,
|
||||
clippy::match_same_arms,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::module_name_repetitions,
|
||||
clippy::must_use_candidate,
|
||||
clippy::similar_names,
|
||||
clippy::too_many_lines
|
||||
)]
|
||||
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::token::Comma;
|
||||
use syn::{
|
||||
parse_macro_input, AngleBracketedGenericArguments, Attribute, Data, DataStruct, DeriveInput,
|
||||
Field, Fields, Lit, LitStr, Path, PathArguments, PathSegment, Token, Type, TypePath,
|
||||
};
|
||||
|
||||
#[proc_macro_derive(ConfigurationOptions, attributes(option, option_group))]
|
||||
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
derive_impl(input)
|
||||
.unwrap_or_else(syn::Error::into_compile_error)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
|
||||
let DeriveInput { ident, data, .. } = input;
|
||||
|
||||
match data {
|
||||
Data::Struct(DataStruct {
|
||||
fields: Fields::Named(fields),
|
||||
..
|
||||
}) => {
|
||||
let mut output = vec![];
|
||||
|
||||
for field in fields.named.iter() {
|
||||
if let Some(attr) = field.attrs.iter().find(|a| a.path.is_ident("option")) {
|
||||
output.push(handle_option(field, attr)?);
|
||||
};
|
||||
|
||||
if field.attrs.iter().any(|a| a.path.is_ident("option_group")) {
|
||||
output.push(handle_option_group(field)?);
|
||||
};
|
||||
}
|
||||
|
||||
Ok(quote! {
|
||||
use crate::settings::options_base::{OptionEntry, OptionField, OptionGroup, ConfigurationOptions};
|
||||
|
||||
#[automatically_derived]
|
||||
impl ConfigurationOptions for #ident {
|
||||
fn get_available_options() -> Vec<OptionEntry> {
|
||||
vec![#(#output),*]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
_ => Err(syn::Error::new(
|
||||
ident.span(),
|
||||
"Can only derive ConfigurationOptions from structs with named fields.",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// For a field with type `Option<Foobar>` where `Foobar` itself is a struct
|
||||
/// deriving `ConfigurationOptions`, create code that calls retrieves options
|
||||
/// from that group: `Foobar::get_available_options()`
|
||||
fn handle_option_group(field: &Field) -> syn::Result<proc_macro2::TokenStream> {
|
||||
// unwrap is safe because we're only going over named fields
|
||||
let ident = field.ident.as_ref().unwrap();
|
||||
|
||||
match &field.ty {
|
||||
Type::Path(TypePath {
|
||||
path: Path { segments, .. },
|
||||
..
|
||||
}) => match segments.first() {
|
||||
Some(PathSegment {
|
||||
ident: type_ident,
|
||||
arguments:
|
||||
PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }),
|
||||
..
|
||||
}) if type_ident == "Option" => {
|
||||
let path = &args[0];
|
||||
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
|
||||
|
||||
Ok(quote_spanned!(
|
||||
ident.span() => OptionEntry::Group(OptionGroup {
|
||||
name: #kebab_name,
|
||||
fields: #path::get_available_options(),
|
||||
})
|
||||
))
|
||||
}
|
||||
_ => Err(syn::Error::new(
|
||||
ident.span(),
|
||||
"Expected `Option<_>` as type.",
|
||||
)),
|
||||
},
|
||||
_ => Err(syn::Error::new(ident.span(), "Expected type.")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an `#[option(doc="...", default="...", value_type="...",
|
||||
/// example="...")]` attribute and return data in the form of an `OptionField`.
|
||||
fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::TokenStream> {
|
||||
// unwrap is safe because we're only going over named fields
|
||||
let ident = field.ident.as_ref().unwrap();
|
||||
|
||||
let FieldAttributes {
|
||||
doc,
|
||||
default,
|
||||
value_type,
|
||||
example,
|
||||
} = attr.parse_args::<FieldAttributes>()?;
|
||||
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
|
||||
|
||||
Ok(quote_spanned!(
|
||||
ident.span() => OptionEntry::Field(OptionField {
|
||||
name: #kebab_name,
|
||||
doc: &#doc,
|
||||
default: &#default,
|
||||
value_type: &#value_type,
|
||||
example: &#example,
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FieldAttributes {
|
||||
doc: String,
|
||||
default: String,
|
||||
value_type: String,
|
||||
example: String,
|
||||
}
|
||||
|
||||
impl Parse for FieldAttributes {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let doc = _parse_key_value(input, "doc")?;
|
||||
input.parse::<Comma>()?;
|
||||
let default = _parse_key_value(input, "default")?;
|
||||
input.parse::<Comma>()?;
|
||||
let value_type = _parse_key_value(input, "value_type")?;
|
||||
input.parse::<Comma>()?;
|
||||
let example = _parse_key_value(input, "example")?;
|
||||
if !input.is_empty() {
|
||||
input.parse::<Comma>()?;
|
||||
}
|
||||
|
||||
Ok(FieldAttributes {
|
||||
doc: textwrap::dedent(&doc).trim_matches('\n').to_string(),
|
||||
default,
|
||||
value_type,
|
||||
example: textwrap::dedent(&example).trim_matches('\n').to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn _parse_key_value(input: ParseStream, name: &str) -> syn::Result<String> {
|
||||
let ident: proc_macro2::Ident = input.parse()?;
|
||||
if ident != name {
|
||||
return Err(syn::Error::new(
|
||||
ident.span(),
|
||||
format!("Expected `{name}` name"),
|
||||
));
|
||||
}
|
||||
|
||||
input.parse::<Token![=]>()?;
|
||||
let value: Lit = input.parse()?;
|
||||
|
||||
match &value {
|
||||
Lit::Str(v) => Ok(v.value()),
|
||||
_ => Err(syn::Error::new(value.span(), "Expected literal string")),
|
||||
}
|
||||
}
|
||||
9
src/ast/cast.rs
Normal file
9
src/ast/cast.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use rustpython_ast::{Expr, Stmt, StmtKind};
|
||||
|
||||
pub fn decorator_list(stmt: &Stmt) -> &Vec<Expr> {
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { decorator_list, .. }
|
||||
| StmtKind::AsyncFunctionDef { decorator_list, .. } => decorator_list,
|
||||
_ => panic!("Expected StmtKind::FunctionDef | StmtKind::AsyncFunctionDef"),
|
||||
}
|
||||
}
|
||||
65
src/ast/function_type.rs
Normal file
65
src/ast/function_type.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::Expr;
|
||||
|
||||
use crate::ast::helpers::{
|
||||
collect_call_paths, dealias_call_path, match_call_path, to_module_and_member,
|
||||
};
|
||||
use crate::ast::types::{Scope, ScopeKind};
|
||||
|
||||
const CLASS_METHODS: [&str; 3] = ["__new__", "__init_subclass__", "__class_getitem__"];
|
||||
const METACLASS_BASES: [(&str, &str); 2] = [("", "type"), ("abc", "ABCMeta")];
|
||||
|
||||
pub enum FunctionType {
|
||||
Function,
|
||||
Method,
|
||||
ClassMethod,
|
||||
StaticMethod,
|
||||
}
|
||||
|
||||
/// Classify a function based on its scope, name, and decorators.
|
||||
pub fn classify(
|
||||
scope: &Scope,
|
||||
name: &str,
|
||||
decorator_list: &[Expr],
|
||||
from_imports: &FxHashMap<&str, FxHashSet<&str>>,
|
||||
import_aliases: &FxHashMap<&str, &str>,
|
||||
classmethod_decorators: &[String],
|
||||
staticmethod_decorators: &[String],
|
||||
) -> FunctionType {
|
||||
let ScopeKind::Class(scope) = &scope.kind else {
|
||||
return FunctionType::Function;
|
||||
};
|
||||
// Special-case class method, like `__new__`.
|
||||
if CLASS_METHODS.contains(&name)
|
||||
|| scope.bases.iter().any(|expr| {
|
||||
// The class itself extends a known metaclass, so all methods are class methods.
|
||||
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
|
||||
METACLASS_BASES
|
||||
.iter()
|
||||
.any(|(module, member)| match_call_path(&call_path, module, member, from_imports))
|
||||
})
|
||||
|| decorator_list.iter().any(|expr| {
|
||||
// The method is decorated with a class method decorator (like `@classmethod`).
|
||||
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
|
||||
classmethod_decorators.iter().any(|decorator| {
|
||||
let (module, member) = to_module_and_member(decorator);
|
||||
match_call_path(&call_path, module, member, from_imports)
|
||||
})
|
||||
})
|
||||
{
|
||||
FunctionType::ClassMethod
|
||||
} else if decorator_list.iter().any(|expr| {
|
||||
// The method is decorated with a static method decorator (like
|
||||
// `@staticmethod`).
|
||||
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
|
||||
staticmethod_decorators.iter().any(|decorator| {
|
||||
let (module, member) = to_module_and_member(decorator);
|
||||
match_call_path(&call_path, module, member, from_imports)
|
||||
})
|
||||
}) {
|
||||
FunctionType::StaticMethod
|
||||
} else {
|
||||
// It's an instance method.
|
||||
FunctionType::Method
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod cast;
|
||||
pub mod function_type;
|
||||
pub mod helpers;
|
||||
pub mod operations;
|
||||
pub mod relocate;
|
||||
|
||||
@@ -94,23 +94,132 @@ pub fn in_nested_block<'a>(parents: &mut impl Iterator<Item = &'a Stmt>) -> bool
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if a node represents an unpacking assignment.
|
||||
pub fn is_unpacking_assignment(stmt: &Stmt) -> bool {
|
||||
let StmtKind::Assign { targets, value, .. } = &stmt.node else {
|
||||
return false;
|
||||
};
|
||||
if !targets.iter().any(|child| {
|
||||
matches!(
|
||||
child.node,
|
||||
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. }
|
||||
)
|
||||
}) {
|
||||
return false;
|
||||
/// Returns `true` if `parent` contains `child`.
|
||||
fn contains(parent: &Expr, child: &Expr) -> bool {
|
||||
match &parent.node {
|
||||
ExprKind::BoolOp { values, .. } => values.iter().any(|parent| contains(parent, child)),
|
||||
ExprKind::NamedExpr { target, value } => contains(target, child) || contains(value, child),
|
||||
ExprKind::BinOp { left, right, .. } => contains(left, child) || contains(right, child),
|
||||
ExprKind::UnaryOp { operand, .. } => contains(operand, child),
|
||||
ExprKind::Lambda { body, .. } => contains(body, child),
|
||||
ExprKind::IfExp { test, body, orelse } => {
|
||||
contains(test, child) || contains(body, child) || contains(orelse, child)
|
||||
}
|
||||
ExprKind::Dict { keys, values } => keys
|
||||
.iter()
|
||||
.chain(values.iter())
|
||||
.any(|parent| contains(parent, child)),
|
||||
ExprKind::Set { elts } => elts.iter().any(|parent| contains(parent, child)),
|
||||
ExprKind::ListComp { elt, .. } => contains(elt, child),
|
||||
ExprKind::SetComp { elt, .. } => contains(elt, child),
|
||||
ExprKind::DictComp { key, value, .. } => contains(key, child) || contains(value, child),
|
||||
ExprKind::GeneratorExp { elt, .. } => contains(elt, child),
|
||||
ExprKind::Await { value } => contains(value, child),
|
||||
ExprKind::Yield { value } => value.as_ref().map_or(false, |value| contains(value, child)),
|
||||
ExprKind::YieldFrom { value } => contains(value, child),
|
||||
ExprKind::Compare {
|
||||
left, comparators, ..
|
||||
} => contains(left, child) || comparators.iter().any(|parent| contains(parent, child)),
|
||||
ExprKind::Call {
|
||||
func,
|
||||
args,
|
||||
keywords,
|
||||
} => {
|
||||
contains(func, child)
|
||||
|| args.iter().any(|parent| contains(parent, child))
|
||||
|| keywords
|
||||
.iter()
|
||||
.any(|keyword| contains(&keyword.node.value, child))
|
||||
}
|
||||
ExprKind::FormattedValue {
|
||||
value, format_spec, ..
|
||||
} => {
|
||||
contains(value, child)
|
||||
|| format_spec
|
||||
.as_ref()
|
||||
.map_or(false, |value| contains(value, child))
|
||||
}
|
||||
ExprKind::JoinedStr { values } => values.iter().any(|parent| contains(parent, child)),
|
||||
ExprKind::Constant { .. } => false,
|
||||
ExprKind::Attribute { value, .. } => contains(value, child),
|
||||
ExprKind::Subscript { value, slice, .. } => {
|
||||
contains(value, child) || contains(slice, child)
|
||||
}
|
||||
ExprKind::Starred { value, .. } => contains(value, child),
|
||||
ExprKind::Name { .. } => parent == child,
|
||||
ExprKind::List { elts, .. } => elts.iter().any(|parent| contains(parent, child)),
|
||||
ExprKind::Tuple { elts, .. } => elts.iter().any(|parent| contains(parent, child)),
|
||||
ExprKind::Slice { lower, upper, step } => {
|
||||
lower.as_ref().map_or(false, |value| contains(value, child))
|
||||
|| upper.as_ref().map_or(false, |value| contains(value, child))
|
||||
|| step.as_ref().map_or(false, |value| contains(value, child))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a node represents an unpacking assignment.
|
||||
pub fn is_unpacking_assignment(parent: &Stmt, child: &Expr) -> bool {
|
||||
match &parent.node {
|
||||
StmtKind::With { items, .. } => items.iter().any(|item| {
|
||||
if let Some(optional_vars) = &item.optional_vars {
|
||||
if matches!(optional_vars.node, ExprKind::Tuple { .. }) {
|
||||
if contains(optional_vars, child) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}),
|
||||
StmtKind::Assign { targets, value, .. } => {
|
||||
// In `(a, b) = (1, 2)`, `(1, 2)` is the target, and it is a tuple.
|
||||
let value_is_tuple = matches!(
|
||||
&value.node,
|
||||
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. }
|
||||
);
|
||||
// In `(a, b) = coords = (1, 2)`, `(a, b)` and `coords` are the targets, and
|
||||
// `(a, b`) is a tuple. (We use "tuple" as a placeholder for any
|
||||
// unpackable expression.)
|
||||
let targets_are_tuples = targets.iter().all(|item| {
|
||||
matches!(
|
||||
item.node,
|
||||
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. }
|
||||
)
|
||||
});
|
||||
// If we're looking at `a` in `(a, b) = coords = (1, 2)`, then we should
|
||||
// identify that the current expression is in a tuple.
|
||||
let child_in_tuple = targets_are_tuples
|
||||
|| targets.iter().any(|item| {
|
||||
matches!(
|
||||
item.node,
|
||||
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. }
|
||||
) && contains(item, child)
|
||||
});
|
||||
|
||||
// If our child is a tuple, and value is not, it's always an unpacking
|
||||
// expression. Ex) `x, y = tup`
|
||||
if child_in_tuple && !value_is_tuple {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If our child isn't a tuple, but value is, it's never an unpacking expression.
|
||||
// Ex) `coords = (1, 2)`
|
||||
if !child_in_tuple && value_is_tuple {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If our target and the value are both tuples, then it's an unpacking
|
||||
// expression assuming there's at least one non-tuple child.
|
||||
// Ex) Given `(x, y) = coords = 1, 2`, `(x, y)` is considered an unpacking
|
||||
// expression. Ex) Given `(x, y) = (a, b) = 1, 2`, `(x, y)` isn't
|
||||
// considered an unpacking expression.
|
||||
if child_in_tuple && value_is_tuple {
|
||||
return !targets_are_tuples;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
!matches!(
|
||||
&value.node,
|
||||
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. }
|
||||
)
|
||||
}
|
||||
|
||||
pub type LocatedCmpop<U = ()> = Located<Cmpop, U>;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_ast::{Expr, Keyword, Stmt};
|
||||
use rustpython_ast::{Arguments, Expr, Keyword, Stmt};
|
||||
use rustpython_parser::ast::{Located, Location};
|
||||
|
||||
fn id() -> usize {
|
||||
@@ -30,35 +30,49 @@ impl Range {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FunctionScope {
|
||||
#[derive(Debug)]
|
||||
pub struct FunctionDef<'a> {
|
||||
pub name: &'a str,
|
||||
pub args: &'a Arguments,
|
||||
pub body: &'a [Stmt],
|
||||
pub decorator_list: &'a [Expr],
|
||||
// pub returns: Option<&'a Expr>,
|
||||
// pub type_comment: Option<&'a str>,
|
||||
// TODO(charlie): Create AsyncFunctionDef to mirror the AST.
|
||||
pub async_: bool,
|
||||
pub uses_locals: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ClassScope<'a> {
|
||||
#[derive(Debug)]
|
||||
pub struct ClassDef<'a> {
|
||||
pub name: &'a str,
|
||||
pub bases: &'a [Expr],
|
||||
pub keywords: &'a [Keyword],
|
||||
// pub body: &'a [Stmt],
|
||||
pub decorator_list: &'a [Expr],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct Lambda<'a> {
|
||||
pub args: &'a Arguments,
|
||||
pub body: &'a Expr,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ScopeKind<'a> {
|
||||
Class(ClassScope<'a>),
|
||||
Function(FunctionScope),
|
||||
Class(ClassDef<'a>),
|
||||
Function(FunctionDef<'a>),
|
||||
Generator,
|
||||
Module,
|
||||
Arg,
|
||||
Lambda,
|
||||
Lambda(Lambda<'a>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct Scope<'a> {
|
||||
pub id: usize,
|
||||
pub kind: ScopeKind<'a>,
|
||||
pub import_starred: bool,
|
||||
pub uses_locals: bool,
|
||||
pub values: FxHashMap<&'a str, Binding>,
|
||||
}
|
||||
|
||||
@@ -68,6 +82,7 @@ impl<'a> Scope<'a> {
|
||||
id: id(),
|
||||
kind,
|
||||
import_starred: false,
|
||||
uses_locals: false,
|
||||
values: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
25
src/cache.rs
25
src/cache.rs
@@ -139,20 +139,21 @@ pub fn get(
|
||||
return None;
|
||||
};
|
||||
|
||||
if let Ok(encoded) = read_sync(cache_key(path, settings, autofix)) {
|
||||
match bincode::deserialize::<CheckResult>(&encoded[..]) {
|
||||
Ok(CheckResult {
|
||||
metadata: CacheMetadata { mtime },
|
||||
messages,
|
||||
}) => {
|
||||
if FileTime::from_last_modification_time(metadata).unix_seconds() == mtime {
|
||||
return Some(messages);
|
||||
}
|
||||
}
|
||||
Err(e) => error!("Failed to deserialize encoded cache entry: {e:?}"),
|
||||
let encoded = read_sync(cache_key(path, settings, autofix)).ok()?;
|
||||
let (mtime, messages) = match bincode::deserialize::<CheckResult>(&encoded[..]) {
|
||||
Ok(CheckResult {
|
||||
metadata: CacheMetadata { mtime },
|
||||
messages,
|
||||
}) => (mtime, messages),
|
||||
Err(e) => {
|
||||
error!("Failed to deserialize encoded cache entry: {e:?}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if FileTime::from_last_modification_time(metadata).unix_seconds() != mtime {
|
||||
return None;
|
||||
}
|
||||
None
|
||||
Some(messages)
|
||||
}
|
||||
|
||||
/// Set a value in the cache.
|
||||
|
||||
219
src/check_ast.rs
219
src/check_ast.rs
@@ -5,7 +5,6 @@ use std::path::Path;
|
||||
|
||||
use log::error;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::Withitem;
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
|
||||
KeywordData, Operator, Stmt, StmtKind, Suite,
|
||||
@@ -18,9 +17,10 @@ use crate::ast::helpers::{
|
||||
use crate::ast::operations::extract_all_names;
|
||||
use crate::ast::relocate::relocate_expr;
|
||||
use crate::ast::types::{
|
||||
Binding, BindingContext, BindingKind, ClassScope, FunctionScope, Node, Range, Scope, ScopeKind,
|
||||
Binding, BindingContext, BindingKind, ClassDef, FunctionDef, Lambda, Node, Range, Scope,
|
||||
ScopeKind,
|
||||
};
|
||||
use crate::ast::visitor::{walk_excepthandler, walk_withitem, Visitor};
|
||||
use crate::ast::visitor::{walk_excepthandler, Visitor};
|
||||
use crate::ast::{helpers, operations, visitor};
|
||||
use crate::checks::{Check, CheckCode, CheckKind, DeferralKeyword};
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
|
||||
@@ -36,8 +36,9 @@ use crate::visibility::{module_visibility, transition_scope, Modifier, Visibilit
|
||||
use crate::{
|
||||
docstrings, flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except,
|
||||
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_debugger,
|
||||
flake8_print, flake8_return, flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle,
|
||||
pyflakes, pygrep_hooks, pylint, pyupgrade,
|
||||
flake8_import_conventions, flake8_print, flake8_return, flake8_tidy_imports,
|
||||
flake8_unused_arguments, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pygrep_hooks,
|
||||
pylint, pyupgrade,
|
||||
};
|
||||
|
||||
const GLOBAL_SCOPE_INDEX: usize = 0;
|
||||
@@ -72,7 +73,7 @@ pub struct Checker<'a> {
|
||||
deferred_type_definitions: Vec<(&'a Expr, bool, DeferralContext)>,
|
||||
deferred_functions: Vec<(&'a Stmt, DeferralContext, VisibleScope)>,
|
||||
deferred_lambdas: Vec<(&'a Expr, DeferralContext)>,
|
||||
deferred_assignments: Vec<usize>,
|
||||
deferred_assignments: Vec<(usize, DeferralContext)>,
|
||||
// Internal, derivative state.
|
||||
visible_scope: VisibleScope,
|
||||
in_f_string: Option<Range>,
|
||||
@@ -82,7 +83,6 @@ pub struct Checker<'a> {
|
||||
in_deferred_type_definition: bool,
|
||||
in_literal: bool,
|
||||
in_subscript: bool,
|
||||
in_withitem: bool,
|
||||
seen_import_boundary: bool,
|
||||
futures_allowed: bool,
|
||||
annotations_future_enabled: bool,
|
||||
@@ -130,7 +130,6 @@ impl<'a> Checker<'a> {
|
||||
in_deferred_type_definition: false,
|
||||
in_literal: false,
|
||||
in_subscript: false,
|
||||
in_withitem: false,
|
||||
seen_import_boundary: false,
|
||||
futures_allowed: true,
|
||||
annotations_future_enabled: false,
|
||||
@@ -145,15 +144,16 @@ impl<'a> Checker<'a> {
|
||||
// If we're in an f-string, override the location. RustPython doesn't produce
|
||||
// reliable locations for expressions within f-strings, so we use the
|
||||
// span of the f-string itself as a best-effort default.
|
||||
if let Some(range) = self.in_f_string {
|
||||
self.checks.push(Check {
|
||||
let check = if let Some(range) = self.in_f_string {
|
||||
Check {
|
||||
location: range.location,
|
||||
end_location: range.end_location,
|
||||
..check
|
||||
});
|
||||
}
|
||||
} else {
|
||||
self.checks.push(check);
|
||||
}
|
||||
check
|
||||
};
|
||||
self.checks.push(check);
|
||||
}
|
||||
|
||||
/// Add multiple `Check` items to the `Checker`.
|
||||
@@ -582,7 +582,7 @@ where
|
||||
for expr in decorator_list {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
self.push_scope(Scope::new(ScopeKind::Class(ClassScope {
|
||||
self.push_scope(Scope::new(ScopeKind::Class(ClassDef {
|
||||
name,
|
||||
bases,
|
||||
keywords,
|
||||
@@ -673,7 +673,7 @@ where
|
||||
pylint::plugins::useless_import_alias(self, alias);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::PLR0402) {
|
||||
pylint::plugins::consider_using_from_import(self, alias);
|
||||
pylint::plugins::use_from_import(self, alias);
|
||||
}
|
||||
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
@@ -730,6 +730,19 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::ICN001) {
|
||||
if let Some(check) =
|
||||
flake8_import_conventions::checks::check_conventional_import(
|
||||
stmt,
|
||||
&alias.node.name,
|
||||
alias.node.asname.as_deref(),
|
||||
&self.settings.flake8_import_conventions.aliases,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StmtKind::ImportFrom {
|
||||
@@ -891,7 +904,7 @@ where
|
||||
);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::I252) {
|
||||
if self.settings.enabled.contains(&CheckCode::TID252) {
|
||||
if let Some(check) = flake8_tidy_imports::checks::banned_relative_import(
|
||||
stmt,
|
||||
level.as_ref(),
|
||||
@@ -1066,9 +1079,6 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::B013) {
|
||||
flake8_bugbear::plugins::redundant_tuple_in_exception_handler(self, handlers);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::BLE001) {
|
||||
flake8_blind_except::plugins::blind_except(self, handlers);
|
||||
}
|
||||
}
|
||||
StmtKind::Assign { targets, value, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::E731) {
|
||||
@@ -1398,20 +1408,18 @@ where
|
||||
}
|
||||
Ok(summary) => {
|
||||
if self.settings.enabled.contains(&CheckCode::F522) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::string_dot_format_extra_named_arguments(
|
||||
&summary, keywords, location,
|
||||
)
|
||||
if let Some(check) = pyflakes::checks::string_dot_format_extra_named_arguments(
|
||||
&summary, keywords, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::F523) {
|
||||
if let Some(check) =
|
||||
pyflakes::checks::string_dot_format_extra_positional_arguments(
|
||||
&summary, args, location,
|
||||
)
|
||||
if let Some(check) = pyflakes::checks::string_dot_format_extra_positional_arguments(
|
||||
&summary, args, location,
|
||||
)
|
||||
{
|
||||
self.add_check(check);
|
||||
}
|
||||
@@ -1447,16 +1455,15 @@ where
|
||||
if self.settings.enabled.contains(&CheckCode::UP005) {
|
||||
pyupgrade::plugins::deprecated_unittest_alias(self, func);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::UP012) {
|
||||
pyupgrade::plugins::unnecessary_encode_utf8(self, expr, func, args, keywords);
|
||||
}
|
||||
|
||||
// flake8-super
|
||||
if self.settings.enabled.contains(&CheckCode::UP008) {
|
||||
pyupgrade::plugins::super_call_with_parameters(self, expr, func, args);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::UP012) {
|
||||
pyupgrade::plugins::unnecessary_encode_utf8(self, expr, func, args, keywords);
|
||||
}
|
||||
|
||||
// flake8-print
|
||||
if self.settings.enabled.contains(&CheckCode::T201)
|
||||
|| self.settings.enabled.contains(&CheckCode::T203)
|
||||
@@ -1464,6 +1471,7 @@ where
|
||||
flake8_print::plugins::print_call(self, expr, func);
|
||||
}
|
||||
|
||||
// flake8-bugbear
|
||||
if self.settings.enabled.contains(&CheckCode::B004) {
|
||||
flake8_bugbear::plugins::unreliable_callable_check(self, expr, func, args);
|
||||
}
|
||||
@@ -1478,7 +1486,7 @@ where
|
||||
.scope_stack
|
||||
.iter()
|
||||
.rev()
|
||||
.any(|index| matches!(self.scopes[*index].kind, ScopeKind::Lambda))
|
||||
.any(|index| matches!(self.scopes[*index].kind, ScopeKind::Lambda(..)))
|
||||
{
|
||||
flake8_bugbear::plugins::setattr_with_constant(self, expr, func, args);
|
||||
}
|
||||
@@ -1491,6 +1499,15 @@ where
|
||||
self, args, keywords,
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B905)
|
||||
&& self.settings.target_version >= PythonVersion::Py310
|
||||
{
|
||||
flake8_bugbear::plugins::zip_without_explicit_strict(
|
||||
self, expr, func, keywords,
|
||||
);
|
||||
}
|
||||
|
||||
// flake8-bandit
|
||||
if self.settings.enabled.contains(&CheckCode::S102) {
|
||||
if let Some(check) = flake8_bandit::plugins::exec_used(expr, func) {
|
||||
self.add_check(check);
|
||||
@@ -1730,9 +1747,7 @@ where
|
||||
if id == "locals" && matches!(ctx, ExprContext::Load) {
|
||||
let scope = &mut self.scopes
|
||||
[*(self.scope_stack.last().expect("No current scope found"))];
|
||||
if let ScopeKind::Function(inner) = &mut scope.kind {
|
||||
inner.uses_locals = true;
|
||||
}
|
||||
scope.uses_locals = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1758,7 +1773,7 @@ where
|
||||
pylint::plugins::unnecessary_direct_lambda_call(self, expr, func);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::PLR1722) {
|
||||
pylint::plugins::consider_using_sys_exit(self, func);
|
||||
pylint::plugins::use_sys_exit(self, func);
|
||||
}
|
||||
}
|
||||
ExprKind::Dict { keys, .. } => {
|
||||
@@ -2053,7 +2068,7 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Lambda { args, .. } => {
|
||||
ExprKind::Lambda { args, body, .. } => {
|
||||
// Visit the arguments, but avoid the body, which will be deferred.
|
||||
for arg in &args.posonlyargs {
|
||||
if let Some(expr) = &arg.node.annotation {
|
||||
@@ -2086,7 +2101,7 @@ where
|
||||
for expr in &args.defaults {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
self.push_scope(Scope::new(ScopeKind::Lambda));
|
||||
self.push_scope(Scope::new(ScopeKind::Lambda(Lambda { args, body })));
|
||||
}
|
||||
ExprKind::ListComp { elt, generators } | ExprKind::SetComp { elt, generators } => {
|
||||
if self.settings.enabled.contains(&CheckCode::C416) {
|
||||
@@ -2114,7 +2129,7 @@ where
|
||||
}
|
||||
ExprKind::BoolOp { op, values } => {
|
||||
if self.settings.enabled.contains(&CheckCode::PLR1701) {
|
||||
pylint::plugins::consider_merging_isinstance(self, expr, op, values);
|
||||
pylint::plugins::merge_isinstance(self, expr, op, values);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -2327,16 +2342,25 @@ where
|
||||
ExcepthandlerKind::ExceptHandler {
|
||||
type_, name, body, ..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::E722) && type_.is_none() {
|
||||
self.add_check(Check::new(
|
||||
CheckKind::DoNotUseBareExcept,
|
||||
if self.settings.enabled.contains(&CheckCode::E722) {
|
||||
if let Some(check) = pycodestyle::checks::do_not_use_bare_except(
|
||||
type_.as_deref(),
|
||||
body,
|
||||
Range::from_located(excepthandler),
|
||||
));
|
||||
) {
|
||||
self.add_check(check);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B904) {
|
||||
{
|
||||
flake8_bugbear::plugins::raise_without_from_inside_except(self, body);
|
||||
}
|
||||
flake8_bugbear::plugins::raise_without_from_inside_except(self, body);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::BLE001) {
|
||||
flake8_blind_except::plugins::blind_except(
|
||||
self,
|
||||
type_.as_deref(),
|
||||
name.as_ref().map(String::as_str),
|
||||
body,
|
||||
);
|
||||
}
|
||||
match name {
|
||||
Some(name) => {
|
||||
@@ -2485,13 +2509,6 @@ where
|
||||
|
||||
self.check_builtin_arg_shadowing(&arg.node.arg, Range::from_located(arg));
|
||||
}
|
||||
|
||||
fn visit_withitem(&mut self, withitem: &'b Withitem) {
|
||||
let prev_in_withitem = self.in_withitem;
|
||||
self.in_withitem = true;
|
||||
walk_withitem(self, withitem);
|
||||
self.in_withitem = prev_in_withitem;
|
||||
}
|
||||
}
|
||||
|
||||
fn try_mark_used(scope: &mut Scope, scope_id: usize, id: &str, expr: &Expr) -> bool {
|
||||
@@ -2798,7 +2815,7 @@ impl<'a> Checker<'a> {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.in_withitem || operations::is_unpacking_assignment(parent) {
|
||||
if operations::is_unpacking_assignment(parent, expr) {
|
||||
self.add_binding(
|
||||
id,
|
||||
Binding {
|
||||
@@ -2939,27 +2956,44 @@ impl<'a> Checker<'a> {
|
||||
|
||||
fn check_deferred_functions(&mut self) {
|
||||
while let Some((stmt, (scopes, parents), visibility)) = self.deferred_functions.pop() {
|
||||
self.scope_stack = scopes;
|
||||
self.parent_stack = parents;
|
||||
self.scope_stack = scopes.clone();
|
||||
self.parent_stack = parents.clone();
|
||||
self.visible_scope = visibility;
|
||||
self.push_scope(Scope::new(ScopeKind::Function(FunctionScope {
|
||||
async_: matches!(stmt.node, StmtKind::AsyncFunctionDef { .. }),
|
||||
uses_locals: false,
|
||||
})));
|
||||
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { body, args, .. }
|
||||
| StmtKind::AsyncFunctionDef { body, args, .. } => {
|
||||
StmtKind::FunctionDef {
|
||||
name,
|
||||
body,
|
||||
args,
|
||||
decorator_list,
|
||||
..
|
||||
}
|
||||
| StmtKind::AsyncFunctionDef {
|
||||
name,
|
||||
body,
|
||||
args,
|
||||
decorator_list,
|
||||
..
|
||||
} => {
|
||||
self.push_scope(Scope::new(ScopeKind::Function(FunctionDef {
|
||||
name,
|
||||
body,
|
||||
args,
|
||||
decorator_list,
|
||||
async_: matches!(stmt.node, StmtKind::AsyncFunctionDef { .. }),
|
||||
})));
|
||||
self.visit_arguments(args);
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
_ => unreachable!("Expected StmtKind::FunctionDef | StmtKind::AsyncFunctionDef"),
|
||||
}
|
||||
|
||||
self.deferred_assignments
|
||||
.push(*self.scope_stack.last().expect("No current scope found"));
|
||||
self.deferred_assignments.push((
|
||||
*self.scope_stack.last().expect("No current scope found"),
|
||||
(scopes, parents),
|
||||
));
|
||||
|
||||
self.pop_scope();
|
||||
}
|
||||
@@ -2967,25 +3001,29 @@ impl<'a> Checker<'a> {
|
||||
|
||||
fn check_deferred_lambdas(&mut self) {
|
||||
while let Some((expr, (scopes, parents))) = self.deferred_lambdas.pop() {
|
||||
self.scope_stack = scopes;
|
||||
self.parent_stack = parents;
|
||||
self.push_scope(Scope::new(ScopeKind::Lambda));
|
||||
self.scope_stack = scopes.clone();
|
||||
self.parent_stack = parents.clone();
|
||||
|
||||
if let ExprKind::Lambda { args, body } = &expr.node {
|
||||
self.push_scope(Scope::new(ScopeKind::Lambda(Lambda { args, body })));
|
||||
self.visit_arguments(args);
|
||||
self.visit_expr(body);
|
||||
} else {
|
||||
unreachable!("Expected ExprKind::Lambda");
|
||||
}
|
||||
|
||||
self.deferred_assignments
|
||||
.push(*self.scope_stack.last().expect("No current scope found"));
|
||||
self.deferred_assignments.push((
|
||||
*self.scope_stack.last().expect("No current scope found"),
|
||||
(scopes, parents),
|
||||
));
|
||||
|
||||
self.pop_scope();
|
||||
}
|
||||
}
|
||||
|
||||
fn check_deferred_assignments(&mut self) {
|
||||
if self.settings.enabled.contains(&CheckCode::F841) {
|
||||
while let Some(index) = self.deferred_assignments.pop() {
|
||||
while let Some((index, (scopes, _parents))) = self.deferred_assignments.pop() {
|
||||
if self.settings.enabled.contains(&CheckCode::F841) {
|
||||
self.add_checks(
|
||||
pyflakes::checks::unused_variables(
|
||||
&self.scopes[index],
|
||||
@@ -2994,6 +3032,23 @@ impl<'a> Checker<'a> {
|
||||
.into_iter(),
|
||||
);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::ARG001)
|
||||
|| self.settings.enabled.contains(&CheckCode::ARG002)
|
||||
|| self.settings.enabled.contains(&CheckCode::ARG003)
|
||||
|| self.settings.enabled.contains(&CheckCode::ARG004)
|
||||
|| self.settings.enabled.contains(&CheckCode::ARG005)
|
||||
{
|
||||
self.add_checks(
|
||||
flake8_unused_arguments::plugins::unused_arguments(
|
||||
self,
|
||||
&self.scopes[*scopes
|
||||
.last()
|
||||
.expect("Expected parent scope above function scope")],
|
||||
&self.scopes[index],
|
||||
)
|
||||
.into_iter(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3073,7 +3128,7 @@ impl<'a> Checker<'a> {
|
||||
for (name, binding) in &scope.values {
|
||||
let (BindingKind::Importation(_, full_name, context)
|
||||
| BindingKind::SubmoduleImportation(_, full_name, context)
|
||||
| BindingKind::FromImportation(_, full_name, context)) = &binding.kind else { continue };
|
||||
| BindingKind::FromImportation(_, full_name, context)) = &binding.kind else { continue; };
|
||||
|
||||
// Skip used exports from `__all__`
|
||||
if binding.used.is_some()
|
||||
@@ -3142,6 +3197,8 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
fn check_definitions(&mut self) {
|
||||
let mut overloaded_name: Option<String> = None;
|
||||
self.definitions.reverse();
|
||||
while let Some((definition, visibility)) = self.definitions.pop() {
|
||||
// flake8-annotations
|
||||
if self.settings.enabled.contains(&CheckCode::ANN001)
|
||||
@@ -3156,7 +3213,21 @@ impl<'a> Checker<'a> {
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN206)
|
||||
|| self.settings.enabled.contains(&CheckCode::ANN401)
|
||||
{
|
||||
flake8_annotations::plugins::definition(self, &definition, &visibility);
|
||||
// TODO(charlie): This should be even stricter, in that an overload
|
||||
// implementation should come immediately after the overloaded
|
||||
// interfaces, without any AST nodes in between. Right now, we
|
||||
// only error when traversing definition boundaries (functions,
|
||||
// classes, etc.).
|
||||
if !overloaded_name.map_or(false, |overloaded_name| {
|
||||
flake8_annotations::helpers::is_overload_impl(
|
||||
self,
|
||||
&definition,
|
||||
&overloaded_name,
|
||||
)
|
||||
}) {
|
||||
flake8_annotations::plugins::definition(self, &definition, &visibility);
|
||||
}
|
||||
overloaded_name = flake8_annotations::helpers::overloaded_name(self, &definition);
|
||||
}
|
||||
|
||||
// pydocstyle
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//! Lint rules based on import analysis.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use rustpython_parser::ast::Suite;
|
||||
|
||||
use crate::ast::visitor::Visitor;
|
||||
@@ -33,8 +35,9 @@ pub fn check_imports(
|
||||
directives: &IsortDirectives,
|
||||
settings: &Settings,
|
||||
autofix: bool,
|
||||
path: &Path,
|
||||
) -> Vec<Check> {
|
||||
let mut tracker = ImportTracker::new(directives);
|
||||
let mut tracker = ImportTracker::new(directives, path);
|
||||
for stmt in python_ast {
|
||||
tracker.visit_stmt(stmt);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checks::{Check, CheckCode, CheckKind, REDIRECTS};
|
||||
use crate::checks::{Check, CheckCode, CheckKind, CODE_REDIRECTS};
|
||||
use crate::noqa;
|
||||
use crate::noqa::{is_file_exempt, Directive};
|
||||
use crate::settings::Settings;
|
||||
@@ -20,19 +20,18 @@ static URL_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^https?://\S+$").unwra
|
||||
|
||||
/// Whether the given line is too long and should be reported.
|
||||
fn should_enforce_line_length(line: &str, length: usize, limit: usize) -> bool {
|
||||
if length > limit {
|
||||
let mut chunks = line.split_whitespace();
|
||||
if let (Some(first), Some(_)) = (chunks.next(), chunks.next()) {
|
||||
// Do not enforce the line length for commented lines that end with a URL
|
||||
// or contain only a single word.
|
||||
!(first == "#" && chunks.last().map_or(true, |c| URL_REGEX.is_match(c)))
|
||||
} else {
|
||||
// Single word / no printable chars - no way to make the line shorter
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
if length <= limit {
|
||||
return false;
|
||||
}
|
||||
let mut chunks = line.split_whitespace();
|
||||
let (Some(first), Some(_)) = (chunks.next(), chunks.next()) else {
|
||||
// Single word / no printable chars - no way to make the line shorter
|
||||
return false;
|
||||
};
|
||||
|
||||
// Do not enforce the line length for commented lines that end with a URL
|
||||
// or contain only a single word.
|
||||
!(first == "#" && chunks.last().map_or(true, |c| URL_REGEX.is_match(c)))
|
||||
}
|
||||
|
||||
pub fn check_lines(
|
||||
@@ -210,7 +209,7 @@ pub fn check_lines(
|
||||
let mut invalid_codes = vec![];
|
||||
let mut valid_codes = vec![];
|
||||
for code in codes {
|
||||
let code = REDIRECTS.get(code).map_or(code, AsRef::as_ref);
|
||||
let code = CODE_REDIRECTS.get(code).map_or(code, AsRef::as_ref);
|
||||
if matches.contains(&code) || settings.external.contains(code) {
|
||||
valid_codes.push(code.to_string());
|
||||
} else {
|
||||
|
||||
@@ -4,9 +4,9 @@ use rustpython_parser::lexer::{LexResult, Tok};
|
||||
|
||||
use crate::checks::{Check, CheckCode};
|
||||
use crate::lex::docstring_detection::StateMachine;
|
||||
use crate::rules::checks::Context;
|
||||
use crate::ruff::checks::Context;
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
use crate::{eradicate, flake8_quotes, pycodestyle, rules, Settings};
|
||||
use crate::{eradicate, flake8_quotes, pycodestyle, ruff, Settings};
|
||||
|
||||
pub fn check_tokens(
|
||||
locator: &SourceCodeLocator,
|
||||
@@ -37,7 +37,7 @@ pub fn check_tokens(
|
||||
// RUF001, RUF002, RUF003
|
||||
if enforce_ambiguous_unicode_character {
|
||||
if matches!(tok, Tok::String { .. } | Tok::Comment) {
|
||||
checks.extend(rules::checks::ambiguous_unicode_character(
|
||||
checks.extend(ruff::checks::ambiguous_unicode_character(
|
||||
locator,
|
||||
start,
|
||||
end,
|
||||
|
||||
163
src/checks.rs
163
src/checks.rs
@@ -10,6 +10,7 @@ use strum_macros::{AsRefStr, Display, EnumIter, EnumString};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::flake8_debugger::types::DebuggerUsingType;
|
||||
use crate::flake8_quotes::settings::Quote;
|
||||
use crate::flake8_tidy_imports::settings::Strictness;
|
||||
@@ -134,6 +135,7 @@ pub enum CheckCode {
|
||||
B026,
|
||||
B027,
|
||||
B904,
|
||||
B905,
|
||||
// flake8-blind-except
|
||||
BLE001,
|
||||
// flake8-comprehensions
|
||||
@@ -158,7 +160,7 @@ pub enum CheckCode {
|
||||
// mccabe
|
||||
C901,
|
||||
// flake8-tidy-imports
|
||||
I252,
|
||||
TID252,
|
||||
// flake8-return
|
||||
RET501,
|
||||
RET502,
|
||||
@@ -290,6 +292,14 @@ pub enum CheckCode {
|
||||
FBT001,
|
||||
FBT002,
|
||||
FBT003,
|
||||
// flake8-unused-arguments
|
||||
ARG001,
|
||||
ARG002,
|
||||
ARG003,
|
||||
ARG004,
|
||||
ARG005,
|
||||
// flake8-import-conventions
|
||||
ICN001,
|
||||
// Ruff
|
||||
RUF001,
|
||||
RUF002,
|
||||
@@ -317,10 +327,12 @@ pub enum CheckCategory {
|
||||
Flake8Builtins,
|
||||
Flake8Comprehensions,
|
||||
Flake8Debugger,
|
||||
Flake8ImportConventions,
|
||||
Flake8Print,
|
||||
Flake8Quotes,
|
||||
Flake8Return,
|
||||
Flake8TidyImports,
|
||||
Flake8UnusedArguments,
|
||||
Eradicate,
|
||||
PygrepHooks,
|
||||
Pylint,
|
||||
@@ -354,10 +366,12 @@ impl CheckCategory {
|
||||
CheckCategory::Flake8Builtins => "flake8-builtins",
|
||||
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
|
||||
CheckCategory::Flake8Debugger => "flake8-debugger",
|
||||
CheckCategory::Flake8ImportConventions => "flake8-import-conventions",
|
||||
CheckCategory::Flake8Print => "flake8-print",
|
||||
CheckCategory::Flake8Quotes => "flake8-quotes",
|
||||
CheckCategory::Flake8Return => "flake8-return",
|
||||
CheckCategory::Flake8TidyImports => "flake8-tidy-imports",
|
||||
CheckCategory::Flake8UnusedArguments => "flake8-unused-arguments",
|
||||
CheckCategory::Isort => "isort",
|
||||
CheckCategory::McCabe => "mccabe",
|
||||
CheckCategory::PEP8Naming => "pep8-naming",
|
||||
@@ -371,6 +385,42 @@ impl CheckCategory {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn codes(&self) -> Vec<CheckCodePrefix> {
|
||||
match self {
|
||||
CheckCategory::Eradicate => vec![CheckCodePrefix::ERA],
|
||||
CheckCategory::Flake82020 => vec![CheckCodePrefix::YTT],
|
||||
CheckCategory::Flake8Annotations => vec![CheckCodePrefix::ANN],
|
||||
CheckCategory::Flake8Bandit => vec![CheckCodePrefix::S],
|
||||
CheckCategory::Flake8BlindExcept => vec![CheckCodePrefix::BLE],
|
||||
CheckCategory::Flake8BooleanTrap => vec![CheckCodePrefix::FBT],
|
||||
CheckCategory::Flake8Bugbear => vec![CheckCodePrefix::B],
|
||||
CheckCategory::Flake8Builtins => vec![CheckCodePrefix::A],
|
||||
CheckCategory::Flake8Comprehensions => vec![CheckCodePrefix::C4],
|
||||
CheckCategory::Flake8Debugger => vec![CheckCodePrefix::T10],
|
||||
CheckCategory::Flake8Print => vec![CheckCodePrefix::T20],
|
||||
CheckCategory::Flake8Quotes => vec![CheckCodePrefix::Q],
|
||||
CheckCategory::Flake8Return => vec![CheckCodePrefix::RET],
|
||||
CheckCategory::Flake8TidyImports => vec![CheckCodePrefix::TID],
|
||||
CheckCategory::Flake8UnusedArguments => vec![CheckCodePrefix::ARG],
|
||||
CheckCategory::Isort => vec![CheckCodePrefix::I],
|
||||
CheckCategory::McCabe => vec![CheckCodePrefix::C90],
|
||||
CheckCategory::PEP8Naming => vec![CheckCodePrefix::N],
|
||||
CheckCategory::Pycodestyle => vec![CheckCodePrefix::E, CheckCodePrefix::W],
|
||||
CheckCategory::Pydocstyle => vec![CheckCodePrefix::D],
|
||||
CheckCategory::Pyflakes => vec![CheckCodePrefix::F],
|
||||
CheckCategory::PygrepHooks => vec![CheckCodePrefix::PGH],
|
||||
CheckCategory::Pylint => vec![
|
||||
CheckCodePrefix::PLC,
|
||||
CheckCodePrefix::PLE,
|
||||
CheckCodePrefix::PLR,
|
||||
CheckCodePrefix::PLW,
|
||||
],
|
||||
CheckCategory::Pyupgrade => vec![CheckCodePrefix::UP],
|
||||
CheckCategory::Flake8ImportConventions => vec![CheckCodePrefix::ICN],
|
||||
CheckCategory::Ruff => vec![CheckCodePrefix::RUF],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn url(&self) -> Option<(&'static str, &'static Platform)> {
|
||||
match self {
|
||||
CheckCategory::Eradicate => {
|
||||
@@ -412,6 +462,7 @@ impl CheckCategory {
|
||||
"https://pypi.org/project/flake8-debugger/4.1.2/",
|
||||
&Platform::PyPI,
|
||||
)),
|
||||
CheckCategory::Flake8ImportConventions => None,
|
||||
CheckCategory::Flake8Print => Some((
|
||||
"https://pypi.org/project/flake8-print/5.0.0/",
|
||||
&Platform::PyPI,
|
||||
@@ -428,6 +479,10 @@ impl CheckCategory {
|
||||
"https://pypi.org/project/flake8-tidy-imports/4.8.0/",
|
||||
&Platform::PyPI,
|
||||
)),
|
||||
CheckCategory::Flake8UnusedArguments => Some((
|
||||
"https://pypi.org/project/flake8-unused-arguments/0.0.12/",
|
||||
&Platform::PyPI,
|
||||
)),
|
||||
CheckCategory::Isort => {
|
||||
Some(("https://pypi.org/project/isort/5.10.1/", &Platform::PyPI))
|
||||
}
|
||||
@@ -583,13 +638,13 @@ pub enum CheckKind {
|
||||
ConsiderUsingFromImport(String, String),
|
||||
AwaitOutsideAsync,
|
||||
UselessElseOnLoop,
|
||||
ConsiderUsingSysExit,
|
||||
UseSysExit(String),
|
||||
// flake8-builtins
|
||||
BuiltinVariableShadowing(String),
|
||||
BuiltinArgumentShadowing(String),
|
||||
BuiltinAttributeShadowing(String),
|
||||
// flake8-blind-except
|
||||
BlindExcept,
|
||||
BlindExcept(String),
|
||||
// flake8-bugbear
|
||||
AbstractBaseClassWithoutAbstractMethod(String),
|
||||
AssignmentToOsEnviron,
|
||||
@@ -618,6 +673,7 @@ pub enum CheckKind {
|
||||
UselessComparison,
|
||||
UselessContextlibSuppress,
|
||||
UselessExpression,
|
||||
ZipWithoutExplicitStrict,
|
||||
// flake8-comprehensions
|
||||
UnnecessaryGeneratorList,
|
||||
UnnecessaryGeneratorSet,
|
||||
@@ -774,6 +830,14 @@ pub enum CheckKind {
|
||||
BooleanPositionalValueInFunctionCall,
|
||||
// pygrep-hooks
|
||||
NoEval,
|
||||
// flake8-unused-arguments
|
||||
UnusedFunctionArgument(String),
|
||||
UnusedMethodArgument(String),
|
||||
UnusedClassMethodArgument(String),
|
||||
UnusedStaticMethodArgument(String),
|
||||
UnusedLambdaArgument(String),
|
||||
// flake8-import-conventions
|
||||
ImportAliasIsNotConventional(String, String),
|
||||
// Ruff
|
||||
AmbiguousUnicodeCharacterString(char, char),
|
||||
AmbiguousUnicodeCharacterDocstring(char, char),
|
||||
@@ -886,7 +950,7 @@ impl CheckCode {
|
||||
CheckCode::PLR1701 => {
|
||||
CheckKind::ConsiderMergingIsinstance("...".to_string(), vec!["...".to_string()])
|
||||
}
|
||||
CheckCode::PLR1722 => CheckKind::ConsiderUsingSysExit,
|
||||
CheckCode::PLR1722 => CheckKind::UseSysExit("exit".to_string()),
|
||||
CheckCode::PLW0120 => CheckKind::UselessElseOnLoop,
|
||||
// flake8-builtins
|
||||
CheckCode::A001 => CheckKind::BuiltinVariableShadowing("...".to_string()),
|
||||
@@ -924,6 +988,7 @@ impl CheckCode {
|
||||
CheckCode::B026 => CheckKind::StarArgUnpackingAfterKeywordArg,
|
||||
CheckCode::B027 => CheckKind::EmptyMethodWithoutAbstractDecorator("...".to_string()),
|
||||
CheckCode::B904 => CheckKind::RaiseWithoutFromInsideExcept,
|
||||
CheckCode::B905 => CheckKind::ZipWithoutExplicitStrict,
|
||||
// flake8-comprehensions
|
||||
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
|
||||
CheckCode::C401 => CheckKind::UnnecessaryGeneratorSet,
|
||||
@@ -957,7 +1022,7 @@ impl CheckCode {
|
||||
// flake8-debugger
|
||||
CheckCode::T100 => CheckKind::Debugger(DebuggerUsingType::Import("...".to_string())),
|
||||
// flake8-tidy-imports
|
||||
CheckCode::I252 => CheckKind::BannedRelativeImport(Strictness::All),
|
||||
CheckCode::TID252 => CheckKind::BannedRelativeImport(Strictness::All),
|
||||
// flake8-return
|
||||
CheckCode::RET501 => CheckKind::UnnecessaryReturnNone,
|
||||
CheckCode::RET502 => CheckKind::ImplicitReturnValue,
|
||||
@@ -999,7 +1064,7 @@ impl CheckCode {
|
||||
CheckCode::YTT302 => CheckKind::SysVersionCmpStr10,
|
||||
CheckCode::YTT303 => CheckKind::SysVersionSlice1Referenced,
|
||||
// flake8-blind-except
|
||||
CheckCode::BLE001 => CheckKind::BlindExcept,
|
||||
CheckCode::BLE001 => CheckKind::BlindExcept("Exception".to_string()),
|
||||
// pyupgrade
|
||||
CheckCode::UP001 => CheckKind::UselessMetaclassType,
|
||||
CheckCode::UP003 => CheckKind::TypeOfPrimitive(Primitive::Str),
|
||||
@@ -1113,6 +1178,16 @@ impl CheckCode {
|
||||
CheckCode::FBT003 => CheckKind::BooleanPositionalValueInFunctionCall,
|
||||
// pygrep-hooks
|
||||
CheckCode::PGH001 => CheckKind::NoEval,
|
||||
// flake8-unused-arguments
|
||||
CheckCode::ARG001 => CheckKind::UnusedFunctionArgument("...".to_string()),
|
||||
CheckCode::ARG002 => CheckKind::UnusedMethodArgument("...".to_string()),
|
||||
CheckCode::ARG003 => CheckKind::UnusedClassMethodArgument("...".to_string()),
|
||||
CheckCode::ARG004 => CheckKind::UnusedStaticMethodArgument("...".to_string()),
|
||||
CheckCode::ARG005 => CheckKind::UnusedLambdaArgument("...".to_string()),
|
||||
// flake8-import-conventions
|
||||
CheckCode::ICN001 => {
|
||||
CheckKind::ImportAliasIsNotConventional("...".to_string(), "...".to_string())
|
||||
}
|
||||
// Ruff
|
||||
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
|
||||
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
|
||||
@@ -1138,6 +1213,11 @@ impl CheckCode {
|
||||
CheckCode::ANN205 => CheckCategory::Flake8Annotations,
|
||||
CheckCode::ANN206 => CheckCategory::Flake8Annotations,
|
||||
CheckCode::ANN401 => CheckCategory::Flake8Annotations,
|
||||
CheckCode::ARG001 => CheckCategory::Flake8UnusedArguments,
|
||||
CheckCode::ARG002 => CheckCategory::Flake8UnusedArguments,
|
||||
CheckCode::ARG003 => CheckCategory::Flake8UnusedArguments,
|
||||
CheckCode::ARG004 => CheckCategory::Flake8UnusedArguments,
|
||||
CheckCode::ARG005 => CheckCategory::Flake8UnusedArguments,
|
||||
CheckCode::B002 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B003 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B004 => CheckCategory::Flake8Bugbear,
|
||||
@@ -1165,6 +1245,7 @@ impl CheckCode {
|
||||
CheckCode::B026 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B027 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B904 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B905 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::BLE001 => CheckCategory::Flake8BlindExcept,
|
||||
CheckCode::C400 => CheckCategory::Flake8Comprehensions,
|
||||
CheckCode::C401 => CheckCategory::Flake8Comprehensions,
|
||||
@@ -1288,7 +1369,8 @@ impl CheckCode {
|
||||
CheckCode::FBT002 => CheckCategory::Flake8BooleanTrap,
|
||||
CheckCode::FBT003 => CheckCategory::Flake8BooleanTrap,
|
||||
CheckCode::I001 => CheckCategory::Isort,
|
||||
CheckCode::I252 => CheckCategory::Flake8TidyImports,
|
||||
CheckCode::TID252 => CheckCategory::Flake8TidyImports,
|
||||
CheckCode::ICN001 => CheckCategory::Flake8ImportConventions,
|
||||
CheckCode::N801 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N802 => CheckCategory::PEP8Naming,
|
||||
CheckCode::N803 => CheckCategory::PEP8Naming,
|
||||
@@ -1441,7 +1523,7 @@ impl CheckKind {
|
||||
CheckKind::ConsiderMergingIsinstance(..) => &CheckCode::PLR1701,
|
||||
CheckKind::PropertyWithParameters => &CheckCode::PLR0206,
|
||||
CheckKind::ConsiderUsingFromImport(..) => &CheckCode::PLR0402,
|
||||
CheckKind::ConsiderUsingSysExit => &CheckCode::PLR1722,
|
||||
CheckKind::UseSysExit(_) => &CheckCode::PLR1722,
|
||||
CheckKind::UselessElseOnLoop => &CheckCode::PLW0120,
|
||||
// flake8-builtins
|
||||
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
|
||||
@@ -1465,6 +1547,7 @@ impl CheckKind {
|
||||
CheckKind::MutableArgumentDefault => &CheckCode::B006,
|
||||
CheckKind::NoAssertRaisesException => &CheckCode::B017,
|
||||
CheckKind::RaiseWithoutFromInsideExcept => &CheckCode::B904,
|
||||
CheckKind::ZipWithoutExplicitStrict => &CheckCode::B905,
|
||||
CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013,
|
||||
CheckKind::SetAttrWithConstant => &CheckCode::B010,
|
||||
CheckKind::StarArgUnpackingAfterKeywordArg => &CheckCode::B026,
|
||||
@@ -1476,7 +1559,7 @@ impl CheckKind {
|
||||
CheckKind::UselessContextlibSuppress => &CheckCode::B022,
|
||||
CheckKind::UselessExpression => &CheckCode::B018,
|
||||
// flake8-blind-except
|
||||
CheckKind::BlindExcept => &CheckCode::BLE001,
|
||||
CheckKind::BlindExcept(_) => &CheckCode::BLE001,
|
||||
// flake8-comprehensions
|
||||
CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
|
||||
CheckKind::UnnecessaryGeneratorSet => &CheckCode::C401,
|
||||
@@ -1497,7 +1580,7 @@ impl CheckKind {
|
||||
// flake8-debugger
|
||||
CheckKind::Debugger(_) => &CheckCode::T100,
|
||||
// flake8-tidy-imports
|
||||
CheckKind::BannedRelativeImport(_) => &CheckCode::I252,
|
||||
CheckKind::BannedRelativeImport(_) => &CheckCode::TID252,
|
||||
// flake8-return
|
||||
CheckKind::UnnecessaryReturnNone => &CheckCode::RET501,
|
||||
CheckKind::ImplicitReturnValue => &CheckCode::RET502,
|
||||
@@ -1633,6 +1716,14 @@ impl CheckKind {
|
||||
CheckKind::BooleanPositionalValueInFunctionCall => &CheckCode::FBT003,
|
||||
// pygrep-hooks
|
||||
CheckKind::NoEval => &CheckCode::PGH001,
|
||||
// flake8-unused-arguments
|
||||
CheckKind::UnusedFunctionArgument(..) => &CheckCode::ARG001,
|
||||
CheckKind::UnusedMethodArgument(..) => &CheckCode::ARG002,
|
||||
CheckKind::UnusedClassMethodArgument(..) => &CheckCode::ARG003,
|
||||
CheckKind::UnusedStaticMethodArgument(..) => &CheckCode::ARG004,
|
||||
CheckKind::UnusedLambdaArgument(..) => &CheckCode::ARG005,
|
||||
// flake8-import-conventions
|
||||
CheckKind::ImportAliasIsNotConventional(..) => &CheckCode::ICN001,
|
||||
// Ruff
|
||||
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
|
||||
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
|
||||
@@ -1830,7 +1921,7 @@ impl CheckKind {
|
||||
}
|
||||
CheckKind::ConsiderMergingIsinstance(obj, types) => {
|
||||
let types = types.join(", ");
|
||||
format!("Consider merging these isinstance calls: `isinstance({obj}, ({types}))`")
|
||||
format!("Merge these isinstance calls: `isinstance({obj}, ({types}))`")
|
||||
}
|
||||
CheckKind::MisplacedComparisonConstant(comprison) => {
|
||||
format!("Comparison should be {comprison}")
|
||||
@@ -1842,7 +1933,7 @@ impl CheckKind {
|
||||
"Cannot have defined parameters for properties".to_string()
|
||||
}
|
||||
CheckKind::ConsiderUsingFromImport(module, name) => {
|
||||
format!("Consider using `from {module} import {name}`")
|
||||
format!("Use `from {module} import {name}` in lieu of alias")
|
||||
}
|
||||
CheckKind::AwaitOutsideAsync => {
|
||||
"`await` should be used within an async function".to_string()
|
||||
@@ -1850,7 +1941,7 @@ impl CheckKind {
|
||||
CheckKind::UselessElseOnLoop => "Else clause on loop without a break statement, \
|
||||
remove the else and de-indent all the code inside it"
|
||||
.to_string(),
|
||||
CheckKind::ConsiderUsingSysExit => "Consider using `sys.exit()`".to_string(),
|
||||
CheckKind::UseSysExit(name) => format!("Use `sys.exit()` instead of `{name}`"),
|
||||
// flake8-builtins
|
||||
CheckKind::BuiltinVariableShadowing(name) => {
|
||||
format!("Variable `{name}` is shadowing a python builtin")
|
||||
@@ -1976,6 +2067,9 @@ impl CheckKind {
|
||||
from None to distinguish them from errors in exception handling"
|
||||
.to_string()
|
||||
}
|
||||
CheckKind::ZipWithoutExplicitStrict => {
|
||||
"`zip()` without an explicit `strict=` parameter".to_string()
|
||||
}
|
||||
// flake8-comprehensions
|
||||
CheckKind::UnnecessaryGeneratorList => {
|
||||
"Unnecessary generator (rewrite as a `list` comprehension)".to_string()
|
||||
@@ -2402,7 +2496,7 @@ impl CheckKind {
|
||||
format!("Possible hardcoded password: `\"{string}\"`")
|
||||
}
|
||||
// flake8-blind-except
|
||||
CheckKind::BlindExcept => "Blind except Exception: statement".to_string(),
|
||||
CheckKind::BlindExcept(name) => format!("Do not catch blind exception: `{name}`"),
|
||||
// mccabe
|
||||
CheckKind::FunctionIsTooComplex(name, complexity) => {
|
||||
format!("`{name}` is too complex ({complexity})")
|
||||
@@ -2419,6 +2513,22 @@ impl CheckKind {
|
||||
}
|
||||
// pygrep-hooks
|
||||
CheckKind::NoEval => "No builtin `eval()` allowed".to_string(),
|
||||
// flake8-unused-arguments
|
||||
CheckKind::UnusedFunctionArgument(name) => {
|
||||
format!("Unused function argument: `{name}`")
|
||||
}
|
||||
CheckKind::UnusedMethodArgument(name) => format!("Unused method argument: `{name}`"),
|
||||
CheckKind::UnusedClassMethodArgument(name) => {
|
||||
format!("Unused class method argument: `{name}`")
|
||||
}
|
||||
CheckKind::UnusedStaticMethodArgument(name) => {
|
||||
format!("Unused static method argument: `{name}`")
|
||||
}
|
||||
CheckKind::UnusedLambdaArgument(name) => format!("Unused lambda argument: `{name}`"),
|
||||
// flake8-import-conventions
|
||||
CheckKind::ImportAliasIsNotConventional(name, asname) => {
|
||||
format!("`{name}` should be imported as `{asname}`")
|
||||
}
|
||||
// Ruff
|
||||
CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) => {
|
||||
format!(
|
||||
@@ -2489,7 +2599,6 @@ impl CheckKind {
|
||||
| CheckKind::BlankLineBeforeSection(..)
|
||||
| CheckKind::CapitalizeSectionName(..)
|
||||
| CheckKind::CommentedOutCode
|
||||
| CheckKind::ConsiderUsingSysExit
|
||||
| CheckKind::ConvertNamedTupleFunctionalToClass(..)
|
||||
| CheckKind::ConvertTypedDictFunctionalToClass(..)
|
||||
| CheckKind::DashedUnderlineAfterSection(..)
|
||||
@@ -2497,6 +2606,8 @@ impl CheckKind {
|
||||
| CheckKind::DoNotAssertFalse
|
||||
| CheckKind::DoNotAssignLambda
|
||||
| CheckKind::DuplicateHandlerException(..)
|
||||
| CheckKind::EndsInPeriod
|
||||
| CheckKind::EndsInPunctuation
|
||||
| CheckKind::GetAttrWithConstant
|
||||
| CheckKind::ImplicitReturn
|
||||
| CheckKind::ImplicitReturnValue
|
||||
@@ -2553,6 +2664,7 @@ impl CheckKind {
|
||||
| CheckKind::UnusedNOQA(..)
|
||||
| CheckKind::UsePEP585Annotation(..)
|
||||
| CheckKind::UsePEP604Annotation
|
||||
| CheckKind::UseSysExit(..)
|
||||
| CheckKind::UselessImportAlias
|
||||
| CheckKind::UselessMetaclassType
|
||||
| CheckKind::UselessObjectInheritance(..)
|
||||
@@ -2584,7 +2696,7 @@ impl Check {
|
||||
}
|
||||
|
||||
/// A hash map from deprecated to latest `CheckCode`.
|
||||
pub static REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(|| {
|
||||
pub static CODE_REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(|| {
|
||||
FxHashMap::from_iter([
|
||||
// TODO(charlie): Remove by 2023-01-01.
|
||||
("U001", CheckCode::UP001),
|
||||
@@ -2601,6 +2713,25 @@ pub static REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(|| {
|
||||
("U013", CheckCode::UP013),
|
||||
("U014", CheckCode::UP014),
|
||||
("U015", CheckCode::UP015),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("I252", CheckCode::TID252),
|
||||
("M001", CheckCode::RUF100),
|
||||
])
|
||||
});
|
||||
|
||||
/// A hash map from deprecated `CheckCodePrefix` to latest `CheckCodePrefix`.
|
||||
pub static PREFIX_REDIRECTS: Lazy<FxHashMap<&'static str, &'static str>> = Lazy::new(|| {
|
||||
FxHashMap::from_iter([
|
||||
// TODO(charlie): Remove by 2023-01-01.
|
||||
("U", "UP"),
|
||||
("U0", "UP0"),
|
||||
("U00", "UP00"),
|
||||
("U01", "UP01"),
|
||||
// TODO(charlie): Remove by 2023-02-01.
|
||||
("I2", "TID2"),
|
||||
("I25", "TID25"),
|
||||
("M", "RUF100"),
|
||||
("M0", "RUF100"),
|
||||
])
|
||||
});
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
use colored::Colorize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum_macros::EnumString;
|
||||
use strum_macros::{AsRefStr, EnumString};
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
|
||||
#[derive(EnumString, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
|
||||
#[derive(
|
||||
EnumString, AsRefStr, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize,
|
||||
)]
|
||||
pub enum CheckCodePrefix {
|
||||
A,
|
||||
A0,
|
||||
@@ -34,6 +36,14 @@ pub enum CheckCodePrefix {
|
||||
ANN4,
|
||||
ANN40,
|
||||
ANN401,
|
||||
ARG,
|
||||
ARG0,
|
||||
ARG00,
|
||||
ARG001,
|
||||
ARG002,
|
||||
ARG003,
|
||||
ARG004,
|
||||
ARG005,
|
||||
B,
|
||||
B0,
|
||||
B00,
|
||||
@@ -68,6 +78,7 @@ pub enum CheckCodePrefix {
|
||||
B9,
|
||||
B90,
|
||||
B904,
|
||||
B905,
|
||||
BLE,
|
||||
BLE0,
|
||||
BLE00,
|
||||
@@ -256,6 +267,13 @@ pub enum CheckCodePrefix {
|
||||
I2,
|
||||
I25,
|
||||
I252,
|
||||
ICN,
|
||||
ICN0,
|
||||
ICN00,
|
||||
ICN001,
|
||||
M,
|
||||
M0,
|
||||
M001,
|
||||
N,
|
||||
N8,
|
||||
N80,
|
||||
@@ -360,6 +378,10 @@ pub enum CheckCodePrefix {
|
||||
T20,
|
||||
T201,
|
||||
T203,
|
||||
TID,
|
||||
TID2,
|
||||
TID25,
|
||||
TID252,
|
||||
U,
|
||||
U0,
|
||||
U00,
|
||||
@@ -485,6 +507,32 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::ANN4 => vec![CheckCode::ANN401],
|
||||
CheckCodePrefix::ANN40 => vec![CheckCode::ANN401],
|
||||
CheckCodePrefix::ANN401 => vec![CheckCode::ANN401],
|
||||
CheckCodePrefix::ARG => vec![
|
||||
CheckCode::ARG001,
|
||||
CheckCode::ARG002,
|
||||
CheckCode::ARG003,
|
||||
CheckCode::ARG004,
|
||||
CheckCode::ARG005,
|
||||
],
|
||||
CheckCodePrefix::ARG0 => vec![
|
||||
CheckCode::ARG001,
|
||||
CheckCode::ARG002,
|
||||
CheckCode::ARG003,
|
||||
CheckCode::ARG004,
|
||||
CheckCode::ARG005,
|
||||
],
|
||||
CheckCodePrefix::ARG00 => vec![
|
||||
CheckCode::ARG001,
|
||||
CheckCode::ARG002,
|
||||
CheckCode::ARG003,
|
||||
CheckCode::ARG004,
|
||||
CheckCode::ARG005,
|
||||
],
|
||||
CheckCodePrefix::ARG001 => vec![CheckCode::ARG001],
|
||||
CheckCodePrefix::ARG002 => vec![CheckCode::ARG002],
|
||||
CheckCodePrefix::ARG003 => vec![CheckCode::ARG003],
|
||||
CheckCodePrefix::ARG004 => vec![CheckCode::ARG004],
|
||||
CheckCodePrefix::ARG005 => vec![CheckCode::ARG005],
|
||||
CheckCodePrefix::B => vec![
|
||||
CheckCode::B002,
|
||||
CheckCode::B003,
|
||||
@@ -513,6 +561,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::B026,
|
||||
CheckCode::B027,
|
||||
CheckCode::B904,
|
||||
CheckCode::B905,
|
||||
],
|
||||
CheckCodePrefix::B0 => vec![
|
||||
CheckCode::B002,
|
||||
@@ -600,9 +649,10 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::B025 => vec![CheckCode::B025],
|
||||
CheckCodePrefix::B026 => vec![CheckCode::B026],
|
||||
CheckCodePrefix::B027 => vec![CheckCode::B027],
|
||||
CheckCodePrefix::B9 => vec![CheckCode::B904],
|
||||
CheckCodePrefix::B90 => vec![CheckCode::B904],
|
||||
CheckCodePrefix::B9 => vec![CheckCode::B904, CheckCode::B905],
|
||||
CheckCodePrefix::B90 => vec![CheckCode::B904, CheckCode::B905],
|
||||
CheckCodePrefix::B904 => vec![CheckCode::B904],
|
||||
CheckCodePrefix::B905 => vec![CheckCode::B905],
|
||||
CheckCodePrefix::BLE => vec![CheckCode::BLE001],
|
||||
CheckCodePrefix::BLE0 => vec![CheckCode::BLE001],
|
||||
CheckCodePrefix::BLE00 => vec![CheckCode::BLE001],
|
||||
@@ -1131,13 +1181,68 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::FBT001 => vec![CheckCode::FBT001],
|
||||
CheckCodePrefix::FBT002 => vec![CheckCode::FBT002],
|
||||
CheckCodePrefix::FBT003 => vec![CheckCode::FBT003],
|
||||
CheckCodePrefix::I => vec![CheckCode::I252, CheckCode::I001],
|
||||
CheckCodePrefix::I => vec![CheckCode::I001],
|
||||
CheckCodePrefix::I0 => vec![CheckCode::I001],
|
||||
CheckCodePrefix::I00 => vec![CheckCode::I001],
|
||||
CheckCodePrefix::I001 => vec![CheckCode::I001],
|
||||
CheckCodePrefix::I2 => vec![CheckCode::I252],
|
||||
CheckCodePrefix::I25 => vec![CheckCode::I252],
|
||||
CheckCodePrefix::I252 => vec![CheckCode::I252],
|
||||
CheckCodePrefix::I2 => {
|
||||
eprintln!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`I2` has been remapped to `TID2`".bold()
|
||||
);
|
||||
vec![CheckCode::TID252]
|
||||
}
|
||||
CheckCodePrefix::I25 => {
|
||||
eprintln!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`I25` has been remapped to `TID25`".bold()
|
||||
);
|
||||
vec![CheckCode::TID252]
|
||||
}
|
||||
CheckCodePrefix::I252 => {
|
||||
eprintln!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`I252` has been remapped to `TID252`".bold()
|
||||
);
|
||||
vec![CheckCode::TID252]
|
||||
}
|
||||
CheckCodePrefix::ICN => vec![CheckCode::ICN001],
|
||||
CheckCodePrefix::ICN0 => vec![CheckCode::ICN001],
|
||||
CheckCodePrefix::ICN00 => vec![CheckCode::ICN001],
|
||||
CheckCodePrefix::ICN001 => vec![CheckCode::ICN001],
|
||||
CheckCodePrefix::M => {
|
||||
eprintln!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`M` has been remapped to `RUF100`".bold()
|
||||
);
|
||||
vec![CheckCode::RUF100]
|
||||
}
|
||||
CheckCodePrefix::M0 => {
|
||||
eprintln!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`M0` has been remapped to `RUF100`".bold()
|
||||
);
|
||||
vec![CheckCode::RUF100]
|
||||
}
|
||||
CheckCodePrefix::M001 => {
|
||||
eprintln!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`M001` has been remapped to `RUF100`".bold()
|
||||
);
|
||||
vec![CheckCode::RUF100]
|
||||
}
|
||||
CheckCodePrefix::N => vec![
|
||||
CheckCode::N801,
|
||||
CheckCode::N802,
|
||||
@@ -1366,54 +1471,82 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::T20 => vec![CheckCode::T201, CheckCode::T203],
|
||||
CheckCodePrefix::T201 => vec![CheckCode::T201],
|
||||
CheckCodePrefix::T203 => vec![CheckCode::T203],
|
||||
CheckCodePrefix::U => vec![
|
||||
CheckCode::UP001,
|
||||
CheckCode::UP003,
|
||||
CheckCode::UP004,
|
||||
CheckCode::UP005,
|
||||
CheckCode::UP006,
|
||||
CheckCode::UP007,
|
||||
CheckCode::UP008,
|
||||
CheckCode::UP009,
|
||||
CheckCode::UP010,
|
||||
CheckCode::UP011,
|
||||
CheckCode::UP012,
|
||||
CheckCode::UP013,
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
],
|
||||
CheckCodePrefix::U0 => vec![
|
||||
CheckCode::UP001,
|
||||
CheckCode::UP003,
|
||||
CheckCode::UP004,
|
||||
CheckCode::UP005,
|
||||
CheckCode::UP006,
|
||||
CheckCode::UP007,
|
||||
CheckCode::UP008,
|
||||
CheckCode::UP009,
|
||||
CheckCode::UP010,
|
||||
CheckCode::UP011,
|
||||
CheckCode::UP012,
|
||||
CheckCode::UP013,
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
],
|
||||
CheckCodePrefix::U00 => vec![
|
||||
CheckCode::UP001,
|
||||
CheckCode::UP003,
|
||||
CheckCode::UP004,
|
||||
CheckCode::UP005,
|
||||
CheckCode::UP006,
|
||||
CheckCode::UP007,
|
||||
CheckCode::UP008,
|
||||
CheckCode::UP009,
|
||||
],
|
||||
CheckCodePrefix::TID => vec![CheckCode::TID252],
|
||||
CheckCodePrefix::TID2 => vec![CheckCode::TID252],
|
||||
CheckCodePrefix::TID25 => vec![CheckCode::TID252],
|
||||
CheckCodePrefix::TID252 => vec![CheckCode::TID252],
|
||||
CheckCodePrefix::U => {
|
||||
eprintln!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U` has been remapped to `UP`".bold()
|
||||
);
|
||||
vec![
|
||||
CheckCode::UP001,
|
||||
CheckCode::UP003,
|
||||
CheckCode::UP004,
|
||||
CheckCode::UP005,
|
||||
CheckCode::UP006,
|
||||
CheckCode::UP007,
|
||||
CheckCode::UP008,
|
||||
CheckCode::UP009,
|
||||
CheckCode::UP010,
|
||||
CheckCode::UP011,
|
||||
CheckCode::UP012,
|
||||
CheckCode::UP013,
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U0 => {
|
||||
eprintln!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U0` has been remapped to `UP0`".bold()
|
||||
);
|
||||
vec![
|
||||
CheckCode::UP001,
|
||||
CheckCode::UP003,
|
||||
CheckCode::UP004,
|
||||
CheckCode::UP005,
|
||||
CheckCode::UP006,
|
||||
CheckCode::UP007,
|
||||
CheckCode::UP008,
|
||||
CheckCode::UP009,
|
||||
CheckCode::UP010,
|
||||
CheckCode::UP011,
|
||||
CheckCode::UP012,
|
||||
CheckCode::UP013,
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U00 => {
|
||||
eprintln!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U00` has been remapped to `UP00`".bold()
|
||||
);
|
||||
vec![
|
||||
CheckCode::UP001,
|
||||
CheckCode::UP003,
|
||||
CheckCode::UP004,
|
||||
CheckCode::UP005,
|
||||
CheckCode::UP006,
|
||||
CheckCode::UP007,
|
||||
CheckCode::UP008,
|
||||
CheckCode::UP009,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U001 => {
|
||||
eprintln!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U001` has been renamed to `UP001`".bold()
|
||||
"`U001` has been remapped to `UP001`".bold()
|
||||
);
|
||||
vec![CheckCode::UP001]
|
||||
}
|
||||
@@ -1422,7 +1555,7 @@ impl CheckCodePrefix {
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U003` has been renamed to `UP003`".bold()
|
||||
"`U003` has been remapped to `UP003`".bold()
|
||||
);
|
||||
vec![CheckCode::UP003]
|
||||
}
|
||||
@@ -1431,7 +1564,7 @@ impl CheckCodePrefix {
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U004` has been renamed to `UP004`".bold()
|
||||
"`U004` has been remapped to `UP004`".bold()
|
||||
);
|
||||
vec![CheckCode::UP004]
|
||||
}
|
||||
@@ -1440,7 +1573,7 @@ impl CheckCodePrefix {
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U005` has been renamed to `UP005`".bold()
|
||||
"`U005` has been remapped to `UP005`".bold()
|
||||
);
|
||||
vec![CheckCode::UP005]
|
||||
}
|
||||
@@ -1449,7 +1582,7 @@ impl CheckCodePrefix {
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U006` has been renamed to `UP006`".bold()
|
||||
"`U006` has been remapped to `UP006`".bold()
|
||||
);
|
||||
vec![CheckCode::UP006]
|
||||
}
|
||||
@@ -1458,7 +1591,7 @@ impl CheckCodePrefix {
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U007` has been renamed to `UP007`".bold()
|
||||
"`U007` has been remapped to `UP007`".bold()
|
||||
);
|
||||
vec![CheckCode::UP007]
|
||||
}
|
||||
@@ -1467,7 +1600,7 @@ impl CheckCodePrefix {
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U008` has been renamed to `UP008`".bold()
|
||||
"`U008` has been remapped to `UP008`".bold()
|
||||
);
|
||||
vec![CheckCode::UP008]
|
||||
}
|
||||
@@ -1476,24 +1609,32 @@ impl CheckCodePrefix {
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U009` has been renamed to `UP009`".bold()
|
||||
"`U009` has been remapped to `UP009`".bold()
|
||||
);
|
||||
vec![CheckCode::UP009]
|
||||
}
|
||||
CheckCodePrefix::U01 => vec![
|
||||
CheckCode::UP010,
|
||||
CheckCode::UP011,
|
||||
CheckCode::UP012,
|
||||
CheckCode::UP013,
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
],
|
||||
CheckCodePrefix::U01 => {
|
||||
eprintln!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U01` has been remapped to `UP01`".bold()
|
||||
);
|
||||
vec![
|
||||
CheckCode::UP010,
|
||||
CheckCode::UP011,
|
||||
CheckCode::UP012,
|
||||
CheckCode::UP013,
|
||||
CheckCode::UP014,
|
||||
CheckCode::UP015,
|
||||
]
|
||||
}
|
||||
CheckCodePrefix::U010 => {
|
||||
eprintln!(
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U010` has been renamed to `UP010`".bold()
|
||||
"`U010` has been remapped to `UP010`".bold()
|
||||
);
|
||||
vec![CheckCode::UP010]
|
||||
}
|
||||
@@ -1502,7 +1643,7 @@ impl CheckCodePrefix {
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U011` has been renamed to `UP011`".bold()
|
||||
"`U011` has been remapped to `UP011`".bold()
|
||||
);
|
||||
vec![CheckCode::UP011]
|
||||
}
|
||||
@@ -1511,7 +1652,7 @@ impl CheckCodePrefix {
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U012` has been renamed to `UP012`".bold()
|
||||
"`U012` has been remapped to `UP012`".bold()
|
||||
);
|
||||
vec![CheckCode::UP012]
|
||||
}
|
||||
@@ -1520,7 +1661,7 @@ impl CheckCodePrefix {
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U013` has been renamed to `UP013`".bold()
|
||||
"`U013` has been remapped to `UP013`".bold()
|
||||
);
|
||||
vec![CheckCode::UP013]
|
||||
}
|
||||
@@ -1529,7 +1670,7 @@ impl CheckCodePrefix {
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U014` has been renamed to `UP014`".bold()
|
||||
"`U014` has been remapped to `UP014`".bold()
|
||||
);
|
||||
vec![CheckCode::UP014]
|
||||
}
|
||||
@@ -1538,7 +1679,7 @@ impl CheckCodePrefix {
|
||||
"{}{} {}",
|
||||
"warning".yellow().bold(),
|
||||
":".bold(),
|
||||
"`U015` has been renamed to `UP015`".bold()
|
||||
"`U015` has been remapped to `UP015`".bold()
|
||||
);
|
||||
vec![CheckCode::UP015]
|
||||
}
|
||||
@@ -1685,6 +1826,14 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::ANN4 => SuffixLength::One,
|
||||
CheckCodePrefix::ANN40 => SuffixLength::Two,
|
||||
CheckCodePrefix::ANN401 => SuffixLength::Three,
|
||||
CheckCodePrefix::ARG => SuffixLength::Zero,
|
||||
CheckCodePrefix::ARG0 => SuffixLength::One,
|
||||
CheckCodePrefix::ARG00 => SuffixLength::Two,
|
||||
CheckCodePrefix::ARG001 => SuffixLength::Three,
|
||||
CheckCodePrefix::ARG002 => SuffixLength::Three,
|
||||
CheckCodePrefix::ARG003 => SuffixLength::Three,
|
||||
CheckCodePrefix::ARG004 => SuffixLength::Three,
|
||||
CheckCodePrefix::ARG005 => SuffixLength::Three,
|
||||
CheckCodePrefix::B => SuffixLength::Zero,
|
||||
CheckCodePrefix::B0 => SuffixLength::One,
|
||||
CheckCodePrefix::B00 => SuffixLength::Two,
|
||||
@@ -1719,6 +1868,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::B9 => SuffixLength::One,
|
||||
CheckCodePrefix::B90 => SuffixLength::Two,
|
||||
CheckCodePrefix::B904 => SuffixLength::Three,
|
||||
CheckCodePrefix::B905 => SuffixLength::Three,
|
||||
CheckCodePrefix::BLE => SuffixLength::Zero,
|
||||
CheckCodePrefix::BLE0 => SuffixLength::One,
|
||||
CheckCodePrefix::BLE00 => SuffixLength::Two,
|
||||
@@ -1907,6 +2057,13 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::I2 => SuffixLength::One,
|
||||
CheckCodePrefix::I25 => SuffixLength::Two,
|
||||
CheckCodePrefix::I252 => SuffixLength::Three,
|
||||
CheckCodePrefix::ICN => SuffixLength::Zero,
|
||||
CheckCodePrefix::ICN0 => SuffixLength::One,
|
||||
CheckCodePrefix::ICN00 => SuffixLength::Two,
|
||||
CheckCodePrefix::ICN001 => SuffixLength::Three,
|
||||
CheckCodePrefix::M => SuffixLength::Zero,
|
||||
CheckCodePrefix::M0 => SuffixLength::One,
|
||||
CheckCodePrefix::M001 => SuffixLength::Three,
|
||||
CheckCodePrefix::N => SuffixLength::Zero,
|
||||
CheckCodePrefix::N8 => SuffixLength::One,
|
||||
CheckCodePrefix::N80 => SuffixLength::Two,
|
||||
@@ -2011,6 +2168,10 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::T20 => SuffixLength::Two,
|
||||
CheckCodePrefix::T201 => SuffixLength::Three,
|
||||
CheckCodePrefix::T203 => SuffixLength::Three,
|
||||
CheckCodePrefix::TID => SuffixLength::Zero,
|
||||
CheckCodePrefix::TID2 => SuffixLength::One,
|
||||
CheckCodePrefix::TID25 => SuffixLength::Two,
|
||||
CheckCodePrefix::TID252 => SuffixLength::Three,
|
||||
CheckCodePrefix::U => SuffixLength::Zero,
|
||||
CheckCodePrefix::U0 => SuffixLength::One,
|
||||
CheckCodePrefix::U00 => SuffixLength::Two,
|
||||
@@ -2078,6 +2239,7 @@ impl CheckCodePrefix {
|
||||
pub const CATEGORIES: &[CheckCodePrefix] = &[
|
||||
CheckCodePrefix::A,
|
||||
CheckCodePrefix::ANN,
|
||||
CheckCodePrefix::ARG,
|
||||
CheckCodePrefix::B,
|
||||
CheckCodePrefix::BLE,
|
||||
CheckCodePrefix::C,
|
||||
@@ -2087,6 +2249,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[
|
||||
CheckCodePrefix::F,
|
||||
CheckCodePrefix::FBT,
|
||||
CheckCodePrefix::I,
|
||||
CheckCodePrefix::ICN,
|
||||
CheckCodePrefix::N,
|
||||
CheckCodePrefix::PGH,
|
||||
CheckCodePrefix::PLC,
|
||||
@@ -2098,7 +2261,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[
|
||||
CheckCodePrefix::RUF,
|
||||
CheckCodePrefix::S,
|
||||
CheckCodePrefix::T,
|
||||
CheckCodePrefix::U,
|
||||
CheckCodePrefix::TID,
|
||||
CheckCodePrefix::UP,
|
||||
CheckCodePrefix::W,
|
||||
CheckCodePrefix::YTT,
|
||||
|
||||
@@ -93,32 +93,34 @@ pub fn extract_isort_directives(lxr: &[LexResult], locator: &SourceCodeLocator)
|
||||
continue;
|
||||
}
|
||||
|
||||
if matches!(tok, Tok::Comment) {
|
||||
// TODO(charlie): Modify RustPython to include the comment text in the token.
|
||||
let comment_text = locator.slice_source_code_range(&Range {
|
||||
location: start,
|
||||
end_location: end,
|
||||
});
|
||||
if !matches!(tok, Tok::Comment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if comment_text == "# isort: split" {
|
||||
splits.push(start.row());
|
||||
} else if comment_text == "# isort: skip_file" {
|
||||
skip_file = true;
|
||||
} else if off.is_some() {
|
||||
if comment_text == "# isort: on" {
|
||||
if let Some(start) = off {
|
||||
for row in start.row() + 1..=end.row() {
|
||||
exclusions.insert(row);
|
||||
}
|
||||
// TODO(charlie): Modify RustPython to include the comment text in the token.
|
||||
let comment_text = locator.slice_source_code_range(&Range {
|
||||
location: start,
|
||||
end_location: end,
|
||||
});
|
||||
|
||||
if comment_text == "# isort: split" {
|
||||
splits.push(start.row());
|
||||
} else if comment_text == "# isort: skip_file" {
|
||||
skip_file = true;
|
||||
} else if off.is_some() {
|
||||
if comment_text == "# isort: on" {
|
||||
if let Some(start) = off {
|
||||
for row in start.row() + 1..=end.row() {
|
||||
exclusions.insert(row);
|
||||
}
|
||||
off = None;
|
||||
}
|
||||
} else {
|
||||
if comment_text.contains("isort: skip") {
|
||||
exclusions.insert(start.row());
|
||||
} else if comment_text == "# isort: off" {
|
||||
off = Some(start);
|
||||
}
|
||||
off = None;
|
||||
}
|
||||
} else {
|
||||
if comment_text.contains("isort: skip") {
|
||||
exclusions.insert(start.row());
|
||||
} else if comment_text == "# isort: off" {
|
||||
off = Some(start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
63
src/flake8_annotations/helpers.rs
Normal file
63
src/flake8_annotations/helpers.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use rustpython_ast::{Arguments, Expr, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::cast;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind};
|
||||
use crate::visibility;
|
||||
|
||||
pub(super) fn match_function_def(
|
||||
stmt: &Stmt,
|
||||
) -> (&str, &Arguments, &Option<Box<Expr>>, &Vec<Stmt>) {
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef {
|
||||
name,
|
||||
args,
|
||||
returns,
|
||||
body,
|
||||
..
|
||||
}
|
||||
| StmtKind::AsyncFunctionDef {
|
||||
name,
|
||||
args,
|
||||
returns,
|
||||
body,
|
||||
..
|
||||
} => (name, args, returns, body),
|
||||
_ => panic!("Found non-FunctionDef in match_name"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the name of the function, if it's overloaded.
|
||||
pub fn overloaded_name(checker: &Checker, definition: &Definition) -> Option<String> {
|
||||
if let DefinitionKind::Function(stmt)
|
||||
| DefinitionKind::NestedFunction(stmt)
|
||||
| DefinitionKind::Method(stmt) = definition.kind
|
||||
{
|
||||
if visibility::is_overload(checker, cast::decorator_list(stmt)) {
|
||||
let (name, ..) = match_function_def(stmt);
|
||||
Some(name.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the definition is the implementation for an overloaded
|
||||
/// function.
|
||||
pub fn is_overload_impl(checker: &Checker, definition: &Definition, overloaded_name: &str) -> bool {
|
||||
if let DefinitionKind::Function(stmt)
|
||||
| DefinitionKind::NestedFunction(stmt)
|
||||
| DefinitionKind::Method(stmt) = definition.kind
|
||||
{
|
||||
if visibility::is_overload(checker, cast::decorator_list(stmt)) {
|
||||
false
|
||||
} else {
|
||||
let (name, ..) = match_function_def(stmt);
|
||||
name == overloaded_name
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod helpers;
|
||||
pub mod plugins;
|
||||
pub mod settings;
|
||||
|
||||
@@ -134,4 +135,24 @@ mod tests {
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allow_overload() -> Result<()> {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_annotations/allow_overload.py"),
|
||||
&Settings {
|
||||
..Settings::for_rules(vec![
|
||||
CheckCode::ANN201,
|
||||
CheckCode::ANN202,
|
||||
CheckCode::ANN204,
|
||||
CheckCode::ANN205,
|
||||
CheckCode::ANN206,
|
||||
])
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use rustpython_ast::{Arguments, Constant, Expr, ExprKind, Stmt, StmtKind};
|
||||
use rustpython_ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::ast::visitor;
|
||||
use crate::ast::visitor::Visitor;
|
||||
use crate::ast::{cast, visitor};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{CheckCode, CheckKind};
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind};
|
||||
use crate::flake8_annotations::helpers::match_function_def;
|
||||
use crate::visibility::Visibility;
|
||||
use crate::{visibility, Check};
|
||||
|
||||
@@ -61,26 +62,6 @@ where
|
||||
};
|
||||
}
|
||||
|
||||
fn match_function_def(stmt: &Stmt) -> (&str, &Arguments, &Option<Box<Expr>>, &Vec<Stmt>) {
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef {
|
||||
name,
|
||||
args,
|
||||
returns,
|
||||
body,
|
||||
..
|
||||
}
|
||||
| StmtKind::AsyncFunctionDef {
|
||||
name,
|
||||
args,
|
||||
returns,
|
||||
body,
|
||||
..
|
||||
} => (name, args, returns, body),
|
||||
_ => panic!("Found non-FunctionDef in match_name"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate flake8-annotation checks for a given `Definition`.
|
||||
pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &Visibility) {
|
||||
// TODO(charlie): Consider using the AST directly here rather than `Definition`.
|
||||
@@ -211,7 +192,10 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
.chain(args.kwonlyargs.iter())
|
||||
.skip(
|
||||
// If this is a non-static method, skip `cls` or `self`.
|
||||
usize::from(!visibility::is_staticmethod(checker, stmt)),
|
||||
usize::from(!visibility::is_staticmethod(
|
||||
checker,
|
||||
cast::decorator_list(stmt),
|
||||
)),
|
||||
)
|
||||
{
|
||||
// ANN401 for dynamically typed arguments
|
||||
@@ -283,10 +267,10 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
}
|
||||
|
||||
// ANN101, ANN102
|
||||
if !visibility::is_staticmethod(checker, stmt) {
|
||||
if !visibility::is_staticmethod(checker, cast::decorator_list(stmt)) {
|
||||
if let Some(arg) = args.args.first() {
|
||||
if arg.node.annotation.is_none() {
|
||||
if visibility::is_classmethod(checker, stmt) {
|
||||
if visibility::is_classmethod(checker, cast::decorator_list(stmt)) {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN102) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::MissingTypeCls(arg.node.arg.to_string()),
|
||||
@@ -319,14 +303,14 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
return;
|
||||
}
|
||||
|
||||
if visibility::is_classmethod(checker, stmt) {
|
||||
if visibility::is_classmethod(checker, cast::decorator_list(stmt)) {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN206) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::MissingReturnTypeClassMethod(name.to_string()),
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
} else if visibility::is_staticmethod(checker, stmt) {
|
||||
} else if visibility::is_staticmethod(checker, cast::decorator_list(stmt)) {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN205) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::MissingReturnTypeStaticMethod(name.to_string()),
|
||||
|
||||
@@ -1,22 +1,51 @@
|
||||
//! Settings for the `flake-annotations` plugin.
|
||||
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
/// Allow omission of a return type hint for `__init__` if at least one
|
||||
/// argument is annotated.
|
||||
#[option(
|
||||
doc = r#"
|
||||
Whether to allow the omission of a return type hint for `__init__` if at least one
|
||||
argument is annotated.
|
||||
"#,
|
||||
default = "false",
|
||||
value_type = "bool",
|
||||
example = "mypy-init-return = true"
|
||||
)]
|
||||
pub mypy_init_return: Option<bool>,
|
||||
/// Suppress ANN000-level errors for dummy arguments, like `_`.
|
||||
#[option(
|
||||
doc = r#"
|
||||
Whether to suppress `ANN000`-level errors for arguments matching the "dummy" variable
|
||||
regex (like `_`).
|
||||
"#,
|
||||
default = "false",
|
||||
value_type = "bool",
|
||||
example = "suppress-dummy-args = true"
|
||||
)]
|
||||
pub suppress_dummy_args: Option<bool>,
|
||||
/// Suppress ANN200-level errors for functions that meet one of the
|
||||
/// following criteria:
|
||||
/// - Contain no `return` statement
|
||||
/// - Explicit `return` statement(s) all return `None` (explicitly or
|
||||
/// implicitly).
|
||||
#[option(
|
||||
doc = r#"
|
||||
Whether to suppress `ANN200`-level errors for functions that meet either of the
|
||||
following criteria:
|
||||
|
||||
- Contain no `return` statement.
|
||||
- Explicit `return` statement(s) all return `None` (explicitly or implicitly).
|
||||
"#,
|
||||
default = "false",
|
||||
value_type = "bool",
|
||||
example = "suppress-none-returning = true"
|
||||
)]
|
||||
pub suppress_none_returning: Option<bool>,
|
||||
/// Suppress ANN401 for dynamically typed *args and **kwargs.
|
||||
#[option(
|
||||
doc = "Whether to suppress `ANN401` for dynamically typed `*args` and `**kwargs` \
|
||||
arguments.",
|
||||
default = "false",
|
||||
value_type = "bool",
|
||||
example = "allow-star-arg-any = true"
|
||||
)]
|
||||
pub allow_star_arg_any: Option<bool>,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: src/flake8_annotations/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
MissingReturnTypePublicFunction: bar
|
||||
location:
|
||||
row: 29
|
||||
column: 4
|
||||
end_location:
|
||||
row: 35
|
||||
column: 0
|
||||
fix: ~
|
||||
|
||||
@@ -1,21 +1,42 @@
|
||||
use rustpython_ast::{Excepthandler, ExcepthandlerKind, ExprKind};
|
||||
use rustpython_ast::{Expr, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
pub fn blind_except(checker: &mut Checker, handlers: &[Excepthandler]) {
|
||||
for handler in handlers {
|
||||
let ExcepthandlerKind::ExceptHandler { type_: Some(type_), .. } = &handler.node else {
|
||||
continue;
|
||||
};
|
||||
let ExprKind::Name { id, .. } = &type_.node else {
|
||||
continue;
|
||||
};
|
||||
for exception in ["BaseException", "Exception"] {
|
||||
if id == exception {
|
||||
/// BLE001
|
||||
pub fn blind_except(
|
||||
checker: &mut Checker,
|
||||
type_: Option<&Expr>,
|
||||
name: Option<&str>,
|
||||
body: &[Stmt],
|
||||
) {
|
||||
let Some(type_) = type_ else {
|
||||
return;
|
||||
};
|
||||
let ExprKind::Name { id, .. } = &type_.node else {
|
||||
return;
|
||||
};
|
||||
for exception in ["BaseException", "Exception"] {
|
||||
if id == exception && checker.is_builtin(exception) {
|
||||
// If the exception is re-raised, don't flag an error.
|
||||
if !body.iter().any(|stmt| {
|
||||
if let StmtKind::Raise { exc, .. } = &stmt.node {
|
||||
if let Some(exc) = exc {
|
||||
if let ExprKind::Name { id, .. } = &exc.node {
|
||||
name.map_or(false, |name| name == id)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::BlindExcept,
|
||||
CheckKind::BlindExcept(id.to_string()),
|
||||
Range::from_located(type_),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -2,31 +2,8 @@
|
||||
source: src/flake8_blind_except/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: BlindExcept
|
||||
location:
|
||||
row: 5
|
||||
column: 7
|
||||
end_location:
|
||||
row: 5
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind: BlindExcept
|
||||
location:
|
||||
row: 13
|
||||
column: 7
|
||||
end_location:
|
||||
row: 13
|
||||
column: 20
|
||||
fix: ~
|
||||
- kind: BlindExcept
|
||||
location:
|
||||
row: 23
|
||||
column: 7
|
||||
end_location:
|
||||
row: 23
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind: BlindExcept
|
||||
- kind:
|
||||
BlindExcept: BaseException
|
||||
location:
|
||||
row: 25
|
||||
column: 7
|
||||
@@ -34,7 +11,8 @@ expression: checks
|
||||
row: 25
|
||||
column: 20
|
||||
fix: ~
|
||||
- kind: BlindExcept
|
||||
- kind:
|
||||
BlindExcept: Exception
|
||||
location:
|
||||
row: 31
|
||||
column: 7
|
||||
@@ -42,15 +20,8 @@ expression: checks
|
||||
row: 31
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind: BlindExcept
|
||||
location:
|
||||
row: 36
|
||||
column: 11
|
||||
end_location:
|
||||
row: 36
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: BlindExcept
|
||||
- kind:
|
||||
BlindExcept: Exception
|
||||
location:
|
||||
row: 42
|
||||
column: 7
|
||||
@@ -58,7 +29,8 @@ expression: checks
|
||||
row: 42
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind: BlindExcept
|
||||
- kind:
|
||||
BlindExcept: BaseException
|
||||
location:
|
||||
row: 45
|
||||
column: 11
|
||||
@@ -66,15 +38,8 @@ expression: checks
|
||||
row: 45
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: BlindExcept
|
||||
location:
|
||||
row: 52
|
||||
column: 11
|
||||
end_location:
|
||||
row: 52
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: BlindExcept
|
||||
- kind:
|
||||
BlindExcept: Exception
|
||||
location:
|
||||
row: 54
|
||||
column: 7
|
||||
@@ -82,4 +47,22 @@ expression: checks
|
||||
row: 54
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind:
|
||||
BlindExcept: Exception
|
||||
location:
|
||||
row: 60
|
||||
column: 7
|
||||
end_location:
|
||||
row: 60
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind:
|
||||
BlindExcept: BaseException
|
||||
location:
|
||||
row: 62
|
||||
column: 7
|
||||
end_location:
|
||||
row: 62
|
||||
column: 20
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ mod tests {
|
||||
#[test_case(CheckCode::B026, Path::new("B026.py"); "B026")]
|
||||
#[test_case(CheckCode::B027, Path::new("B027.py"); "B027")]
|
||||
#[test_case(CheckCode::B904, Path::new("B904.py"); "B904")]
|
||||
#[test_case(CheckCode::B905, Path::new("B905.py"); "B905")]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||
let mut checks = test_path(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::FxHashSet;
|
||||
use rustpython_ast::{
|
||||
Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Location, Stmt,
|
||||
};
|
||||
@@ -27,9 +26,9 @@ fn duplicate_handler_exceptions<'a>(
|
||||
checker: &mut Checker,
|
||||
expr: &'a Expr,
|
||||
elts: &'a [Expr],
|
||||
) -> BTreeSet<Vec<&'a str>> {
|
||||
let mut seen: BTreeSet<Vec<&str>> = BTreeSet::default();
|
||||
let mut duplicates: BTreeSet<Vec<&str>> = BTreeSet::default();
|
||||
) -> FxHashSet<Vec<&'a str>> {
|
||||
let mut seen: FxHashSet<Vec<&str>> = FxHashSet::default();
|
||||
let mut duplicates: FxHashSet<Vec<&str>> = FxHashSet::default();
|
||||
let mut unique_elts: Vec<&Expr> = Vec::default();
|
||||
for type_ in elts {
|
||||
let call_path = helpers::collect_call_paths(type_);
|
||||
@@ -76,8 +75,8 @@ fn duplicate_handler_exceptions<'a>(
|
||||
}
|
||||
|
||||
pub fn duplicate_exceptions(checker: &mut Checker, stmt: &Stmt, handlers: &[Excepthandler]) {
|
||||
let mut seen: BTreeSet<Vec<&str>> = BTreeSet::default();
|
||||
let mut duplicates: BTreeSet<Vec<&str>> = BTreeSet::default();
|
||||
let mut seen: FxHashSet<Vec<&str>> = FxHashSet::default();
|
||||
let mut duplicates: FxHashSet<Vec<&str>> = FxHashSet::default();
|
||||
for handler in handlers {
|
||||
let ExcepthandlerKind::ExceptHandler { type_: Some(type_), .. } = &handler.node else {
|
||||
continue;
|
||||
|
||||
@@ -23,6 +23,7 @@ pub use unused_loop_control_variable::unused_loop_control_variable;
|
||||
pub use useless_comparison::useless_comparison;
|
||||
pub use useless_contextlib_suppress::useless_contextlib_suppress;
|
||||
pub use useless_expression::useless_expression;
|
||||
pub use zip_without_explicit_strict::zip_without_explicit_strict;
|
||||
|
||||
mod abstract_base_class;
|
||||
mod assert_false;
|
||||
@@ -49,3 +50,4 @@ mod unused_loop_control_variable;
|
||||
mod useless_comparison;
|
||||
mod useless_contextlib_suppress;
|
||||
mod useless_expression;
|
||||
mod zip_without_explicit_strict;
|
||||
|
||||
31
src/flake8_bugbear/plugins/zip_without_explicit_strict.rs
Normal file
31
src/flake8_bugbear/plugins/zip_without_explicit_strict.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use rustpython_ast::{Expr, ExprKind, Keyword};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// B905
|
||||
pub fn zip_without_explicit_strict(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
kwargs: &[Keyword],
|
||||
) {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
if id == "zip"
|
||||
&& checker.is_builtin("zip")
|
||||
&& !kwargs.iter().any(|keyword| {
|
||||
keyword
|
||||
.node
|
||||
.arg
|
||||
.as_ref()
|
||||
.map_or(false, |name| name == "strict")
|
||||
})
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::ZipWithoutExplicitStrict,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,23 @@
|
||||
//! Settings for the `flake8-bugbear` plugin.
|
||||
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
doc = r#"
|
||||
Additional callable functions to consider "immutable" when evaluating, e.g.,
|
||||
`no-mutable-default-argument` checks (`B006`).
|
||||
"#,
|
||||
default = r#"[]"#,
|
||||
value_type = "Vec<String>",
|
||||
example = r#"
|
||||
# Allow default arguments like, e.g., `data: List[str] = fastapi.Query(None)`.
|
||||
extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"]
|
||||
"#
|
||||
)]
|
||||
pub extend_immutable_calls: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
source: src/flake8_bugbear/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: ZipWithoutExplicitStrict
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 1
|
||||
column: 5
|
||||
fix: ~
|
||||
- kind: ZipWithoutExplicitStrict
|
||||
location:
|
||||
row: 2
|
||||
column: 0
|
||||
end_location:
|
||||
row: 2
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind: ZipWithoutExplicitStrict
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind: ZipWithoutExplicitStrict
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: ZipWithoutExplicitStrict
|
||||
location:
|
||||
row: 4
|
||||
column: 15
|
||||
end_location:
|
||||
row: 4
|
||||
column: 23
|
||||
fix: ~
|
||||
- kind: ZipWithoutExplicitStrict
|
||||
location:
|
||||
row: 5
|
||||
column: 4
|
||||
end_location:
|
||||
row: 5
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind: ZipWithoutExplicitStrict
|
||||
location:
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 26
|
||||
fix: ~
|
||||
|
||||
36
src/flake8_import_conventions/checks.rs
Normal file
36
src/flake8_import_conventions/checks.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_ast::Stmt;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// ICN001
|
||||
pub fn check_conventional_import(
|
||||
import_from: &Stmt,
|
||||
name: &str,
|
||||
asname: Option<&str>,
|
||||
conventions: &FxHashMap<String, String>,
|
||||
) -> Option<Check> {
|
||||
let mut is_valid_import = true;
|
||||
if let Some(expected_alias) = conventions.get(name) {
|
||||
if !expected_alias.is_empty() {
|
||||
if let Some(alias) = asname {
|
||||
if expected_alias != alias {
|
||||
is_valid_import = false;
|
||||
}
|
||||
} else {
|
||||
is_valid_import = false;
|
||||
}
|
||||
}
|
||||
if !is_valid_import {
|
||||
return Some(Check::new(
|
||||
CheckKind::ImportAliasIsNotConventional(
|
||||
name.to_string(),
|
||||
expected_alias.to_string(),
|
||||
),
|
||||
Range::from_located(import_from),
|
||||
));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
101
src/flake8_import_conventions/mod.rs
Normal file
101
src/flake8_import_conventions/mod.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
pub mod checks;
|
||||
pub mod settings;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::{flake8_import_conventions, Settings};
|
||||
|
||||
#[test]
|
||||
fn defaults() -> Result<()> {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_import_conventions/defaults.py"),
|
||||
&Settings::for_rule(CheckCode::ICN001),
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!("defaults", checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom() -> Result<()> {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_import_conventions/custom.py"),
|
||||
&Settings {
|
||||
flake8_import_conventions:
|
||||
flake8_import_conventions::settings::Settings::from_options(
|
||||
flake8_import_conventions::settings::Options {
|
||||
aliases: None,
|
||||
extend_aliases: Some(FxHashMap::from_iter([
|
||||
("dask.array".to_string(), "da".to_string()),
|
||||
("dask.dataframe".to_string(), "dd".to_string()),
|
||||
])),
|
||||
},
|
||||
),
|
||||
..Settings::for_rule(CheckCode::ICN001)
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!("custom", checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_defaults() -> Result<()> {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_import_conventions/remove_default.py"),
|
||||
&Settings {
|
||||
flake8_import_conventions:
|
||||
flake8_import_conventions::settings::Settings::from_options(
|
||||
flake8_import_conventions::settings::Options {
|
||||
aliases: Some(FxHashMap::from_iter([
|
||||
("altair".to_string(), "alt".to_string()),
|
||||
("matplotlib.pyplot".to_string(), "plt".to_string()),
|
||||
("pandas".to_string(), "pd".to_string()),
|
||||
("seaborn".to_string(), "sns".to_string()),
|
||||
])),
|
||||
extend_aliases: None,
|
||||
},
|
||||
),
|
||||
..Settings::for_rule(CheckCode::ICN001)
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!("remove_default", checks);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn override_defaults() -> Result<()> {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_import_conventions/override_default.py"),
|
||||
&Settings {
|
||||
flake8_import_conventions:
|
||||
flake8_import_conventions::settings::Settings::from_options(
|
||||
flake8_import_conventions::settings::Options {
|
||||
aliases: None,
|
||||
extend_aliases: Some(FxHashMap::from_iter([(
|
||||
"numpy".to_string(),
|
||||
"nmp".to_string(),
|
||||
)])),
|
||||
},
|
||||
),
|
||||
..Settings::for_rule(CheckCode::ICN001)
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!("override_default", checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
94
src/flake8_import_conventions/settings.rs
Normal file
94
src/flake8_import_conventions/settings.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
//! Settings for import conventions.
|
||||
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use itertools::Itertools;
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const CONVENTIONAL_ALIASES: &[(&str, &str)] = &[
|
||||
("altair", "alt"),
|
||||
("matplotlib.pyplot", "plt"),
|
||||
("numpy", "np"),
|
||||
("pandas", "pd"),
|
||||
("seaborn", "sns"),
|
||||
];
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
doc = "The conventional aliases for imports. These aliases can be extended by the \
|
||||
`extend_aliases` option.",
|
||||
default = r#"{"altair": "alt", "matplotlib.pyplot": "plt", "numpy": "np", "pandas": "pd", "seaborn": "sns"}"#,
|
||||
value_type = "FxHashMap<String, String>",
|
||||
example = r#"
|
||||
# Declare the default aliases.
|
||||
altair = "alt"
|
||||
matplotlib.pyplot = "plt"
|
||||
numpy = "np"
|
||||
pandas = "pd"
|
||||
seaborn = "sns"
|
||||
"#
|
||||
)]
|
||||
pub aliases: Option<FxHashMap<String, String>>,
|
||||
#[option(
|
||||
doc = "A mapping of modules to their conventional import aliases. These aliases will be \
|
||||
added to the `aliases` mapping.",
|
||||
default = r#"{}"#,
|
||||
value_type = "FxHashMap<String, String>",
|
||||
example = r#"
|
||||
# Declare a custom alias for the `matplotlib` module.
|
||||
"dask.dataframe" = "dd"
|
||||
"#
|
||||
)]
|
||||
pub extend_aliases: Option<FxHashMap<String, String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Settings {
|
||||
pub aliases: FxHashMap<String, String>,
|
||||
}
|
||||
|
||||
impl Hash for Settings {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
for value in self.aliases.iter().sorted() {
|
||||
value.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_aliases() -> FxHashMap<String, String> {
|
||||
CONVENTIONAL_ALIASES
|
||||
.iter()
|
||||
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
|
||||
.collect::<FxHashMap<_, _>>()
|
||||
}
|
||||
|
||||
fn resolve_aliases(options: Options) -> FxHashMap<String, String> {
|
||||
let mut aliases = match options.aliases {
|
||||
Some(options_aliases) => options_aliases,
|
||||
None => default_aliases(),
|
||||
};
|
||||
if let Some(extend_aliases) = options.extend_aliases {
|
||||
aliases.extend(extend_aliases);
|
||||
}
|
||||
aliases
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn from_options(options: Options) -> Self {
|
||||
Self {
|
||||
aliases: resolve_aliases(options),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
aliases: default_aliases(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
---
|
||||
source: src/flake8_import_conventions/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- altair
|
||||
- alt
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- dask.array
|
||||
- da
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- dask.dataframe
|
||||
- dd
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- matplotlib.pyplot
|
||||
- plt
|
||||
location:
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- numpy
|
||||
- np
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- pandas
|
||||
- pd
|
||||
location:
|
||||
row: 8
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- seaborn
|
||||
- sns
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- altair
|
||||
- alt
|
||||
location:
|
||||
row: 11
|
||||
column: 0
|
||||
end_location:
|
||||
row: 11
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- matplotlib.pyplot
|
||||
- plt
|
||||
location:
|
||||
row: 12
|
||||
column: 0
|
||||
end_location:
|
||||
row: 12
|
||||
column: 32
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- dask.array
|
||||
- da
|
||||
location:
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 13
|
||||
column: 27
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- dask.dataframe
|
||||
- dd
|
||||
location:
|
||||
row: 14
|
||||
column: 0
|
||||
end_location:
|
||||
row: 14
|
||||
column: 28
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- numpy
|
||||
- np
|
||||
location:
|
||||
row: 15
|
||||
column: 0
|
||||
end_location:
|
||||
row: 15
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- pandas
|
||||
- pd
|
||||
location:
|
||||
row: 16
|
||||
column: 0
|
||||
end_location:
|
||||
row: 16
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- seaborn
|
||||
- sns
|
||||
location:
|
||||
row: 17
|
||||
column: 0
|
||||
end_location:
|
||||
row: 17
|
||||
column: 22
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
---
|
||||
source: src/flake8_import_conventions/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- altair
|
||||
- alt
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- matplotlib.pyplot
|
||||
- plt
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- numpy
|
||||
- np
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- pandas
|
||||
- pd
|
||||
location:
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- seaborn
|
||||
- sns
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- altair
|
||||
- alt
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- matplotlib.pyplot
|
||||
- plt
|
||||
location:
|
||||
row: 10
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 32
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- numpy
|
||||
- np
|
||||
location:
|
||||
row: 11
|
||||
column: 0
|
||||
end_location:
|
||||
row: 11
|
||||
column: 19
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- pandas
|
||||
- pd
|
||||
location:
|
||||
row: 12
|
||||
column: 0
|
||||
end_location:
|
||||
row: 12
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- seaborn
|
||||
- sns
|
||||
location:
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 13
|
||||
column: 22
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
---
|
||||
source: src/flake8_import_conventions/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- altair
|
||||
- alt
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- matplotlib.pyplot
|
||||
- plt
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- numpy
|
||||
- nmp
|
||||
location:
|
||||
row: 5
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- pandas
|
||||
- pd
|
||||
location:
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- seaborn
|
||||
- sns
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- altair
|
||||
- alt
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- matplotlib.pyplot
|
||||
- plt
|
||||
location:
|
||||
row: 10
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 32
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- numpy
|
||||
- nmp
|
||||
location:
|
||||
row: 11
|
||||
column: 0
|
||||
end_location:
|
||||
row: 11
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- pandas
|
||||
- pd
|
||||
location:
|
||||
row: 12
|
||||
column: 0
|
||||
end_location:
|
||||
row: 12
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- seaborn
|
||||
- sns
|
||||
location:
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 13
|
||||
column: 22
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
source: src/flake8_import_conventions/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- altair
|
||||
- alt
|
||||
location:
|
||||
row: 3
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- matplotlib.pyplot
|
||||
- plt
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- pandas
|
||||
- pd
|
||||
location:
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- seaborn
|
||||
- sns
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- altair
|
||||
- alt
|
||||
location:
|
||||
row: 9
|
||||
column: 0
|
||||
end_location:
|
||||
row: 9
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- matplotlib.pyplot
|
||||
- plt
|
||||
location:
|
||||
row: 10
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 32
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- pandas
|
||||
- pd
|
||||
location:
|
||||
row: 12
|
||||
column: 0
|
||||
end_location:
|
||||
row: 12
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind:
|
||||
ImportAliasIsNotConventional:
|
||||
- seaborn
|
||||
- sns
|
||||
location:
|
||||
row: 13
|
||||
column: 0
|
||||
end_location:
|
||||
row: 13
|
||||
column: 22
|
||||
fix: ~
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Settings for the `flake8-quotes` plugin.
|
||||
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
@@ -9,12 +10,55 @@ pub enum Quote {
|
||||
Double,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
doc = r#"
|
||||
Quote style to prefer for inline strings (either "single" (`'`) or "double" (`"`)).
|
||||
"#,
|
||||
default = r#""double""#,
|
||||
value_type = "Quote",
|
||||
example = r#"
|
||||
inline-quotes = "single"
|
||||
"#
|
||||
)]
|
||||
pub inline_quotes: Option<Quote>,
|
||||
#[option(
|
||||
doc = r#"
|
||||
Quote style to prefer for multiline strings (either "single" (`'`) or "double" (`"`)).
|
||||
"#,
|
||||
default = r#""double""#,
|
||||
value_type = "Quote",
|
||||
example = r#"
|
||||
multiline-quotes = "single"
|
||||
"#
|
||||
)]
|
||||
pub multiline_quotes: Option<Quote>,
|
||||
#[option(
|
||||
doc = r#"
|
||||
Quote style to prefer for docstrings (either "single" (`'`) or "double" (`"`)).
|
||||
"#,
|
||||
default = r#""double""#,
|
||||
value_type = "Quote",
|
||||
example = r#"
|
||||
docstring-quotes = "single"
|
||||
"#
|
||||
)]
|
||||
pub docstring_quotes: Option<Quote>,
|
||||
#[option(
|
||||
doc = r#"
|
||||
Whether to avoid using single quotes if a string contains single quotes, or vice-versa
|
||||
with double quotes, as per [PEP8](https://peps.python.org/pep-0008/#string-quotes).
|
||||
This minimizes the need to escape quotation marks within strings.
|
||||
"#,
|
||||
default = r#"true"#,
|
||||
value_type = "bool",
|
||||
example = r#"
|
||||
# Don't bother trying to avoid escapes.
|
||||
avoid-escape = false
|
||||
"#
|
||||
)]
|
||||
pub avoid_escape: Option<bool>,
|
||||
}
|
||||
|
||||
|
||||
@@ -15,12 +15,12 @@ mod tests {
|
||||
#[test]
|
||||
fn ban_parent_imports() -> Result<()> {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_tidy_imports/I252.py"),
|
||||
Path::new("./resources/test/fixtures/flake8_tidy_imports/TID252.py"),
|
||||
&Settings {
|
||||
flake8_tidy_imports: flake8_tidy_imports::settings::Settings {
|
||||
ban_relative_imports: Strictness::Parents,
|
||||
},
|
||||
..Settings::for_rules(vec![CheckCode::I252])
|
||||
..Settings::for_rules(vec![CheckCode::TID252])
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
@@ -32,12 +32,12 @@ mod tests {
|
||||
#[test]
|
||||
fn ban_all_imports() -> Result<()> {
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_tidy_imports/I252.py"),
|
||||
Path::new("./resources/test/fixtures/flake8_tidy_imports/TID252.py"),
|
||||
&Settings {
|
||||
flake8_tidy_imports: flake8_tidy_imports::settings::Settings {
|
||||
ban_relative_imports: Strictness::All,
|
||||
},
|
||||
..Settings::for_rules(vec![CheckCode::I252])
|
||||
..Settings::for_rules(vec![CheckCode::TID252])
|
||||
},
|
||||
true,
|
||||
)?;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Settings for the `flake8-tidy-imports` plugin.
|
||||
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
@@ -9,9 +10,21 @@ pub enum Strictness {
|
||||
All,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
doc = r#"
|
||||
Whether to ban all relative imports (`"all"`), or only those imports that extend into
|
||||
the parent module and beyond (`"parents"`).
|
||||
"#,
|
||||
default = r#""parents""#,
|
||||
value_type = "Strictness",
|
||||
example = r#"
|
||||
# Disallow all relative imports.
|
||||
ban-relative-imports = "all"
|
||||
"#
|
||||
)]
|
||||
pub ban_relative_imports: Option<Strictness>,
|
||||
}
|
||||
|
||||
|
||||
19
src/flake8_unused_arguments/helpers.rs
Normal file
19
src/flake8_unused_arguments/helpers.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use rustpython_ast::{Constant, ExprKind, Stmt, StmtKind};
|
||||
|
||||
pub fn is_empty(body: &[Stmt]) -> bool {
|
||||
match &body {
|
||||
[] => true,
|
||||
// Also allow: raise NotImplementedError, raise NotImplemented
|
||||
[stmt] => match &stmt.node {
|
||||
StmtKind::Pass => true,
|
||||
StmtKind::Expr { value } => match &value.node {
|
||||
ExprKind::Constant { value, .. } => {
|
||||
matches!(value, Constant::Str(_) | Constant::Ellipsis)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
35
src/flake8_unused_arguments/mod.rs
Normal file
35
src/flake8_unused_arguments/mod.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
mod helpers;
|
||||
pub mod plugins;
|
||||
mod types;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::convert::AsRef;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
use crate::linter::test_path;
|
||||
use crate::settings;
|
||||
|
||||
#[test_case(CheckCode::ARG001, Path::new("ARG.py"); "ARG001")]
|
||||
#[test_case(CheckCode::ARG002, Path::new("ARG.py"); "ARG002")]
|
||||
#[test_case(CheckCode::ARG003, Path::new("ARG.py"); "ARG003")]
|
||||
#[test_case(CheckCode::ARG004, Path::new("ARG.py"); "ARG004")]
|
||||
#[test_case(CheckCode::ARG005, Path::new("ARG.py"); "ARG005")]
|
||||
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures/flake8_unused_arguments")
|
||||
.join(path)
|
||||
.as_path(),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
true,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
insta::assert_yaml_snapshot!(snapshot, checks);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
184
src/flake8_unused_arguments/plugins.rs
Normal file
184
src/flake8_unused_arguments/plugins.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
use std::iter;
|
||||
|
||||
use regex::Regex;
|
||||
use rustc_hash::FxHashMap;
|
||||
use rustpython_ast::{Arg, Arguments};
|
||||
|
||||
use crate::ast::function_type;
|
||||
use crate::ast::function_type::FunctionType;
|
||||
use crate::ast::helpers::collect_arg_names;
|
||||
use crate::ast::types::{Binding, BindingKind, FunctionDef, Lambda, Scope, ScopeKind};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::flake8_unused_arguments::helpers;
|
||||
use crate::flake8_unused_arguments::types::Argumentable;
|
||||
use crate::{visibility, Check};
|
||||
|
||||
/// Check a plain function for unused arguments.
|
||||
fn function(
|
||||
argumentable: &Argumentable,
|
||||
args: &Arguments,
|
||||
bindings: &FxHashMap<&str, Binding>,
|
||||
dummy_variable_rgx: &Regex,
|
||||
) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
for arg_name in collect_arg_names(args) {
|
||||
if let Some(binding) = bindings.get(arg_name) {
|
||||
if binding.used.is_none()
|
||||
&& matches!(binding.kind, BindingKind::Argument)
|
||||
&& !dummy_variable_rgx.is_match(arg_name)
|
||||
{
|
||||
checks.push(Check::new(
|
||||
argumentable.check_for(arg_name.to_string()),
|
||||
binding.range,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
checks
|
||||
}
|
||||
|
||||
/// Check a method for unused arguments.
|
||||
fn method(
|
||||
argumentable: &Argumentable,
|
||||
args: &Arguments,
|
||||
bindings: &FxHashMap<&str, Binding>,
|
||||
dummy_variable_rgx: &Regex,
|
||||
) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
for arg in args
|
||||
.posonlyargs
|
||||
.iter()
|
||||
.chain(args.args.iter())
|
||||
.skip(1)
|
||||
.chain(args.kwonlyargs.iter())
|
||||
.chain(iter::once::<Option<&Arg>>(args.vararg.as_deref()).flatten())
|
||||
.chain(iter::once::<Option<&Arg>>(args.kwarg.as_deref()).flatten())
|
||||
{
|
||||
if let Some(binding) = bindings.get(&arg.node.arg.as_str()) {
|
||||
if binding.used.is_none()
|
||||
&& matches!(binding.kind, BindingKind::Argument)
|
||||
&& !dummy_variable_rgx.is_match(arg.node.arg.as_str())
|
||||
{
|
||||
checks.push(Check::new(
|
||||
argumentable.check_for(arg.node.arg.to_string()),
|
||||
binding.range,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
checks
|
||||
}
|
||||
|
||||
/// ARG001, ARG002, ARG003, ARG004, ARG005
|
||||
pub fn unused_arguments(checker: &Checker, parent: &Scope, scope: &Scope) -> Vec<Check> {
|
||||
match &scope.kind {
|
||||
ScopeKind::Function(FunctionDef {
|
||||
name,
|
||||
args,
|
||||
body,
|
||||
decorator_list,
|
||||
..
|
||||
}) => {
|
||||
match function_type::classify(
|
||||
parent,
|
||||
name,
|
||||
decorator_list,
|
||||
&checker.from_imports,
|
||||
&checker.import_aliases,
|
||||
&checker.settings.pep8_naming.classmethod_decorators,
|
||||
&checker.settings.pep8_naming.staticmethod_decorators,
|
||||
) {
|
||||
FunctionType::Function => {
|
||||
if checker
|
||||
.settings
|
||||
.enabled
|
||||
.contains(Argumentable::Function.check_code())
|
||||
{
|
||||
function(
|
||||
&Argumentable::Function,
|
||||
args,
|
||||
&scope.values,
|
||||
&checker.settings.dummy_variable_rgx,
|
||||
)
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
FunctionType::Method => {
|
||||
if checker
|
||||
.settings
|
||||
.enabled
|
||||
.contains(Argumentable::Method.check_code())
|
||||
&& !helpers::is_empty(body)
|
||||
&& !visibility::is_abstract(checker, decorator_list)
|
||||
&& !visibility::is_override(checker, decorator_list)
|
||||
{
|
||||
method(
|
||||
&Argumentable::Method,
|
||||
args,
|
||||
&scope.values,
|
||||
&checker.settings.dummy_variable_rgx,
|
||||
)
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
FunctionType::ClassMethod => {
|
||||
if checker
|
||||
.settings
|
||||
.enabled
|
||||
.contains(Argumentable::ClassMethod.check_code())
|
||||
&& !helpers::is_empty(body)
|
||||
&& !visibility::is_abstract(checker, decorator_list)
|
||||
&& !visibility::is_override(checker, decorator_list)
|
||||
{
|
||||
method(
|
||||
&Argumentable::ClassMethod,
|
||||
args,
|
||||
&scope.values,
|
||||
&checker.settings.dummy_variable_rgx,
|
||||
)
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
FunctionType::StaticMethod => {
|
||||
if checker
|
||||
.settings
|
||||
.enabled
|
||||
.contains(Argumentable::StaticMethod.check_code())
|
||||
&& !helpers::is_empty(body)
|
||||
&& !visibility::is_abstract(checker, decorator_list)
|
||||
&& !visibility::is_override(checker, decorator_list)
|
||||
{
|
||||
function(
|
||||
&Argumentable::StaticMethod,
|
||||
args,
|
||||
&scope.values,
|
||||
&checker.settings.dummy_variable_rgx,
|
||||
)
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ScopeKind::Lambda(Lambda { args, .. }) => {
|
||||
if checker
|
||||
.settings
|
||||
.enabled
|
||||
.contains(Argumentable::Lambda.check_code())
|
||||
{
|
||||
function(
|
||||
&Argumentable::Lambda,
|
||||
args,
|
||||
&scope.values,
|
||||
&checker.settings.dummy_variable_rgx,
|
||||
)
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
_ => unreachable!("Expected ScopeKind::Function | ScopeKind::Lambda"),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
source: src/flake8_unused_arguments/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnusedFunctionArgument: self
|
||||
location:
|
||||
row: 8
|
||||
column: 6
|
||||
end_location:
|
||||
row: 8
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedFunctionArgument: x
|
||||
location:
|
||||
row: 8
|
||||
column: 12
|
||||
end_location:
|
||||
row: 8
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedFunctionArgument: cls
|
||||
location:
|
||||
row: 12
|
||||
column: 6
|
||||
end_location:
|
||||
row: 12
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedFunctionArgument: x
|
||||
location:
|
||||
row: 12
|
||||
column: 11
|
||||
end_location:
|
||||
row: 12
|
||||
column: 12
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedFunctionArgument: self
|
||||
location:
|
||||
row: 16
|
||||
column: 6
|
||||
end_location:
|
||||
row: 16
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedFunctionArgument: x
|
||||
location:
|
||||
row: 16
|
||||
column: 12
|
||||
end_location:
|
||||
row: 16
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedFunctionArgument: cls
|
||||
location:
|
||||
row: 20
|
||||
column: 6
|
||||
end_location:
|
||||
row: 20
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedFunctionArgument: x
|
||||
location:
|
||||
row: 20
|
||||
column: 11
|
||||
end_location:
|
||||
row: 20
|
||||
column: 12
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
source: src/flake8_unused_arguments/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnusedMethodArgument: x
|
||||
location:
|
||||
row: 34
|
||||
column: 16
|
||||
end_location:
|
||||
row: 34
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedMethodArgument: x
|
||||
location:
|
||||
row: 37
|
||||
column: 19
|
||||
end_location:
|
||||
row: 37
|
||||
column: 20
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedMethodArgument: x
|
||||
location:
|
||||
row: 40
|
||||
column: 15
|
||||
end_location:
|
||||
row: 40
|
||||
column: 16
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: src/flake8_unused_arguments/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnusedClassMethodArgument: x
|
||||
location:
|
||||
row: 44
|
||||
column: 15
|
||||
end_location:
|
||||
row: 44
|
||||
column: 16
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
source: src/flake8_unused_arguments/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnusedStaticMethodArgument: cls
|
||||
location:
|
||||
row: 48
|
||||
column: 10
|
||||
end_location:
|
||||
row: 48
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedStaticMethodArgument: x
|
||||
location:
|
||||
row: 48
|
||||
column: 15
|
||||
end_location:
|
||||
row: 48
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedStaticMethodArgument: x
|
||||
location:
|
||||
row: 52
|
||||
column: 10
|
||||
end_location:
|
||||
row: 52
|
||||
column: 11
|
||||
fix: ~
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: src/flake8_unused_arguments/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnusedLambdaArgument: x
|
||||
location:
|
||||
row: 27
|
||||
column: 7
|
||||
end_location:
|
||||
row: 27
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
32
src/flake8_unused_arguments/types.rs
Normal file
32
src/flake8_unused_arguments/types.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use crate::checks::{CheckCode, CheckKind};
|
||||
|
||||
/// An AST node that can contain arguments.
|
||||
pub enum Argumentable {
|
||||
Function,
|
||||
Method,
|
||||
ClassMethod,
|
||||
StaticMethod,
|
||||
Lambda,
|
||||
}
|
||||
|
||||
impl Argumentable {
|
||||
pub fn check_for(&self, name: String) -> CheckKind {
|
||||
match self {
|
||||
Argumentable::Function => CheckKind::UnusedFunctionArgument(name),
|
||||
Argumentable::Method => CheckKind::UnusedMethodArgument(name),
|
||||
Argumentable::ClassMethod => CheckKind::UnusedClassMethodArgument(name),
|
||||
Argumentable::StaticMethod => CheckKind::UnusedStaticMethodArgument(name),
|
||||
Argumentable::Lambda => CheckKind::UnusedLambdaArgument(name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_code(&self) -> &CheckCode {
|
||||
match self {
|
||||
Argumentable::Function => &CheckCode::ARG001,
|
||||
Argumentable::Method => &CheckCode::ARG002,
|
||||
Argumentable::ClassMethod => &CheckCode::ARG003,
|
||||
Argumentable::StaticMethod => &CheckCode::ARG004,
|
||||
Argumentable::Lambda => &CheckCode::ARG005,
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/fs.rs
12
src/fs.rs
@@ -1,5 +1,4 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -8,6 +7,7 @@ use anyhow::{anyhow, Result};
|
||||
use globset::GlobMatcher;
|
||||
use log::debug;
|
||||
use path_absolutize::{path_dedot, Absolutize};
|
||||
use rustc_hash::FxHashSet;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
use crate::checks::CheckCode;
|
||||
@@ -83,8 +83,8 @@ pub fn iter_python_files<'a>(
|
||||
/// Create tree set with codes matching the pattern/code pairs.
|
||||
pub(crate) fn ignores_from_path<'a>(
|
||||
path: &Path,
|
||||
pattern_code_pairs: &'a [(GlobMatcher, GlobMatcher, BTreeSet<CheckCode>)],
|
||||
) -> Result<BTreeSet<&'a CheckCode>> {
|
||||
pattern_code_pairs: &'a [(GlobMatcher, GlobMatcher, FxHashSet<CheckCode>)],
|
||||
) -> Result<FxHashSet<&'a CheckCode>> {
|
||||
let (file_path, file_basename) = extract_path_names(path)?;
|
||||
Ok(pattern_code_pairs
|
||||
.iter()
|
||||
@@ -97,7 +97,7 @@ pub(crate) fn ignores_from_path<'a>(
|
||||
|
||||
/// Convert any path to an absolute path (based on the current working
|
||||
/// directory).
|
||||
pub(crate) fn normalize_path(path: &Path) -> PathBuf {
|
||||
pub fn normalize_path(path: &Path) -> PathBuf {
|
||||
if let Ok(path) = path.absolutize() {
|
||||
return path.to_path_buf();
|
||||
}
|
||||
@@ -105,7 +105,7 @@ pub(crate) fn normalize_path(path: &Path) -> PathBuf {
|
||||
}
|
||||
|
||||
/// Convert any path to an absolute path (based on the specified project root).
|
||||
pub(crate) fn normalize_path_to(path: &Path, project_root: &Path) -> PathBuf {
|
||||
pub fn normalize_path_to(path: &Path, project_root: &Path) -> PathBuf {
|
||||
if let Ok(path) = path.absolutize_from(project_root) {
|
||||
return path.to_path_buf();
|
||||
}
|
||||
@@ -113,7 +113,7 @@ pub(crate) fn normalize_path_to(path: &Path, project_root: &Path) -> PathBuf {
|
||||
}
|
||||
|
||||
/// Convert an absolute path to be relative to the current working directory.
|
||||
pub(crate) fn relativize_path(path: &Path) -> Cow<str> {
|
||||
pub fn relativize_path(path: &Path) -> Cow<str> {
|
||||
if let Ok(path) = path.strip_prefix(&*path_dedot::CWD) {
|
||||
return path.to_string_lossy();
|
||||
}
|
||||
|
||||
@@ -559,6 +559,7 @@ mod tests {
|
||||
#[test_case(Path::new("force_wrap_aliases.py"))]
|
||||
#[test_case(Path::new("import_from_after_import.py"))]
|
||||
#[test_case(Path::new("insert_empty_lines.py"))]
|
||||
#[test_case(Path::new("insert_empty_lines.pyi"))]
|
||||
#[test_case(Path::new("leading_prefix.py"))]
|
||||
#[test_case(Path::new("no_reorder_within_section.py"))]
|
||||
#[test_case(Path::new("no_wrap_star.py"))]
|
||||
|
||||
@@ -2,15 +2,79 @@
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
doc = r#"
|
||||
Combines as imports on the same line. See isort's [`combine-as-imports`](https://pycqa.github.io/isort/docs/configuration/options.html#combine-as-imports)
|
||||
option.
|
||||
"#,
|
||||
default = r#"false"#,
|
||||
value_type = "bool",
|
||||
example = r#"
|
||||
combine-as-imports = true
|
||||
"#
|
||||
)]
|
||||
pub combine_as_imports: Option<bool>,
|
||||
#[option(
|
||||
doc = r#"
|
||||
Force `import from` statements with multiple members and at least one alias (e.g.,
|
||||
`import A as B`) to wrap such that every line contains exactly one member. For example,
|
||||
this formatting would be retained, rather than condensing to a single line:
|
||||
|
||||
```py
|
||||
from .utils import (
|
||||
test_directory as test_directory,
|
||||
test_id as test_id
|
||||
)
|
||||
```
|
||||
"#,
|
||||
default = r#"false"#,
|
||||
value_type = "bool",
|
||||
example = r#"
|
||||
force-wrap-aliases = true
|
||||
"#
|
||||
)]
|
||||
pub force_wrap_aliases: Option<bool>,
|
||||
#[option(
|
||||
doc = r#"
|
||||
A list of modules to consider first-party, regardless of whether they can be identified
|
||||
as such via introspection of the local filesystem.
|
||||
"#,
|
||||
default = r#"[]"#,
|
||||
value_type = "Vec<String>",
|
||||
example = r#"
|
||||
known-first-party = ["src"]
|
||||
"#
|
||||
)]
|
||||
pub known_first_party: Option<Vec<String>>,
|
||||
#[option(
|
||||
doc = r#"
|
||||
A list of modules to consider third-party, regardless of whether they can be identified
|
||||
as such via introspection of the local filesystem.
|
||||
"#,
|
||||
default = r#"[]"#,
|
||||
value_type = "Vec<String>",
|
||||
example = r#"
|
||||
known-third-party = ["src"]
|
||||
"#
|
||||
)]
|
||||
pub known_third_party: Option<Vec<String>>,
|
||||
#[option(
|
||||
doc = r#"
|
||||
A list of modules to consider standard-library, in addition to those known to Ruff in
|
||||
advance.
|
||||
"#,
|
||||
default = r#"[]"#,
|
||||
value_type = "Vec<String>",
|
||||
example = r#"
|
||||
extra-standard-library = ["path"]
|
||||
"#
|
||||
)]
|
||||
pub extra_standard_library: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -47,4 +47,34 @@ expression: checks
|
||||
end_location:
|
||||
row: 16
|
||||
column: 0
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 33
|
||||
column: 0
|
||||
end_location:
|
||||
row: 35
|
||||
column: 0
|
||||
fix:
|
||||
content: " import collections\n import typing\n\n"
|
||||
location:
|
||||
row: 33
|
||||
column: 0
|
||||
end_location:
|
||||
row: 35
|
||||
column: 0
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 39
|
||||
column: 0
|
||||
end_location:
|
||||
row: 41
|
||||
column: 0
|
||||
fix:
|
||||
content: " import collections\n import typing\n\n"
|
||||
location:
|
||||
row: 39
|
||||
column: 0
|
||||
end_location:
|
||||
row: 41
|
||||
column: 0
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 0
|
||||
fix:
|
||||
content: "import a\nimport b\n\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
column: 0
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 0
|
||||
fix:
|
||||
content: "import os\nimport sys\n\n"
|
||||
location:
|
||||
row: 4
|
||||
column: 0
|
||||
end_location:
|
||||
row: 6
|
||||
column: 0
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 14
|
||||
column: 0
|
||||
end_location:
|
||||
row: 16
|
||||
column: 0
|
||||
fix:
|
||||
content: "import os\nimport sys\n\n"
|
||||
location:
|
||||
row: 14
|
||||
column: 0
|
||||
end_location:
|
||||
row: 16
|
||||
column: 0
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 33
|
||||
column: 0
|
||||
end_location:
|
||||
row: 35
|
||||
column: 0
|
||||
fix:
|
||||
content: " import collections\n import typing\n\n"
|
||||
location:
|
||||
row: 33
|
||||
column: 0
|
||||
end_location:
|
||||
row: 35
|
||||
column: 0
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::path::Path;
|
||||
|
||||
use rustpython_ast::{
|
||||
Alias, Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, Excepthandler,
|
||||
ExcepthandlerKind, Expr, ExprContext, Keyword, MatchCase, Operator, Pattern, Stmt, StmtKind,
|
||||
@@ -20,17 +22,21 @@ pub struct Block<'a> {
|
||||
}
|
||||
|
||||
pub struct ImportTracker<'a> {
|
||||
blocks: Vec<Block<'a>>,
|
||||
directives: &'a IsortDirectives,
|
||||
pyi: bool,
|
||||
blocks: Vec<Block<'a>>,
|
||||
split_index: usize,
|
||||
nested: bool,
|
||||
}
|
||||
|
||||
impl<'a> ImportTracker<'a> {
|
||||
pub fn new(directives: &'a IsortDirectives) -> Self {
|
||||
pub fn new(directives: &'a IsortDirectives, path: &'a Path) -> Self {
|
||||
Self {
|
||||
directives,
|
||||
pyi: path.extension().map_or(false, |ext| ext == "pyi"),
|
||||
blocks: vec![Block::default()],
|
||||
split_index: 0,
|
||||
nested: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +45,34 @@ impl<'a> ImportTracker<'a> {
|
||||
self.blocks[index].imports.push(stmt);
|
||||
}
|
||||
|
||||
fn trailer_for(&self, stmt: &'a Stmt) -> Option<Trailer> {
|
||||
if self.pyi {
|
||||
// Black treats interface files differently, limiting to one newline
|
||||
// (`Trailing::Sibling`), and avoiding inserting any newlines in nested function
|
||||
// blocks.
|
||||
if self.nested
|
||||
&& matches!(
|
||||
stmt.node,
|
||||
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. }
|
||||
)
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(Trailer::Sibling)
|
||||
}
|
||||
} else if self.nested {
|
||||
Some(Trailer::Sibling)
|
||||
} else {
|
||||
Some(match &stmt.node {
|
||||
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
|
||||
Trailer::FunctionDef
|
||||
}
|
||||
StmtKind::ClassDef { .. } => Trailer::ClassDef,
|
||||
_ => Trailer::Sibling,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn finalize(&mut self, trailer: Option<Trailer>) {
|
||||
let index = self.blocks.len() - 1;
|
||||
if !self.blocks[index].imports.is_empty() {
|
||||
@@ -60,13 +94,7 @@ where
|
||||
// Track manual splits.
|
||||
while self.split_index < self.directives.splits.len() {
|
||||
if stmt.location.row() >= self.directives.splits[self.split_index] {
|
||||
self.finalize(Some(match &stmt.node {
|
||||
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
|
||||
Trailer::FunctionDef
|
||||
}
|
||||
StmtKind::ClassDef { .. } => Trailer::ClassDef,
|
||||
_ => Trailer::Sibling,
|
||||
}));
|
||||
self.finalize(self.trailer_for(stmt));
|
||||
self.split_index += 1;
|
||||
} else {
|
||||
break;
|
||||
@@ -81,16 +109,12 @@ where
|
||||
{
|
||||
self.track_import(stmt);
|
||||
} else {
|
||||
self.finalize(Some(match &stmt.node {
|
||||
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
|
||||
Trailer::FunctionDef
|
||||
}
|
||||
StmtKind::ClassDef { .. } => Trailer::ClassDef,
|
||||
_ => Trailer::Sibling,
|
||||
}));
|
||||
self.finalize(self.trailer_for(stmt));
|
||||
}
|
||||
|
||||
// Track scope.
|
||||
let prev_nested = self.nested;
|
||||
self.nested = true;
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { body, .. } => {
|
||||
for stmt in body {
|
||||
@@ -198,6 +222,7 @@ where
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.nested = prev_nested;
|
||||
}
|
||||
|
||||
fn visit_annotation(&mut self, _: &'b Expr) {}
|
||||
@@ -219,11 +244,16 @@ where
|
||||
fn visit_comprehension(&mut self, _: &'b Comprehension) {}
|
||||
|
||||
fn visit_excepthandler(&mut self, excepthandler: &'b Excepthandler) {
|
||||
let prev_nested = self.nested;
|
||||
self.nested = true;
|
||||
|
||||
let ExcepthandlerKind::ExceptHandler { body, .. } = &excepthandler.node;
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
self.finalize(None);
|
||||
|
||||
self.nested = prev_nested;
|
||||
}
|
||||
|
||||
fn visit_arguments(&mut self, _: &'b Arguments) {}
|
||||
|
||||
@@ -49,10 +49,12 @@ pub mod flake8_bugbear;
|
||||
mod flake8_builtins;
|
||||
mod flake8_comprehensions;
|
||||
mod flake8_debugger;
|
||||
mod flake8_import_conventions;
|
||||
mod flake8_print;
|
||||
pub mod flake8_quotes;
|
||||
mod flake8_return;
|
||||
pub mod flake8_tidy_imports;
|
||||
mod flake8_unused_arguments;
|
||||
pub mod fs;
|
||||
mod isort;
|
||||
mod lex;
|
||||
@@ -70,7 +72,7 @@ mod pygrep_hooks;
|
||||
mod pylint;
|
||||
mod python;
|
||||
mod pyupgrade;
|
||||
mod rules;
|
||||
mod ruff;
|
||||
mod rustpython_helpers;
|
||||
pub mod settings;
|
||||
pub mod source_code_locator;
|
||||
|
||||
@@ -92,6 +92,7 @@ pub(crate) fn check_path(
|
||||
&directives.isort,
|
||||
settings,
|
||||
autofix,
|
||||
path,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
16
src/main.rs
16
src/main.rs
@@ -17,6 +17,7 @@ use std::process::ExitCode;
|
||||
use std::sync::mpsc::channel;
|
||||
use std::time::Instant;
|
||||
|
||||
use ::ruff::autofix::fixer;
|
||||
use ::ruff::checks::{CheckCode, CheckKind};
|
||||
use ::ruff::cli::{collect_per_file_ignores, extract_log_level, Cli};
|
||||
use ::ruff::fs::iter_python_files;
|
||||
@@ -29,7 +30,7 @@ use ::ruff::settings::types::SerializationFormat;
|
||||
use ::ruff::settings::{pyproject, Settings};
|
||||
#[cfg(feature = "update-informer")]
|
||||
use ::ruff::updates;
|
||||
use ::ruff::{cache, commands};
|
||||
use ::ruff::{cache, commands, fs};
|
||||
use anyhow::Result;
|
||||
use clap::{CommandFactory, Parser};
|
||||
use colored::Colorize;
|
||||
@@ -37,7 +38,6 @@ use log::{debug, error};
|
||||
use notify::{recommended_watcher, RecursiveMode, Watcher};
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use rayon::prelude::*;
|
||||
use ruff::autofix::fixer;
|
||||
use rustpython_ast::Location;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
@@ -210,10 +210,12 @@ fn inner_main() -> Result<ExitCode> {
|
||||
}
|
||||
|
||||
// Find the project root and pyproject.toml.
|
||||
let project_root = pyproject::find_project_root(&cli.files);
|
||||
let pyproject = cli
|
||||
.config
|
||||
.or_else(|| pyproject::find_pyproject_toml(project_root.as_ref()));
|
||||
let config: Option<PathBuf> = cli.config;
|
||||
let project_root = config.as_ref().map_or_else(
|
||||
|| pyproject::find_project_root(&cli.files),
|
||||
|config| config.parent().map(fs::normalize_path),
|
||||
);
|
||||
let pyproject = config.or_else(|| pyproject::find_pyproject_toml(project_root.as_ref()));
|
||||
|
||||
// Reconcile configuration from pyproject.toml and command-line arguments.
|
||||
let mut configuration =
|
||||
@@ -390,7 +392,7 @@ fn inner_main() -> Result<ExitCode> {
|
||||
// unless we're writing fixes via stdin (in which case, the transformed
|
||||
// source code goes to stdout).
|
||||
if !(is_stdin && matches!(autofix, fixer::Mode::Apply)) {
|
||||
printer.write_once(&diagnostics)?;
|
||||
printer.write_once(&diagnostics, &autofix)?;
|
||||
}
|
||||
|
||||
// Check for updates if we're in a non-silent log level.
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
//! Settings for the `mccabe` plugin.
|
||||
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
doc = "The maximum McCabe complexity to allow before triggering `C901` errors.",
|
||||
default = "10",
|
||||
value_type = "usize",
|
||||
example = r#"
|
||||
# Flag errors (`C901`) whenever the complexity level exceeds 5.
|
||||
max-complexity = 5
|
||||
"#
|
||||
)]
|
||||
pub max_complexity: Option<usize>,
|
||||
}
|
||||
|
||||
@@ -14,8 +24,7 @@ pub struct Settings {
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn from_options(options: Options) -> Self {
|
||||
pub fn from_options(options: &Options) -> Self {
|
||||
Self {
|
||||
max_complexity: options.max_complexity.unwrap_or_default(),
|
||||
}
|
||||
|
||||
26
src/noqa.rs
26
src/noqa.rs
@@ -1,4 +1,3 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
@@ -7,8 +6,9 @@ use itertools::Itertools;
|
||||
use nohash_hasher::IntMap;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::checks::{Check, CheckCode, REDIRECTS};
|
||||
use crate::checks::{Check, CheckCode, CODE_REDIRECTS};
|
||||
|
||||
static NO_QA_LINE_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(
|
||||
@@ -70,7 +70,7 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
|
||||
pub fn includes(needle: &CheckCode, haystack: &[&str]) -> bool {
|
||||
let needle: &str = needle.as_ref();
|
||||
haystack.iter().any(|candidate| {
|
||||
if let Some(candidate) = REDIRECTS.get(candidate) {
|
||||
if let Some(candidate) = CODE_REDIRECTS.get(candidate) {
|
||||
needle == candidate.as_ref()
|
||||
} else {
|
||||
&needle == candidate
|
||||
@@ -83,7 +83,7 @@ pub fn add_noqa(
|
||||
checks: &[Check],
|
||||
contents: &str,
|
||||
noqa_line_for: &IntMap<usize, usize>,
|
||||
external: &BTreeSet<String>,
|
||||
external: &FxHashSet<String>,
|
||||
) -> Result<usize> {
|
||||
let (count, output) = add_noqa_inner(checks, contents, noqa_line_for, external);
|
||||
fs::write(path, output)?;
|
||||
@@ -94,16 +94,16 @@ fn add_noqa_inner(
|
||||
checks: &[Check],
|
||||
contents: &str,
|
||||
noqa_line_for: &IntMap<usize, usize>,
|
||||
external: &BTreeSet<String>,
|
||||
external: &FxHashSet<String>,
|
||||
) -> (usize, String) {
|
||||
let mut matches_by_line: BTreeMap<usize, BTreeSet<&CheckCode>> = BTreeMap::new();
|
||||
let mut matches_by_line: FxHashMap<usize, FxHashSet<&CheckCode>> = FxHashMap::default();
|
||||
for (lineno, line) in contents.lines().enumerate() {
|
||||
// If we hit an exemption for the entire file, bail.
|
||||
if is_file_exempt(line) {
|
||||
return (0, contents.to_string());
|
||||
}
|
||||
|
||||
let mut codes: BTreeSet<&CheckCode> = BTreeSet::new();
|
||||
let mut codes: FxHashSet<&CheckCode> = FxHashSet::default();
|
||||
for check in checks {
|
||||
if check.location.row() == lineno + 1 {
|
||||
codes.insert(check.kind.code());
|
||||
@@ -117,7 +117,7 @@ fn add_noqa_inner(
|
||||
|
||||
if !codes.is_empty() {
|
||||
let matches = matches_by_line.entry(noqa_lineno).or_default();
|
||||
matches.append(&mut codes);
|
||||
matches.extend(codes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,9 +199,9 @@ fn add_noqa_inner(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use nohash_hasher::IntMap;
|
||||
use rustc_hash::FxHashSet;
|
||||
use rustpython_parser::ast::Location;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
@@ -227,7 +227,7 @@ mod tests {
|
||||
let checks = vec![];
|
||||
let contents = "x = 1";
|
||||
let noqa_line_for = IntMap::default();
|
||||
let external = BTreeSet::default();
|
||||
let external = FxHashSet::default();
|
||||
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for, &external);
|
||||
assert_eq!(count, 0);
|
||||
assert_eq!(output.trim(), contents.trim());
|
||||
@@ -241,7 +241,7 @@ mod tests {
|
||||
)];
|
||||
let contents = "x = 1";
|
||||
let noqa_line_for = IntMap::default();
|
||||
let external = BTreeSet::default();
|
||||
let external = FxHashSet::default();
|
||||
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for, &external);
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output.trim(), "x = 1 # noqa: F841".trim());
|
||||
@@ -264,7 +264,7 @@ mod tests {
|
||||
];
|
||||
let contents = "x = 1 # noqa: E741";
|
||||
let noqa_line_for = IntMap::default();
|
||||
let external = BTreeSet::default();
|
||||
let external = FxHashSet::default();
|
||||
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for, &external);
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output.trim(), "x = 1 # noqa: E741, F841".trim());
|
||||
@@ -287,7 +287,7 @@ mod tests {
|
||||
];
|
||||
let contents = "x = 1 # noqa";
|
||||
let noqa_line_for = IntMap::default();
|
||||
let external = BTreeSet::default();
|
||||
let external = FxHashSet::default();
|
||||
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for, &external);
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(output.trim(), "x = 1 # noqa: E741, F841".trim());
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::{Arguments, Expr, ExprKind, Stmt};
|
||||
|
||||
use crate::ast::function_type;
|
||||
use crate::ast::types::{Range, Scope, ScopeKind};
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::pep8_naming::helpers;
|
||||
use crate::pep8_naming::helpers::FunctionType;
|
||||
use crate::pep8_naming::settings::Settings;
|
||||
use crate::python::string::{self};
|
||||
|
||||
@@ -58,15 +58,16 @@ pub fn invalid_first_argument_name_for_class_method(
|
||||
settings: &Settings,
|
||||
) -> Option<Check> {
|
||||
if !matches!(
|
||||
helpers::function_type(
|
||||
function_type::classify(
|
||||
scope,
|
||||
name,
|
||||
decorator_list,
|
||||
from_imports,
|
||||
import_aliases,
|
||||
settings,
|
||||
&settings.classmethod_decorators,
|
||||
&settings.staticmethod_decorators,
|
||||
),
|
||||
FunctionType::ClassMethod
|
||||
function_type::FunctionType::ClassMethod
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
@@ -99,15 +100,16 @@ pub fn invalid_first_argument_name_for_method(
|
||||
settings: &Settings,
|
||||
) -> Option<Check> {
|
||||
if !matches!(
|
||||
helpers::function_type(
|
||||
function_type::classify(
|
||||
scope,
|
||||
name,
|
||||
decorator_list,
|
||||
from_imports,
|
||||
import_aliases,
|
||||
settings,
|
||||
&settings.classmethod_decorators,
|
||||
&settings.staticmethod_decorators,
|
||||
),
|
||||
FunctionType::Method
|
||||
function_type::FunctionType::Method
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -1,71 +1,10 @@
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use rustpython_ast::{Expr, Stmt, StmtKind};
|
||||
use rustpython_ast::{Stmt, StmtKind};
|
||||
|
||||
use crate::ast::helpers::{
|
||||
collect_call_paths, dealias_call_path, match_call_path, to_module_and_member,
|
||||
};
|
||||
use crate::ast::types::{Scope, ScopeKind};
|
||||
use crate::pep8_naming::settings::Settings;
|
||||
use crate::ast::helpers::{collect_call_paths, match_call_path};
|
||||
use crate::python::string::{is_lower, is_upper};
|
||||
|
||||
const CLASS_METHODS: [&str; 3] = ["__new__", "__init_subclass__", "__class_getitem__"];
|
||||
const METACLASS_BASES: [(&str, &str); 2] = [("", "type"), ("abc", "ABCMeta")];
|
||||
|
||||
pub enum FunctionType {
|
||||
Function,
|
||||
Method,
|
||||
ClassMethod,
|
||||
StaticMethod,
|
||||
}
|
||||
|
||||
/// Classify a function based on its scope, name, and decorators.
|
||||
pub fn function_type(
|
||||
scope: &Scope,
|
||||
name: &str,
|
||||
decorator_list: &[Expr],
|
||||
from_imports: &FxHashMap<&str, FxHashSet<&str>>,
|
||||
import_aliases: &FxHashMap<&str, &str>,
|
||||
settings: &Settings,
|
||||
) -> FunctionType {
|
||||
let ScopeKind::Class(scope) = &scope.kind else {
|
||||
return FunctionType::Function;
|
||||
};
|
||||
// Special-case class method, like `__new__`.
|
||||
if CLASS_METHODS.contains(&name)
|
||||
|| scope.bases.iter().any(|expr| {
|
||||
// The class itself extends a known metaclass, so all methods are class methods.
|
||||
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
|
||||
METACLASS_BASES
|
||||
.iter()
|
||||
.any(|(module, member)| match_call_path(&call_path, module, member, from_imports))
|
||||
})
|
||||
|| decorator_list.iter().any(|expr| {
|
||||
// The method is decorated with a class method decorator (like `@classmethod`).
|
||||
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
|
||||
settings.classmethod_decorators.iter().any(|decorator| {
|
||||
let (module, member) = to_module_and_member(decorator);
|
||||
match_call_path(&call_path, module, member, from_imports)
|
||||
})
|
||||
})
|
||||
{
|
||||
FunctionType::ClassMethod
|
||||
} else if decorator_list.iter().any(|expr| {
|
||||
// The method is decorated with a static method decorator (like
|
||||
// `@staticmethod`).
|
||||
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
|
||||
settings.staticmethod_decorators.iter().any(|decorator| {
|
||||
let (module, member) = to_module_and_member(decorator);
|
||||
match_call_path(&call_path, module, member, from_imports)
|
||||
})
|
||||
}) {
|
||||
FunctionType::StaticMethod
|
||||
} else {
|
||||
// It's an instance method.
|
||||
FunctionType::Method
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_camelcase(name: &str) -> bool {
|
||||
!is_lower(name) && !is_upper(name) && !name.contains('_')
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Settings for the `pep8-naming` plugin.
|
||||
|
||||
use ruff_macros::ConfigurationOptions;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const IGNORE_NAMES: [&str; 12] = [
|
||||
@@ -21,11 +22,47 @@ const CLASSMETHOD_DECORATORS: [&str; 1] = ["classmethod"];
|
||||
|
||||
const STATICMETHOD_DECORATORS: [&str; 1] = ["staticmethod"];
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
pub struct Options {
|
||||
#[option(
|
||||
doc = r#"
|
||||
A list of names to ignore when considering `pep8-naming` violations.
|
||||
"#,
|
||||
default = r#"["setUp", "tearDown", "setUpClass", "tearDownClass", "setUpModule", "tearDownModule", "asyncSetUp", "asyncTearDown", "setUpTestData", "failureException", "longMessage", "maxDiff"]"#,
|
||||
value_type = "Vec<String>",
|
||||
example = r#"
|
||||
ignore-names = ["callMethod"]
|
||||
"#
|
||||
)]
|
||||
pub ignore_names: Option<Vec<String>>,
|
||||
#[option(
|
||||
doc = r#"
|
||||
A list of decorators that, when applied to a method, indicate that the method should be
|
||||
treated as a class method. For example, Ruff will expect that any method decorated by a
|
||||
decorator in this list takes a `cls` argument as its first argument.
|
||||
"#,
|
||||
default = r#"["classmethod"]"#,
|
||||
value_type = "Vec<String>",
|
||||
example = r#"
|
||||
# Allow Pydantic's `@validator` decorator to trigger class method treatment.
|
||||
classmethod-decorators = ["classmethod", "pydantic.validator"]
|
||||
"#
|
||||
)]
|
||||
pub classmethod_decorators: Option<Vec<String>>,
|
||||
#[option(
|
||||
doc = r#"
|
||||
A list of decorators that, when applied to a method, indicate that the method should be
|
||||
treated as a static method. For example, Ruff will expect that any method decorated by a
|
||||
decorator in this list has no `self` or `cls` argument.
|
||||
"#,
|
||||
default = r#"["staticmethod"]"#,
|
||||
value_type = "Vec<String>",
|
||||
example = r#"
|
||||
# Allow a shorthand alias, `@stcmthd`, to trigger static method treatment.
|
||||
staticmethod-decorators = ["staticmethod", "stcmthd"]
|
||||
"#
|
||||
)]
|
||||
pub staticmethod_decorators: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use itertools::iterate;
|
||||
use rustpython_parser::ast::Location;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::autofix::Fix;
|
||||
use crate::autofix::{fixer, Fix};
|
||||
use crate::checks::CheckCode;
|
||||
use crate::fs::relativize_path;
|
||||
use crate::linter::Diagnostics;
|
||||
@@ -57,15 +57,15 @@ impl<'a> Printer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn post_text(&self, num_fixable: usize) {
|
||||
fn post_text(&self, num_fixable: usize, autofix: &fixer::Mode) {
|
||||
if self.log_level >= &LogLevel::Default {
|
||||
if num_fixable > 0 {
|
||||
if num_fixable > 0 && !matches!(autofix, fixer::Mode::Apply) {
|
||||
println!("{num_fixable} potentially fixable with the --fix option.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_once(&self, diagnostics: &Diagnostics) -> Result<()> {
|
||||
pub fn write_once(&self, diagnostics: &Diagnostics, autofix: &fixer::Mode) -> Result<()> {
|
||||
if matches!(self.log_level, LogLevel::Silent) {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -147,7 +147,7 @@ impl<'a> Printer<'a> {
|
||||
print_message(message);
|
||||
}
|
||||
|
||||
self.post_text(num_fixable);
|
||||
self.post_text(num_fixable, autofix);
|
||||
}
|
||||
SerializationFormat::Grouped => {
|
||||
self.pre_text(diagnostics);
|
||||
@@ -190,7 +190,7 @@ impl<'a> Printer<'a> {
|
||||
println!();
|
||||
}
|
||||
|
||||
self.post_text(num_fixable);
|
||||
self.post_text(num_fixable, autofix);
|
||||
}
|
||||
SerializationFormat::Github => {
|
||||
self.pre_text(diagnostics);
|
||||
|
||||
@@ -1,11 +1,65 @@
|
||||
use itertools::izip;
|
||||
use rustpython_ast::Location;
|
||||
use rustpython_ast::{Location, Stmt, StmtKind};
|
||||
use rustpython_parser::ast::{Cmpop, Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::source_code_locator::SourceCodeLocator;
|
||||
|
||||
/// E721
|
||||
pub fn type_comparison(ops: &[Cmpop], comparators: &[Expr], location: Range) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
for (op, right) in izip!(ops, comparators) {
|
||||
if !matches!(op, Cmpop::Is | Cmpop::IsNot | Cmpop::Eq | Cmpop::NotEq) {
|
||||
continue;
|
||||
}
|
||||
match &right.node {
|
||||
ExprKind::Call { func, args, .. } => {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
// Ex) type(False)
|
||||
if id == "type" {
|
||||
if let Some(arg) = args.first() {
|
||||
// Allow comparison for types which are not obvious.
|
||||
if !matches!(arg.node, ExprKind::Name { .. }) {
|
||||
checks.push(Check::new(CheckKind::TypeComparison, location));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Attribute { value, .. } => {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
// Ex) types.IntType
|
||||
if id == "types" {
|
||||
checks.push(Check::new(CheckKind::TypeComparison, location));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
checks
|
||||
}
|
||||
|
||||
/// E722
|
||||
pub fn do_not_use_bare_except(
|
||||
type_: Option<&Expr>,
|
||||
body: &[Stmt],
|
||||
location: Range,
|
||||
) -> Option<Check> {
|
||||
if type_.is_none()
|
||||
&& !body
|
||||
.iter()
|
||||
.any(|stmt| matches!(stmt.node, StmtKind::Raise { exc: None, .. }))
|
||||
{
|
||||
Some(Check::new(CheckKind::DoNotUseBareExcept, location))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn is_ambiguous_name(name: &str) -> bool {
|
||||
name == "l" || name == "I" || name == "O"
|
||||
}
|
||||
@@ -46,43 +100,6 @@ pub fn ambiguous_function_name(name: &str, location: Range) -> Option<Check> {
|
||||
}
|
||||
}
|
||||
|
||||
/// E721
|
||||
pub fn type_comparison(ops: &[Cmpop], comparators: &[Expr], location: Range) -> Vec<Check> {
|
||||
let mut checks: Vec<Check> = vec![];
|
||||
|
||||
for (op, right) in izip!(ops, comparators) {
|
||||
if !matches!(op, Cmpop::Is | Cmpop::IsNot | Cmpop::Eq | Cmpop::NotEq) {
|
||||
continue;
|
||||
}
|
||||
match &right.node {
|
||||
ExprKind::Call { func, args, .. } => {
|
||||
if let ExprKind::Name { id, .. } = &func.node {
|
||||
// Ex) type(False)
|
||||
if id == "type" {
|
||||
if let Some(arg) = args.first() {
|
||||
// Allow comparison for types which are not obvious.
|
||||
if !matches!(arg.node, ExprKind::Name { .. }) {
|
||||
checks.push(Check::new(CheckKind::TypeComparison, location));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Attribute { value, .. } => {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
// Ex) types.IntType
|
||||
if id == "types" {
|
||||
checks.push(Check::new(CheckKind::TypeComparison, location));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
checks
|
||||
}
|
||||
|
||||
// See: https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals
|
||||
const VALID_ESCAPE_SEQUENCES: &[char; 23] = &[
|
||||
'\n', '\\', '\'', '"', 'a', 'b', 'f', 'n', 'r', 't', 'v', '0', '1', '2', '3', '4', '5', '6',
|
||||
|
||||
43
src/pydocstyle/helpers.rs
Normal file
43
src/pydocstyle/helpers.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use rustpython_ast::Expr;
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::docstrings::constants;
|
||||
use crate::SourceCodeLocator;
|
||||
|
||||
/// Return the leading quote string for a docstring (e.g., `"""`).
|
||||
pub fn leading_quote<'a>(docstring: &Expr, locator: &'a SourceCodeLocator) -> Option<&'a str> {
|
||||
if let Some(first_line) = locator
|
||||
.slice_source_code_range(&Range::from_located(docstring))
|
||||
.lines()
|
||||
.next()
|
||||
.map(str::to_lowercase)
|
||||
{
|
||||
for pattern in constants::TRIPLE_QUOTE_PREFIXES
|
||||
.iter()
|
||||
.chain(constants::SINGLE_QUOTE_PREFIXES)
|
||||
{
|
||||
if first_line.starts_with(pattern) {
|
||||
return Some(pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Return the index of the first logical line in a string.
|
||||
pub fn logical_line(content: &str) -> Option<usize> {
|
||||
// Find the first logical line.
|
||||
let mut logical_line = None;
|
||||
for (i, line) in content.lines().enumerate() {
|
||||
if line.trim().is_empty() {
|
||||
// Empty line. If this is the line _after_ the first logical line, stop.
|
||||
if logical_line.is_some() {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Non-empty line. Store the index.
|
||||
logical_line = Some(i);
|
||||
}
|
||||
}
|
||||
logical_line
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
mod helpers;
|
||||
pub mod plugins;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -36,7 +37,8 @@ mod tests {
|
||||
#[test_case(CheckCode::D214, Path::new("sections.py"); "D214")]
|
||||
#[test_case(CheckCode::D215, Path::new("sections.py"); "D215")]
|
||||
#[test_case(CheckCode::D300, Path::new("D.py"); "D300")]
|
||||
#[test_case(CheckCode::D400, Path::new("D.py"); "D400")]
|
||||
#[test_case(CheckCode::D400, Path::new("D.py"); "D400_0")]
|
||||
#[test_case(CheckCode::D400, Path::new("D400.py"); "D400_1")]
|
||||
#[test_case(CheckCode::D402, Path::new("D.py"); "D402")]
|
||||
#[test_case(CheckCode::D403, Path::new("D.py"); "D403")]
|
||||
#[test_case(CheckCode::D404, Path::new("D.py"); "D404")]
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use itertools::Itertools;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
@@ -7,8 +5,8 @@ use rustc_hash::FxHashSet;
|
||||
use rustpython_ast::{Constant, ExprKind, Location, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::ast::whitespace;
|
||||
use crate::ast::whitespace::LinesWithTrailingNewline;
|
||||
use crate::ast::{cast, whitespace};
|
||||
use crate::autofix::Fix;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
@@ -16,6 +14,7 @@ use crate::docstrings::constants;
|
||||
use crate::docstrings::definition::{Definition, DefinitionKind};
|
||||
use crate::docstrings::sections::{section_contexts, SectionContext};
|
||||
use crate::docstrings::styles::SectionStyle;
|
||||
use crate::pydocstyle::helpers::{leading_quote, logical_line};
|
||||
use crate::visibility::{is_init, is_magic, is_overload, is_override, is_staticmethod, Visibility};
|
||||
|
||||
/// D100, D101, D102, D103, D104, D105, D106, D107
|
||||
@@ -76,7 +75,7 @@ pub fn not_missing(
|
||||
false
|
||||
}
|
||||
DefinitionKind::Function(stmt) | DefinitionKind::NestedFunction(stmt) => {
|
||||
if is_overload(checker, stmt) {
|
||||
if is_overload(checker, cast::decorator_list(stmt)) {
|
||||
true
|
||||
} else {
|
||||
if checker.settings.enabled.contains(&CheckCode::D103) {
|
||||
@@ -89,7 +88,9 @@ pub fn not_missing(
|
||||
}
|
||||
}
|
||||
DefinitionKind::Method(stmt) => {
|
||||
if is_overload(checker, stmt) || is_override(checker, stmt) {
|
||||
if is_overload(checker, cast::decorator_list(stmt))
|
||||
|| is_override(checker, cast::decorator_list(stmt))
|
||||
{
|
||||
true
|
||||
} else if is_magic(stmt) {
|
||||
if checker.settings.enabled.contains(&CheckCode::D105) {
|
||||
@@ -621,18 +622,11 @@ pub fn no_surrounding_whitespace(checker: &mut Checker, definition: &Definition)
|
||||
Range::from_located(docstring),
|
||||
);
|
||||
if checker.patch(check.kind.code()) {
|
||||
if let Some(first_line) = checker
|
||||
.locator
|
||||
.slice_source_code_range(&Range::from_located(docstring))
|
||||
.lines()
|
||||
.next()
|
||||
.map(str::to_lowercase)
|
||||
{
|
||||
for pattern in constants::TRIPLE_QUOTE_PREFIXES
|
||||
.iter()
|
||||
.chain(constants::SINGLE_QUOTE_PREFIXES)
|
||||
{
|
||||
if first_line.starts_with(pattern) {
|
||||
if let Some(pattern) = leading_quote(docstring, checker.locator) {
|
||||
if let Some(quote) = pattern.chars().last() {
|
||||
// If removing whitespace would lead to an invalid string of quote
|
||||
// characters, avoid applying the fix.
|
||||
if !trimmed.ends_with(quote) {
|
||||
check.amend(Fix::replacement(
|
||||
trimmed.to_string(),
|
||||
Location::new(
|
||||
@@ -644,7 +638,6 @@ pub fn no_surrounding_whitespace(checker: &mut Checker, definition: &Definition)
|
||||
docstring.location.column() + pattern.len() + line.chars().count(),
|
||||
),
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -743,16 +736,30 @@ pub fn ends_with_period(checker: &mut Checker, definition: &Definition) {
|
||||
} = &docstring.node else {
|
||||
return;
|
||||
};
|
||||
let Some(string) = string.trim().lines().next() else {
|
||||
return;
|
||||
};
|
||||
if string.ends_with('.') {
|
||||
return;
|
||||
};
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::EndsInPeriod,
|
||||
Range::from_located(docstring),
|
||||
));
|
||||
if let Some(index) = logical_line(string) {
|
||||
let line = string.lines().nth(index).unwrap();
|
||||
let trimmed = line.trim_end();
|
||||
if !trimmed.ends_with('.') {
|
||||
let mut check = Check::new(CheckKind::EndsInPeriod, Range::from_located(docstring));
|
||||
// Best-effort autofix: avoid adding a period after other punctuation marks.
|
||||
if checker.patch(&CheckCode::D400) && !trimmed.ends_with(':') && !trimmed.ends_with(';')
|
||||
{
|
||||
if let Some((row, column)) = if index == 0 {
|
||||
leading_quote(docstring, checker.locator).map(|pattern| {
|
||||
(
|
||||
docstring.location.row(),
|
||||
docstring.location.column() + pattern.len() + trimmed.chars().count(),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Some((docstring.location.row() + index, trimmed.chars().count()))
|
||||
} {
|
||||
check.amend(Fix::insertion(".".to_string(), Location::new(row, column)));
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// D402
|
||||
@@ -870,16 +877,31 @@ pub fn ends_with_punctuation(checker: &mut Checker, definition: &Definition) {
|
||||
} = &docstring.node else {
|
||||
return
|
||||
};
|
||||
let Some(string) = string.trim().lines().next() else {
|
||||
return
|
||||
};
|
||||
if string.ends_with('.') || string.ends_with('!') || string.ends_with('?') {
|
||||
return;
|
||||
if let Some(index) = logical_line(string) {
|
||||
let line = string.lines().nth(index).unwrap();
|
||||
let trimmed = line.trim_end();
|
||||
if !(trimmed.ends_with('.') || trimmed.ends_with('!') || trimmed.ends_with('?')) {
|
||||
let mut check =
|
||||
Check::new(CheckKind::EndsInPunctuation, Range::from_located(docstring));
|
||||
// Best-effort autofix: avoid adding a period after other punctuation marks.
|
||||
if checker.patch(&CheckCode::D415) && !trimmed.ends_with(':') && !trimmed.ends_with(';')
|
||||
{
|
||||
if let Some((row, column)) = if index == 0 {
|
||||
leading_quote(docstring, checker.locator).map(|pattern| {
|
||||
(
|
||||
docstring.location.row(),
|
||||
docstring.location.column() + pattern.len() + trimmed.chars().count(),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Some((docstring.location.row() + index, trimmed.chars().count()))
|
||||
} {
|
||||
check.amend(Fix::insertion(".".to_string(), Location::new(row, column)));
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
};
|
||||
}
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::EndsInPunctuation,
|
||||
Range::from_located(docstring),
|
||||
));
|
||||
}
|
||||
|
||||
/// D418
|
||||
@@ -894,7 +916,7 @@ pub fn if_needed(checker: &mut Checker, definition: &Definition) {
|
||||
) = definition.kind else {
|
||||
return
|
||||
};
|
||||
if !is_overload(checker, stmt) {
|
||||
if !is_overload(checker, cast::decorator_list(stmt)) {
|
||||
return;
|
||||
}
|
||||
checker.add_check(Check::new(
|
||||
@@ -1378,7 +1400,7 @@ fn missing_args(checker: &mut Checker, definition: &Definition, docstrings_args:
|
||||
};
|
||||
|
||||
// Look for arguments that weren't included in the docstring.
|
||||
let mut missing_arg_names: BTreeSet<String> = BTreeSet::default();
|
||||
let mut missing_arg_names: FxHashSet<String> = FxHashSet::default();
|
||||
for arg in arguments
|
||||
.args
|
||||
.iter()
|
||||
@@ -1388,7 +1410,7 @@ fn missing_args(checker: &mut Checker, definition: &Definition, docstrings_args:
|
||||
// If this is a non-static method, skip `cls` or `self`.
|
||||
usize::from(
|
||||
matches!(definition.kind, DefinitionKind::Method(_))
|
||||
&& !is_staticmethod(checker, parent),
|
||||
&& !is_staticmethod(checker, cast::decorator_list(parent)),
|
||||
),
|
||||
)
|
||||
{
|
||||
|
||||
@@ -47,4 +47,12 @@ expression: checks
|
||||
end_location:
|
||||
row: 299
|
||||
column: 36
|
||||
- kind: NoSurroundingWhitespace
|
||||
location:
|
||||
row: 581
|
||||
column: 4
|
||||
end_location:
|
||||
row: 581
|
||||
column: 51
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -9,7 +9,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 355
|
||||
column: 17
|
||||
fix: ~
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 355
|
||||
column: 14
|
||||
end_location:
|
||||
row: 355
|
||||
column: 14
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 406
|
||||
@@ -17,7 +24,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 406
|
||||
column: 39
|
||||
fix: ~
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 406
|
||||
column: 36
|
||||
end_location:
|
||||
row: 406
|
||||
column: 36
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 410
|
||||
@@ -25,7 +39,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 410
|
||||
column: 24
|
||||
fix: ~
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 410
|
||||
column: 21
|
||||
end_location:
|
||||
row: 410
|
||||
column: 21
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 416
|
||||
@@ -33,7 +54,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 416
|
||||
column: 24
|
||||
fix: ~
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 416
|
||||
column: 21
|
||||
end_location:
|
||||
row: 416
|
||||
column: 21
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 422
|
||||
@@ -41,7 +69,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 422
|
||||
column: 49
|
||||
fix: ~
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 422
|
||||
column: 46
|
||||
end_location:
|
||||
row: 422
|
||||
column: 46
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 429
|
||||
@@ -49,7 +84,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 429
|
||||
column: 63
|
||||
fix: ~
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 429
|
||||
column: 60
|
||||
end_location:
|
||||
row: 429
|
||||
column: 60
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 470
|
||||
@@ -57,7 +99,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 470
|
||||
column: 24
|
||||
fix: ~
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 470
|
||||
column: 21
|
||||
end_location:
|
||||
row: 470
|
||||
column: 21
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 475
|
||||
@@ -65,7 +114,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 475
|
||||
column: 24
|
||||
fix: ~
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 475
|
||||
column: 21
|
||||
end_location:
|
||||
row: 475
|
||||
column: 21
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 480
|
||||
@@ -73,7 +129,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 480
|
||||
column: 24
|
||||
fix: ~
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 480
|
||||
column: 21
|
||||
end_location:
|
||||
row: 480
|
||||
column: 21
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 487
|
||||
@@ -81,7 +144,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 487
|
||||
column: 24
|
||||
fix: ~
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 487
|
||||
column: 21
|
||||
end_location:
|
||||
row: 487
|
||||
column: 21
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 509
|
||||
@@ -89,7 +159,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 509
|
||||
column: 34
|
||||
fix: ~
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 509
|
||||
column: 31
|
||||
end_location:
|
||||
row: 509
|
||||
column: 31
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 514
|
||||
@@ -97,7 +174,14 @@ expression: checks
|
||||
end_location:
|
||||
row: 514
|
||||
column: 33
|
||||
fix: ~
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 514
|
||||
column: 30
|
||||
end_location:
|
||||
row: 514
|
||||
column: 30
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 520
|
||||
@@ -105,5 +189,27 @@ expression: checks
|
||||
end_location:
|
||||
row: 520
|
||||
column: 32
|
||||
fix: ~
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 520
|
||||
column: 29
|
||||
end_location:
|
||||
row: 520
|
||||
column: 29
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 581
|
||||
column: 4
|
||||
end_location:
|
||||
row: 581
|
||||
column: 51
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 581
|
||||
column: 47
|
||||
end_location:
|
||||
row: 581
|
||||
column: 47
|
||||
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
---
|
||||
source: src/pydocstyle/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 2
|
||||
column: 4
|
||||
end_location:
|
||||
row: 2
|
||||
column: 36
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 2
|
||||
column: 35
|
||||
end_location:
|
||||
row: 2
|
||||
column: 35
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 7
|
||||
column: 4
|
||||
end_location:
|
||||
row: 7
|
||||
column: 40
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 7
|
||||
column: 37
|
||||
end_location:
|
||||
row: 7
|
||||
column: 37
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 12
|
||||
column: 4
|
||||
end_location:
|
||||
row: 15
|
||||
column: 7
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 14
|
||||
column: 28
|
||||
end_location:
|
||||
row: 14
|
||||
column: 28
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 20
|
||||
column: 4
|
||||
end_location:
|
||||
row: 20
|
||||
column: 40
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 20
|
||||
column: 37
|
||||
end_location:
|
||||
row: 20
|
||||
column: 37
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 25
|
||||
column: 4
|
||||
end_location:
|
||||
row: 27
|
||||
column: 31
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 27
|
||||
column: 28
|
||||
end_location:
|
||||
row: 27
|
||||
column: 28
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 32
|
||||
column: 4
|
||||
end_location:
|
||||
row: 34
|
||||
column: 52
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 34
|
||||
column: 48
|
||||
end_location:
|
||||
row: 34
|
||||
column: 48
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 40
|
||||
column: 4
|
||||
end_location:
|
||||
row: 40
|
||||
column: 37
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 40
|
||||
column: 36
|
||||
end_location:
|
||||
row: 40
|
||||
column: 36
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 45
|
||||
column: 4
|
||||
end_location:
|
||||
row: 45
|
||||
column: 41
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 45
|
||||
column: 38
|
||||
end_location:
|
||||
row: 45
|
||||
column: 38
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 50
|
||||
column: 4
|
||||
end_location:
|
||||
row: 53
|
||||
column: 7
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 52
|
||||
column: 28
|
||||
end_location:
|
||||
row: 52
|
||||
column: 28
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 58
|
||||
column: 4
|
||||
end_location:
|
||||
row: 58
|
||||
column: 41
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 58
|
||||
column: 38
|
||||
end_location:
|
||||
row: 58
|
||||
column: 38
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 63
|
||||
column: 4
|
||||
end_location:
|
||||
row: 65
|
||||
column: 31
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 65
|
||||
column: 28
|
||||
end_location:
|
||||
row: 65
|
||||
column: 28
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 70
|
||||
column: 4
|
||||
end_location:
|
||||
row: 72
|
||||
column: 52
|
||||
fix:
|
||||
content: "."
|
||||
location:
|
||||
row: 72
|
||||
column: 48
|
||||
end_location:
|
||||
row: 72
|
||||
column: 48
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user