mirror of https://github.com/astral-sh/ruff
Merge branch 'main' into 5246_try301_identical_rule
This commit is contained in:
commit
f244d0387a
|
|
@ -4,3 +4,4 @@ crates/ruff/resources/test/fixtures/isort/line_ending_crlf.py text eol=crlf
|
||||||
crates/ruff/resources/test/fixtures/pycodestyle/W605_1.py text eol=crlf
|
crates/ruff/resources/test/fixtures/pycodestyle/W605_1.py text eol=crlf
|
||||||
|
|
||||||
ruff.schema.json linguist-generated=true text=auto eol=lf
|
ruff.schema.json linguist-generated=true text=auto eol=lf
|
||||||
|
*.md.snap linguist-language=Markdown
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,31 @@
|
||||||
# Breaking Changes
|
# Breaking Changes
|
||||||
|
|
||||||
|
## 0.0.276
|
||||||
|
|
||||||
|
### The `keep-runtime-typing` setting has been reinstated ([#5470](https://github.com/astral-sh/ruff/pull/5470))
|
||||||
|
|
||||||
|
The `keep-runtime-typing` setting has been reinstated with revised semantics. This setting was
|
||||||
|
removed in [#4427](https://github.com/astral-sh/ruff/pull/4427), as it was equivalent to ignoring
|
||||||
|
the `UP006` and `UP007` rules via Ruff's standard `ignore` mechanism.
|
||||||
|
|
||||||
|
Taking `UP006` (rewrite `List[int]` to `list[int]`) as an example, the setting now behaves as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
- On Python 3.7 and Python 3.8, setting `keep-runtime-typing = true` will cause Ruff to ignore
|
||||||
|
`UP006` violations, even if `from __future__ import annotations` is present in the file.
|
||||||
|
While such annotations are valid in Python 3.7 and Python 3.8 when combined with
|
||||||
|
`from __future__ import annotations`, they aren't supported by libraries like Pydantic and
|
||||||
|
FastAPI, which rely on runtime type checking.
|
||||||
|
- On Python 3.9 and above, the setting has no effect, as `list[int]` is a valid type annotation,
|
||||||
|
and libraries like Pydantic and FastAPI support it without issue.
|
||||||
|
|
||||||
|
In short: `keep-runtime-typing` can be used to ensure that Ruff doesn't introduce type annotations
|
||||||
|
that are not supported at runtime by the current Python version, which are unsupported by libraries
|
||||||
|
like Pydantic and FastAPI.
|
||||||
|
|
||||||
|
Note that this is not a breaking change, but is included here to complement the previous removal
|
||||||
|
of `keep-runtime-typing`.
|
||||||
|
|
||||||
## 0.0.268
|
## 0.0.268
|
||||||
|
|
||||||
### The `keep-runtime-typing` setting has been removed ([#4427](https://github.com/astral-sh/ruff/pull/4427))
|
### The `keep-runtime-typing` setting has been removed ([#4427](https://github.com/astral-sh/ruff/pull/4427))
|
||||||
|
|
|
||||||
|
|
@ -746,7 +746,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flake8-to-ruff"
|
name = "flake8-to-ruff"
|
||||||
version = "0.0.275"
|
version = "0.0.276"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
|
@ -1829,7 +1829,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.0.275"
|
version = "0.0.276"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"annotate-snippets 0.9.1",
|
"annotate-snippets 0.9.1",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
@ -1865,6 +1865,7 @@ dependencies = [
|
||||||
"result-like",
|
"result-like",
|
||||||
"ruff_cache",
|
"ruff_cache",
|
||||||
"ruff_diagnostics",
|
"ruff_diagnostics",
|
||||||
|
"ruff_index",
|
||||||
"ruff_macros",
|
"ruff_macros",
|
||||||
"ruff_python_ast",
|
"ruff_python_ast",
|
||||||
"ruff_python_semantic",
|
"ruff_python_semantic",
|
||||||
|
|
@ -1926,7 +1927,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff_cli"
|
name = "ruff_cli"
|
||||||
version = "0.0.275"
|
version = "0.0.276"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"annotate-snippets 0.9.1",
|
"annotate-snippets 0.9.1",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||||
```yaml
|
```yaml
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: v0.0.275
|
rev: v0.0.276
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "flake8-to-ruff"
|
name = "flake8-to-ruff"
|
||||||
version = "0.0.275"
|
version = "0.0.276"
|
||||||
description = """
|
description = """
|
||||||
Convert Flake8 configuration files to Ruff configuration files.
|
Convert Flake8 configuration files to Ruff configuration files.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.0.275"
|
version = "0.0.276"
|
||||||
publish = false
|
publish = false
|
||||||
authors = { workspace = true }
|
authors = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
|
|
@ -17,6 +17,7 @@ name = "ruff"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ruff_cache = { path = "../ruff_cache" }
|
ruff_cache = { path = "../ruff_cache" }
|
||||||
ruff_diagnostics = { path = "../ruff_diagnostics", features = ["serde"] }
|
ruff_diagnostics = { path = "../ruff_diagnostics", features = ["serde"] }
|
||||||
|
ruff_index = { path = "../ruff_index" }
|
||||||
ruff_macros = { path = "../ruff_macros" }
|
ruff_macros = { path = "../ruff_macros" }
|
||||||
ruff_python_whitespace = { path = "../ruff_python_whitespace" }
|
ruff_python_whitespace = { path = "../ruff_python_whitespace" }
|
||||||
ruff_python_ast = { path = "../ruff_python_ast", features = ["serde"] }
|
ruff_python_ast = { path = "../ruff_python_ast", features = ["serde"] }
|
||||||
|
|
@ -88,3 +89,5 @@ colored = { workspace = true, features = ["no-color"] }
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
schemars = ["dep:schemars"]
|
schemars = ["dep:schemars"]
|
||||||
|
# Enables the UnreachableCode rule
|
||||||
|
unreachable-code = []
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
def func():
|
||||||
|
assert True
|
||||||
|
|
||||||
|
def func():
|
||||||
|
assert False
|
||||||
|
|
||||||
|
def func():
|
||||||
|
assert True, "oops"
|
||||||
|
|
||||||
|
def func():
|
||||||
|
assert False, "oops"
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
def func():
|
||||||
|
async for i in range(5):
|
||||||
|
print(i)
|
||||||
|
|
||||||
|
def func():
|
||||||
|
async for i in range(20):
|
||||||
|
print(i)
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def func():
|
||||||
|
async for i in range(10):
|
||||||
|
if i == 5:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def func():
|
||||||
|
async for i in range(111):
|
||||||
|
if i == 5:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
return 2
|
||||||
|
|
||||||
|
def func():
|
||||||
|
async for i in range(12):
|
||||||
|
continue
|
||||||
|
|
||||||
|
def func():
|
||||||
|
async for i in range(1110):
|
||||||
|
if True:
|
||||||
|
continue
|
||||||
|
|
||||||
|
def func():
|
||||||
|
async for i in range(13):
|
||||||
|
break
|
||||||
|
|
||||||
|
def func():
|
||||||
|
async for i in range(1110):
|
||||||
|
if True:
|
||||||
|
break
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
def func():
|
||||||
|
for i in range(5):
|
||||||
|
print(i)
|
||||||
|
|
||||||
|
def func():
|
||||||
|
for i in range(20):
|
||||||
|
print(i)
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def func():
|
||||||
|
for i in range(10):
|
||||||
|
if i == 5:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def func():
|
||||||
|
for i in range(111):
|
||||||
|
if i == 5:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
return 2
|
||||||
|
|
||||||
|
def func():
|
||||||
|
for i in range(12):
|
||||||
|
continue
|
||||||
|
|
||||||
|
def func():
|
||||||
|
for i in range(1110):
|
||||||
|
if True:
|
||||||
|
continue
|
||||||
|
|
||||||
|
def func():
|
||||||
|
for i in range(13):
|
||||||
|
break
|
||||||
|
|
||||||
|
def func():
|
||||||
|
for i in range(1110):
|
||||||
|
if True:
|
||||||
|
break
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
def func():
|
||||||
|
if False:
|
||||||
|
return 0
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def func():
|
||||||
|
if True:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def func():
|
||||||
|
if False:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def func():
|
||||||
|
if True:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def func():
|
||||||
|
if False:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
def func():
|
||||||
|
if True:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
def func():
|
||||||
|
if True:
|
||||||
|
if True:
|
||||||
|
return 1
|
||||||
|
return 2
|
||||||
|
else:
|
||||||
|
return 3
|
||||||
|
return "unreachable2"
|
||||||
|
|
||||||
|
def func():
|
||||||
|
if False:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def func():
|
||||||
|
if True:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def func():
|
||||||
|
if True:
|
||||||
|
return 1
|
||||||
|
elif False:
|
||||||
|
return 2
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def func():
|
||||||
|
if False:
|
||||||
|
return 1
|
||||||
|
elif True:
|
||||||
|
return 2
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def func():
|
||||||
|
if True:
|
||||||
|
if False:
|
||||||
|
return 0
|
||||||
|
elif True:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return 2
|
||||||
|
return 3
|
||||||
|
elif True:
|
||||||
|
return 4
|
||||||
|
else:
|
||||||
|
return 5
|
||||||
|
return 6
|
||||||
|
|
||||||
|
def func():
|
||||||
|
if False:
|
||||||
|
return "unreached"
|
||||||
|
elif False:
|
||||||
|
return "also unreached"
|
||||||
|
return "reached"
|
||||||
|
|
||||||
|
# Test case found in the Bokeh repository that trigger a false positive.
|
||||||
|
def func(self, obj: BytesRep) -> bytes:
|
||||||
|
data = obj["data"]
|
||||||
|
|
||||||
|
if isinstance(data, str):
|
||||||
|
return base64.b64decode(data)
|
||||||
|
elif isinstance(data, Buffer):
|
||||||
|
buffer = data
|
||||||
|
else:
|
||||||
|
id = data["id"]
|
||||||
|
|
||||||
|
if id in self._buffers:
|
||||||
|
buffer = self._buffers[id]
|
||||||
|
else:
|
||||||
|
self.error(f"can't resolve buffer '{id}'")
|
||||||
|
|
||||||
|
return buffer.data
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
def func(status):
|
||||||
|
match status:
|
||||||
|
case _:
|
||||||
|
return 0
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
def func(status):
|
||||||
|
match status:
|
||||||
|
case 1:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def func(status):
|
||||||
|
match status:
|
||||||
|
case 1:
|
||||||
|
return 1
|
||||||
|
case _:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def func(status):
|
||||||
|
match status:
|
||||||
|
case 1 | 2 | 3:
|
||||||
|
return 5
|
||||||
|
return 6
|
||||||
|
|
||||||
|
def func(status):
|
||||||
|
match status:
|
||||||
|
case 1 | 2 | 3:
|
||||||
|
return 5
|
||||||
|
case _:
|
||||||
|
return 10
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def func(status):
|
||||||
|
match status:
|
||||||
|
case 0:
|
||||||
|
return 0
|
||||||
|
case 1:
|
||||||
|
return 1
|
||||||
|
case 1:
|
||||||
|
return "1 again"
|
||||||
|
case _:
|
||||||
|
return 3
|
||||||
|
|
||||||
|
def func(status):
|
||||||
|
i = 0
|
||||||
|
match status, i:
|
||||||
|
case _, _:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def func(status):
|
||||||
|
i = 0
|
||||||
|
match status, i:
|
||||||
|
case _, 0:
|
||||||
|
return 0
|
||||||
|
case _, 2:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def func(point):
|
||||||
|
match point:
|
||||||
|
case (0, 0):
|
||||||
|
print("Origin")
|
||||||
|
case _:
|
||||||
|
raise ValueError("oops")
|
||||||
|
|
||||||
|
def func(point):
|
||||||
|
match point:
|
||||||
|
case (0, 0):
|
||||||
|
print("Origin")
|
||||||
|
case (0, y):
|
||||||
|
print(f"Y={y}")
|
||||||
|
case (x, 0):
|
||||||
|
print(f"X={x}")
|
||||||
|
case (x, y):
|
||||||
|
print(f"X={x}, Y={y}")
|
||||||
|
case _:
|
||||||
|
raise ValueError("Not a point")
|
||||||
|
|
||||||
|
def where_is(point):
|
||||||
|
class Point:
|
||||||
|
x: int
|
||||||
|
y: int
|
||||||
|
|
||||||
|
match point:
|
||||||
|
case Point(x=0, y=0):
|
||||||
|
print("Origin")
|
||||||
|
case Point(x=0, y=y):
|
||||||
|
print(f"Y={y}")
|
||||||
|
case Point(x=x, y=0):
|
||||||
|
print(f"X={x}")
|
||||||
|
case Point():
|
||||||
|
print("Somewhere else")
|
||||||
|
case _:
|
||||||
|
print("Not a point")
|
||||||
|
|
||||||
|
def func(points):
|
||||||
|
match points:
|
||||||
|
case []:
|
||||||
|
print("No points")
|
||||||
|
case [Point(0, 0)]:
|
||||||
|
print("The origin")
|
||||||
|
case [Point(x, y)]:
|
||||||
|
print(f"Single point {x}, {y}")
|
||||||
|
case [Point(0, y1), Point(0, y2)]:
|
||||||
|
print(f"Two on the Y axis at {y1}, {y2}")
|
||||||
|
case _:
|
||||||
|
print("Something else")
|
||||||
|
|
||||||
|
def func(point):
|
||||||
|
match point:
|
||||||
|
case Point(x, y) if x == y:
|
||||||
|
print(f"Y=X at {x}")
|
||||||
|
case Point(x, y):
|
||||||
|
print(f"Not on the diagonal")
|
||||||
|
|
||||||
|
def func():
|
||||||
|
from enum import Enum
|
||||||
|
class Color(Enum):
|
||||||
|
RED = 'red'
|
||||||
|
GREEN = 'green'
|
||||||
|
BLUE = 'blue'
|
||||||
|
|
||||||
|
color = Color(input("Enter your choice of 'red', 'blue' or 'green': "))
|
||||||
|
|
||||||
|
match color:
|
||||||
|
case Color.RED:
|
||||||
|
print("I see red!")
|
||||||
|
case Color.GREEN:
|
||||||
|
print("Grass is green")
|
||||||
|
case Color.BLUE:
|
||||||
|
print("I'm feeling the blues :(")
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
def func():
|
||||||
|
raise Exception
|
||||||
|
|
||||||
|
def func():
|
||||||
|
raise "a glass!"
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
def func():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def func():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def func():
|
||||||
|
return
|
||||||
|
|
||||||
|
def func():
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def func():
|
||||||
|
return 1
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
def func():
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
def func():
|
||||||
|
i = 0
|
||||||
|
i += 2
|
||||||
|
return i
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
def func():
|
||||||
|
try:
|
||||||
|
...
|
||||||
|
except Exception:
|
||||||
|
...
|
||||||
|
except OtherException as e:
|
||||||
|
...
|
||||||
|
else:
|
||||||
|
...
|
||||||
|
finally:
|
||||||
|
...
|
||||||
|
|
||||||
|
def func():
|
||||||
|
try:
|
||||||
|
...
|
||||||
|
except Exception:
|
||||||
|
...
|
||||||
|
|
||||||
|
def func():
|
||||||
|
try:
|
||||||
|
...
|
||||||
|
except Exception:
|
||||||
|
...
|
||||||
|
except OtherException as e:
|
||||||
|
...
|
||||||
|
|
||||||
|
def func():
|
||||||
|
try:
|
||||||
|
...
|
||||||
|
except Exception:
|
||||||
|
...
|
||||||
|
except OtherException as e:
|
||||||
|
...
|
||||||
|
else:
|
||||||
|
...
|
||||||
|
|
||||||
|
def func():
|
||||||
|
try:
|
||||||
|
...
|
||||||
|
finally:
|
||||||
|
...
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
def func():
|
||||||
|
while False:
|
||||||
|
return "unreachable"
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def func():
|
||||||
|
while False:
|
||||||
|
return "unreachable"
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def func():
|
||||||
|
while False:
|
||||||
|
return "unreachable"
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
return "also unreachable"
|
||||||
|
|
||||||
|
def func():
|
||||||
|
while True:
|
||||||
|
return 1
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
def func():
|
||||||
|
while True:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
def func():
|
||||||
|
while True:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
return "unreachable"
|
||||||
|
return "also unreachable"
|
||||||
|
|
||||||
|
def func():
|
||||||
|
i = 0
|
||||||
|
while False:
|
||||||
|
i += 1
|
||||||
|
return i
|
||||||
|
|
||||||
|
def func():
|
||||||
|
i = 0
|
||||||
|
while True:
|
||||||
|
i += 1
|
||||||
|
return i
|
||||||
|
|
||||||
|
def func():
|
||||||
|
while True:
|
||||||
|
pass
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def func():
|
||||||
|
i = 0
|
||||||
|
while True:
|
||||||
|
if True:
|
||||||
|
print("ok")
|
||||||
|
i += 1
|
||||||
|
return i
|
||||||
|
|
||||||
|
def func():
|
||||||
|
i = 0
|
||||||
|
while True:
|
||||||
|
if False:
|
||||||
|
print("ok")
|
||||||
|
i += 1
|
||||||
|
return i
|
||||||
|
|
||||||
|
def func():
|
||||||
|
while True:
|
||||||
|
if True:
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def func():
|
||||||
|
while True:
|
||||||
|
continue
|
||||||
|
|
||||||
|
def func():
|
||||||
|
while False:
|
||||||
|
continue
|
||||||
|
|
||||||
|
def func():
|
||||||
|
while True:
|
||||||
|
break
|
||||||
|
|
||||||
|
def func():
|
||||||
|
while False:
|
||||||
|
break
|
||||||
|
|
||||||
|
def func():
|
||||||
|
while True:
|
||||||
|
if True:
|
||||||
|
continue
|
||||||
|
|
||||||
|
def func():
|
||||||
|
while True:
|
||||||
|
if True:
|
||||||
|
break
|
||||||
|
|
||||||
|
'''
|
||||||
|
TODO: because `try` statements aren't handled this triggers a false positive as
|
||||||
|
the last statement is reached, but the rules thinks it isn't (it doesn't
|
||||||
|
see/process the break statement).
|
||||||
|
|
||||||
|
# Test case found in the Bokeh repository that trigger a false positive.
|
||||||
|
def bokeh2(self, host: str = DEFAULT_HOST, port: int = DEFAULT_PORT) -> None:
|
||||||
|
self.stop_serving = False
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
self.server = HTTPServer((host, port), HtmlOnlyHandler)
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
break
|
||||||
|
except OSError:
|
||||||
|
log.debug(f"port {port} is in use, trying to next one")
|
||||||
|
port += 1
|
||||||
|
|
||||||
|
self.thread = threading.Thread(target=self._run_web_server)
|
||||||
|
'''
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
print(eval("1+1")) # S307
|
||||||
|
print(eval("os.getcwd()")) # S307
|
||||||
|
|
||||||
|
|
||||||
|
class Class(object):
|
||||||
|
def eval(self):
|
||||||
|
print("hi")
|
||||||
|
|
||||||
|
def foo(self):
|
||||||
|
self.eval() # OK
|
||||||
|
|
@ -23,6 +23,10 @@ class Foobar(unittest.TestCase):
|
||||||
with self.assertRaises(Exception):
|
with self.assertRaises(Exception):
|
||||||
raise Exception("Evil I say!")
|
raise Exception("Evil I say!")
|
||||||
|
|
||||||
|
def also_evil_raises(self) -> None:
|
||||||
|
with self.assertRaises(BaseException):
|
||||||
|
raise Exception("Evil I say!")
|
||||||
|
|
||||||
def context_manager_raises(self) -> None:
|
def context_manager_raises(self) -> None:
|
||||||
with self.assertRaises(Exception) as ex:
|
with self.assertRaises(Exception) as ex:
|
||||||
raise Exception("Context manager is good")
|
raise Exception("Context manager is good")
|
||||||
|
|
@ -41,6 +45,9 @@ def test_pytest_raises():
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
raise ValueError("Hello")
|
raise ValueError("Hello")
|
||||||
|
|
||||||
|
with pytest.raises(Exception), pytest.raises(ValueError):
|
||||||
|
raise ValueError("Hello")
|
||||||
|
|
||||||
with pytest.raises(Exception, "hello"):
|
with pytest.raises(Exception, "hello"):
|
||||||
raise ValueError("This is fine")
|
raise ValueError("This is fine")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -111,3 +111,19 @@ class PerfectlyFine(models.Model):
|
||||||
@property
|
@property
|
||||||
def random_property(self):
|
def random_property(self):
|
||||||
return "%s" % self
|
return "%s" % self
|
||||||
|
|
||||||
|
|
||||||
|
class MultipleConsecutiveFields(models.Model):
|
||||||
|
"""Model that contains multiple out-of-order field definitions in a row."""
|
||||||
|
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "test"
|
||||||
|
|
||||||
|
first_name = models.CharField(max_length=32)
|
||||||
|
last_name = models.CharField(max_length=32)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
middle_name = models.CharField(max_length=32)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.version == 'Python 2.7.10': ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||||
|
if 'linux' == sys.platform: ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||||
|
if hasattr(sys, 'maxint'): ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||||
|
if sys.maxsize == 42: ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.version == 'Python 2.7.10': ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||||
|
if 'linux' == sys.platform: ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||||
|
if hasattr(sys, 'maxint'): ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||||
|
if sys.maxsize == 42: ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.version_info[0] == 2: ...
|
||||||
|
if sys.version_info[0] == True: ... # Y003 Unrecognized sys.version_info check # E712 comparison to True should be 'if cond is True:' or 'if cond:'
|
||||||
|
if sys.version_info[0.0] == 2: ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[False] == 2: ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[0j] == 2: ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[0] == (2, 7): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[0] == '2': ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[1:] >= (7, 11): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[::-1] < (11, 7): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[:3] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[:True] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[:1] == (2,): ...
|
||||||
|
if sys.version_info[:1] == (True,): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[:1] == (2, 7): ... # Y005 Version comparison must be against a length-1 tuple
|
||||||
|
if sys.version_info[:2] == (2, 7): ...
|
||||||
|
if sys.version_info[:2] == (2,): ... # Y005 Version comparison must be against a length-2 tuple
|
||||||
|
if sys.version_info[:2] == "lol": ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[:2.0] >= (3, 9): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[:2j] >= (3, 9): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[:, :] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info < [3, 0]: ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info < ('3', '0'): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info >= (3, 4, 3): ... # Y004 Version comparison must use only major and minor version
|
||||||
|
if sys.version_info == (3, 4): ... # Y006 Use only < and >= for version comparisons
|
||||||
|
if sys.version_info > (3, 0): ... # Y006 Use only < and >= for version comparisons
|
||||||
|
if sys.version_info <= (3, 0): ... # Y006 Use only < and >= for version comparisons
|
||||||
|
if sys.version_info < (3, 5): ...
|
||||||
|
if sys.version_info >= (3, 5): ...
|
||||||
|
if (2, 7) <= sys.version_info < (3, 5): ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.version_info[0] == 2: ...
|
||||||
|
if sys.version_info[0] == True: ... # Y003 Unrecognized sys.version_info check # E712 comparison to True should be 'if cond is True:' or 'if cond:'
|
||||||
|
if sys.version_info[0.0] == 2: ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[False] == 2: ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[0j] == 2: ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[0] == (2, 7): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[0] == '2': ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[1:] >= (7, 11): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[::-1] < (11, 7): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[:3] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[:True] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[:1] == (2,): ...
|
||||||
|
if sys.version_info[:1] == (True,): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[:1] == (2, 7): ... # Y005 Version comparison must be against a length-1 tuple
|
||||||
|
if sys.version_info[:2] == (2, 7): ...
|
||||||
|
if sys.version_info[:2] == (2,): ... # Y005 Version comparison must be against a length-2 tuple
|
||||||
|
if sys.version_info[:2] == "lol": ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[:2.0] >= (3, 9): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[:2j] >= (3, 9): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info[:, :] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info < [3, 0]: ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info < ('3', '0'): ... # Y003 Unrecognized sys.version_info check
|
||||||
|
if sys.version_info >= (3, 4, 3): ... # Y004 Version comparison must use only major and minor version
|
||||||
|
if sys.version_info == (3, 4): ... # Y006 Use only < and >= for version comparisons
|
||||||
|
if sys.version_info > (3, 0): ... # Y006 Use only < and >= for version comparisons
|
||||||
|
if sys.version_info <= (3, 0): ... # Y006 Use only < and >= for version comparisons
|
||||||
|
if sys.version_info < (3, 5): ...
|
||||||
|
if sys.version_info >= (3, 5): ...
|
||||||
|
if (2, 7) <= sys.version_info < (3, 5): ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import sys
|
||||||
|
from sys import version_info
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 4, 3): ... # PYI004
|
||||||
|
if sys.version_info < (3, 4, 3): ... # PYI004
|
||||||
|
if sys.version_info == (3, 4, 3): ... # PYI004
|
||||||
|
if sys.version_info != (3, 4, 3): ... # PYI004
|
||||||
|
|
||||||
|
if sys.version_info[0] == 2: ...
|
||||||
|
if version_info[0] == 2: ...
|
||||||
|
if sys.version_info < (3, 5): ...
|
||||||
|
if version_info >= (3, 5): ...
|
||||||
|
if sys.version_info[:2] == (2, 7): ...
|
||||||
|
if sys.version_info[:1] == (2,): ...
|
||||||
|
if sys.platform == 'linux': ...
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import sys
|
||||||
|
from sys import version_info
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 4, 3): ... # PYI004
|
||||||
|
if sys.version_info < (3, 4, 3): ... # PYI004
|
||||||
|
if sys.version_info == (3, 4, 3): ... # PYI004
|
||||||
|
if sys.version_info != (3, 4, 3): ... # PYI004
|
||||||
|
|
||||||
|
if sys.version_info[0] == 2: ...
|
||||||
|
if version_info[0] == 2: ...
|
||||||
|
if sys.version_info < (3, 5): ...
|
||||||
|
if version_info >= (3, 5): ...
|
||||||
|
if sys.version_info[:2] == (2, 7): ...
|
||||||
|
if sys.version_info[:1] == (2,): ...
|
||||||
|
if sys.platform == 'linux': ...
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import sys
|
||||||
|
from sys import platform, version_info
|
||||||
|
|
||||||
|
if sys.version_info[:1] == (2, 7): ... # Y005
|
||||||
|
if sys.version_info[:2] == (2,): ... # Y005
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info[0] == 2: ...
|
||||||
|
if version_info[0] == 2: ...
|
||||||
|
if sys.version_info < (3, 5): ...
|
||||||
|
if version_info >= (3, 5): ...
|
||||||
|
if sys.version_info[:2] == (2, 7): ...
|
||||||
|
if sys.version_info[:1] == (2,): ...
|
||||||
|
if platform == 'linux': ...
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import sys
|
||||||
|
from sys import platform, version_info
|
||||||
|
|
||||||
|
if sys.version_info[:1] == (2, 7): ... # Y005
|
||||||
|
if sys.version_info[:2] == (2,): ... # Y005
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info[0] == 2: ...
|
||||||
|
if version_info[0] == 2: ...
|
||||||
|
if sys.version_info < (3, 5): ...
|
||||||
|
if version_info >= (3, 5): ...
|
||||||
|
if sys.version_info[:2] == (2, 7): ...
|
||||||
|
if sys.version_info[:1] == (2,): ...
|
||||||
|
if platform == 'linux': ...
|
||||||
|
|
@ -91,3 +91,4 @@ field27 = list[str]
|
||||||
field28 = builtins.str
|
field28 = builtins.str
|
||||||
field29 = str
|
field29 = str
|
||||||
field30 = str | bytes | None
|
field30 = str | bytes | None
|
||||||
|
field31: typing.Final = field30
|
||||||
|
|
|
||||||
|
|
@ -98,3 +98,4 @@ field27 = list[str]
|
||||||
field28 = builtins.str
|
field28 = builtins.str
|
||||||
field29 = str
|
field29 = str
|
||||||
field30 = str | bytes | None
|
field30 = str | bytes | None
|
||||||
|
field31: typing.Final = field30
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
# Do this (new version)
|
# Do this (new version)
|
||||||
from numpy.random import default_rng
|
from numpy.random import default_rng
|
||||||
|
|
||||||
rng = default_rng()
|
rng = default_rng()
|
||||||
vals = rng.standard_normal(10)
|
vals = rng.standard_normal(10)
|
||||||
more_vals = rng.standard_normal(10)
|
more_vals = rng.standard_normal(10)
|
||||||
|
|
@ -7,11 +8,13 @@ numbers = rng.integers(high, size=5)
|
||||||
|
|
||||||
# instead of this (legacy version)
|
# instead of this (legacy version)
|
||||||
from numpy import random
|
from numpy import random
|
||||||
|
|
||||||
vals = random.standard_normal(10)
|
vals = random.standard_normal(10)
|
||||||
more_vals = random.standard_normal(10)
|
more_vals = random.standard_normal(10)
|
||||||
numbers = random.integers(high, size=5)
|
numbers = random.integers(high, size=5)
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
|
|
||||||
numpy.random.seed()
|
numpy.random.seed()
|
||||||
numpy.random.get_state()
|
numpy.random.get_state()
|
||||||
numpy.random.set_state()
|
numpy.random.set_state()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
np.round_(np.random.rand(5, 5), 2)
|
||||||
|
np.product(np.random.rand(5, 5))
|
||||||
|
np.cumproduct(np.random.rand(5, 5))
|
||||||
|
np.sometrue(np.random.rand(5, 5))
|
||||||
|
np.alltrue(np.random.rand(5, 5))
|
||||||
|
|
||||||
|
from numpy import round_, product, cumproduct, sometrue, alltrue
|
||||||
|
|
||||||
|
round_(np.random.rand(5, 5), 2)
|
||||||
|
product(np.random.rand(5, 5))
|
||||||
|
cumproduct(np.random.rand(5, 5))
|
||||||
|
sometrue(np.random.rand(5, 5))
|
||||||
|
alltrue(np.random.rand(5, 5))
|
||||||
|
|
@ -4,7 +4,9 @@ x = pd.DataFrame()
|
||||||
|
|
||||||
x.drop(["a"], axis=1, inplace=True)
|
x.drop(["a"], axis=1, inplace=True)
|
||||||
|
|
||||||
x.drop(["a"], axis=1, inplace=True)
|
x.y.drop(["a"], axis=1, inplace=True)
|
||||||
|
|
||||||
|
x["y"].drop(["a"], axis=1, inplace=True)
|
||||||
|
|
||||||
x.drop(
|
x.drop(
|
||||||
inplace=True,
|
inplace=True,
|
||||||
|
|
@ -23,6 +25,7 @@ x.drop(["a"], axis=1, **kwargs, inplace=True)
|
||||||
x.drop(["a"], axis=1, inplace=True, **kwargs)
|
x.drop(["a"], axis=1, inplace=True, **kwargs)
|
||||||
f(x.drop(["a"], axis=1, inplace=True))
|
f(x.drop(["a"], axis=1, inplace=True))
|
||||||
|
|
||||||
x.apply(lambda x: x.sort_values('a', inplace=True))
|
x.apply(lambda x: x.sort_values("a", inplace=True))
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
torch.m.ReLU(inplace=True) # safe because this isn't a pandas call
|
torch.m.ReLU(inplace=True) # safe because this isn't a pandas call
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
def f():
|
||||||
|
items = [1, 2, 3, 4]
|
||||||
|
result = []
|
||||||
|
for i in items:
|
||||||
|
if i % 2:
|
||||||
|
result.append(i) # PERF401
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
items = [1, 2, 3, 4]
|
||||||
|
result = []
|
||||||
|
for i in items:
|
||||||
|
result.append(i * i) # PERF401
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
items = [1, 2, 3, 4]
|
||||||
|
result = []
|
||||||
|
for i in items:
|
||||||
|
if i % 2:
|
||||||
|
result.append(i) # PERF401
|
||||||
|
elif i % 2:
|
||||||
|
result.append(i) # PERF401
|
||||||
|
else:
|
||||||
|
result.append(i) # PERF401
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
items = [1, 2, 3, 4]
|
||||||
|
result = []
|
||||||
|
for i in items:
|
||||||
|
result.append(i) # OK
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
def f():
|
||||||
|
items = [1, 2, 3, 4]
|
||||||
|
result = []
|
||||||
|
for i in items:
|
||||||
|
result.append(i) # PERF402
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
items = [1, 2, 3, 4]
|
||||||
|
result = []
|
||||||
|
for i in items:
|
||||||
|
result.insert(0, i) # PERF402
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
items = [1, 2, 3, 4]
|
||||||
|
result = []
|
||||||
|
for i in items:
|
||||||
|
result.append(i * i) # OK
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
def f(a: int, b: int) -> int:
|
||||||
|
"""Showcase function.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
a : int
|
||||||
|
_description_
|
||||||
|
b : int
|
||||||
|
_description_
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
int
|
||||||
|
_description
|
||||||
|
"""
|
||||||
|
return b - a
|
||||||
|
|
||||||
|
|
||||||
|
def f() -> int:
|
||||||
|
"""Showcase function.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
"""
|
||||||
|
|
@ -70,3 +70,8 @@ print("foo".encode()) # print(b"foo")
|
||||||
"abc"
|
"abc"
|
||||||
"def"
|
"def"
|
||||||
)).encode()
|
)).encode()
|
||||||
|
|
||||||
|
(f"foo{bar}").encode("utf-8")
|
||||||
|
(f"foo{bar}").encode(encoding="utf-8")
|
||||||
|
("unicode text©").encode("utf-8")
|
||||||
|
("unicode text©").encode(encoding="utf-8")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,185 @@
|
||||||
|
def after_return():
|
||||||
|
return "reachable"
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
async def also_works_on_async_functions():
|
||||||
|
return "reachable"
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
def if_always_true():
|
||||||
|
if True:
|
||||||
|
return "reachable"
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
def if_always_false():
|
||||||
|
if False:
|
||||||
|
return "unreachable"
|
||||||
|
return "reachable"
|
||||||
|
|
||||||
|
def if_elif_always_false():
|
||||||
|
if False:
|
||||||
|
return "unreachable"
|
||||||
|
elif False:
|
||||||
|
return "also unreachable"
|
||||||
|
return "reachable"
|
||||||
|
|
||||||
|
def if_elif_always_true():
|
||||||
|
if False:
|
||||||
|
return "unreachable"
|
||||||
|
elif True:
|
||||||
|
return "reachable"
|
||||||
|
return "also unreachable"
|
||||||
|
|
||||||
|
def ends_with_if():
|
||||||
|
if False:
|
||||||
|
return "unreachable"
|
||||||
|
else:
|
||||||
|
return "reachable"
|
||||||
|
|
||||||
|
def infinite_loop():
|
||||||
|
while True:
|
||||||
|
continue
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
''' TODO: we could determine these, but we don't yet.
|
||||||
|
def for_range_return():
|
||||||
|
for i in range(10):
|
||||||
|
if i == 5:
|
||||||
|
return "reachable"
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
def for_range_else():
|
||||||
|
for i in range(111):
|
||||||
|
if i == 5:
|
||||||
|
return "reachable"
|
||||||
|
else:
|
||||||
|
return "unreachable"
|
||||||
|
return "also unreachable"
|
||||||
|
|
||||||
|
def for_range_break():
|
||||||
|
for i in range(13):
|
||||||
|
return "reachable"
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
def for_range_if_break():
|
||||||
|
for i in range(1110):
|
||||||
|
if True:
|
||||||
|
return "reachable"
|
||||||
|
return "unreachable"
|
||||||
|
'''
|
||||||
|
|
||||||
|
def match_wildcard(status):
|
||||||
|
match status:
|
||||||
|
case _:
|
||||||
|
return "reachable"
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
def match_case_and_wildcard(status):
|
||||||
|
match status:
|
||||||
|
case 1:
|
||||||
|
return "reachable"
|
||||||
|
case _:
|
||||||
|
return "reachable"
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
def raise_exception():
|
||||||
|
raise Exception
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
def while_false():
|
||||||
|
while False:
|
||||||
|
return "unreachable"
|
||||||
|
return "reachable"
|
||||||
|
|
||||||
|
def while_false_else():
|
||||||
|
while False:
|
||||||
|
return "unreachable"
|
||||||
|
else:
|
||||||
|
return "reachable"
|
||||||
|
|
||||||
|
def while_false_else_return():
|
||||||
|
while False:
|
||||||
|
return "unreachable"
|
||||||
|
else:
|
||||||
|
return "reachable"
|
||||||
|
return "also unreachable"
|
||||||
|
|
||||||
|
def while_true():
|
||||||
|
while True:
|
||||||
|
return "reachable"
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
def while_true_else():
|
||||||
|
while True:
|
||||||
|
return "reachable"
|
||||||
|
else:
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
def while_true_else_return():
|
||||||
|
while True:
|
||||||
|
return "reachable"
|
||||||
|
else:
|
||||||
|
return "unreachable"
|
||||||
|
return "also unreachable"
|
||||||
|
|
||||||
|
def while_false_var_i():
|
||||||
|
i = 0
|
||||||
|
while False:
|
||||||
|
i += 1
|
||||||
|
return i
|
||||||
|
|
||||||
|
def while_true_var_i():
|
||||||
|
i = 0
|
||||||
|
while True:
|
||||||
|
i += 1
|
||||||
|
return i
|
||||||
|
|
||||||
|
def while_infinite():
|
||||||
|
while True:
|
||||||
|
pass
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
def while_if_true():
|
||||||
|
while True:
|
||||||
|
if True:
|
||||||
|
return "reachable"
|
||||||
|
return "unreachable"
|
||||||
|
|
||||||
|
# Test case found in the Bokeh repository that trigger a false positive.
|
||||||
|
def bokeh1(self, obj: BytesRep) -> bytes:
|
||||||
|
data = obj["data"]
|
||||||
|
|
||||||
|
if isinstance(data, str):
|
||||||
|
return base64.b64decode(data)
|
||||||
|
elif isinstance(data, Buffer):
|
||||||
|
buffer = data
|
||||||
|
else:
|
||||||
|
id = data["id"]
|
||||||
|
|
||||||
|
if id in self._buffers:
|
||||||
|
buffer = self._buffers[id]
|
||||||
|
else:
|
||||||
|
self.error(f"can't resolve buffer '{id}'")
|
||||||
|
|
||||||
|
return buffer.data
|
||||||
|
|
||||||
|
'''
|
||||||
|
TODO: because `try` statements aren't handled this triggers a false positive as
|
||||||
|
the last statement is reached, but the rules thinks it isn't (it doesn't
|
||||||
|
see/process the break statement).
|
||||||
|
|
||||||
|
# Test case found in the Bokeh repository that trigger a false positive.
|
||||||
|
def bokeh2(self, host: str = DEFAULT_HOST, port: int = DEFAULT_PORT) -> None:
|
||||||
|
self.stop_serving = False
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
self.server = HTTPServer((host, port), HtmlOnlyHandler)
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
break
|
||||||
|
except OSError:
|
||||||
|
log.debug(f"port {port} is in use, trying to next one")
|
||||||
|
port += 1
|
||||||
|
|
||||||
|
self.thread = threading.Thread(target=self._run_web_server)
|
||||||
|
'''
|
||||||
|
|
@ -36,6 +36,7 @@ use crate::importer::Importer;
|
||||||
use crate::noqa::NoqaMapping;
|
use crate::noqa::NoqaMapping;
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
use crate::rules::flake8_builtins::helpers::AnyShadowing;
|
use crate::rules::flake8_builtins::helpers::AnyShadowing;
|
||||||
|
|
||||||
use crate::rules::{
|
use crate::rules::{
|
||||||
airflow, flake8_2020, flake8_annotations, flake8_async, flake8_bandit, flake8_blind_except,
|
airflow, flake8_2020, flake8_annotations, flake8_async, flake8_bandit, flake8_blind_except,
|
||||||
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez,
|
flake8_boolean_trap, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_datetimez,
|
||||||
|
|
@ -71,7 +72,7 @@ pub(crate) struct Checker<'a> {
|
||||||
deferred: Deferred<'a>,
|
deferred: Deferred<'a>,
|
||||||
pub(crate) diagnostics: Vec<Diagnostic>,
|
pub(crate) diagnostics: Vec<Diagnostic>,
|
||||||
// Check-specific state.
|
// Check-specific state.
|
||||||
pub(crate) flake8_bugbear_seen: Vec<&'a Expr>,
|
pub(crate) flake8_bugbear_seen: Vec<&'a ast::ExprName>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Checker<'a> {
|
impl<'a> Checker<'a> {
|
||||||
|
|
@ -358,11 +359,7 @@ where
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
if self.enabled(Rule::DjangoNonLeadingReceiverDecorator) {
|
if self.enabled(Rule::DjangoNonLeadingReceiverDecorator) {
|
||||||
self.diagnostics
|
flake8_django::rules::non_leading_receiver_decorator(self, decorator_list);
|
||||||
.extend(flake8_django::rules::non_leading_receiver_decorator(
|
|
||||||
decorator_list,
|
|
||||||
|expr| self.semantic.resolve_call_path(expr),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::AmbiguousFunctionName) {
|
if self.enabled(Rule::AmbiguousFunctionName) {
|
||||||
if let Some(diagnostic) =
|
if let Some(diagnostic) =
|
||||||
|
|
@ -504,8 +501,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::HardcodedPasswordDefault) {
|
if self.enabled(Rule::HardcodedPasswordDefault) {
|
||||||
self.diagnostics
|
flake8_bandit::rules::hardcoded_password_default(self, args);
|
||||||
.extend(flake8_bandit::rules::hardcoded_password_default(args));
|
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::PropertyWithParameters) {
|
if self.enabled(Rule::PropertyWithParameters) {
|
||||||
pylint::rules::property_with_parameters(self, stmt, decorator_list, args);
|
pylint::rules::property_with_parameters(self, stmt, decorator_list, args);
|
||||||
|
|
@ -623,6 +619,11 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "unreachable-code")]
|
||||||
|
if self.enabled(Rule::UnreachableCode) {
|
||||||
|
self.diagnostics
|
||||||
|
.extend(ruff::rules::unreachable::in_function(name, body));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Stmt::Return(_) => {
|
Stmt::Return(_) => {
|
||||||
if self.enabled(Rule::ReturnOutsideFunction) {
|
if self.enabled(Rule::ReturnOutsideFunction) {
|
||||||
|
|
@ -643,10 +644,7 @@ where
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
if self.enabled(Rule::DjangoNullableModelStringField) {
|
if self.enabled(Rule::DjangoNullableModelStringField) {
|
||||||
self.diagnostics
|
flake8_django::rules::nullable_model_string_field(self, body);
|
||||||
.extend(flake8_django::rules::nullable_model_string_field(
|
|
||||||
self, body,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::DjangoExcludeWithModelForm) {
|
if self.enabled(Rule::DjangoExcludeWithModelForm) {
|
||||||
if let Some(diagnostic) =
|
if let Some(diagnostic) =
|
||||||
|
|
@ -667,21 +665,17 @@ where
|
||||||
}
|
}
|
||||||
if !self.is_stub {
|
if !self.is_stub {
|
||||||
if self.enabled(Rule::DjangoModelWithoutDunderStr) {
|
if self.enabled(Rule::DjangoModelWithoutDunderStr) {
|
||||||
if let Some(diagnostic) =
|
flake8_django::rules::model_without_dunder_str(self, class_def);
|
||||||
flake8_django::rules::model_without_dunder_str(self, bases, body, stmt)
|
|
||||||
{
|
|
||||||
self.diagnostics.push(diagnostic);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::GlobalStatement) {
|
if self.enabled(Rule::GlobalStatement) {
|
||||||
pylint::rules::global_statement(self, name);
|
pylint::rules::global_statement(self, name);
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::UselessObjectInheritance) {
|
if self.enabled(Rule::UselessObjectInheritance) {
|
||||||
pyupgrade::rules::useless_object_inheritance(self, class_def, stmt);
|
pyupgrade::rules::useless_object_inheritance(self, class_def);
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::UnnecessaryClassParentheses) {
|
if self.enabled(Rule::UnnecessaryClassParentheses) {
|
||||||
pyupgrade::rules::unnecessary_class_parentheses(self, class_def, stmt);
|
pyupgrade::rules::unnecessary_class_parentheses(self, class_def);
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::AmbiguousClassName) {
|
if self.enabled(Rule::AmbiguousClassName) {
|
||||||
if let Some(diagnostic) =
|
if let Some(diagnostic) =
|
||||||
|
|
@ -1370,6 +1364,51 @@ where
|
||||||
self.diagnostics.push(diagnostic);
|
self.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if self.is_stub {
|
||||||
|
if self.any_enabled(&[
|
||||||
|
Rule::UnrecognizedVersionInfoCheck,
|
||||||
|
Rule::PatchVersionComparison,
|
||||||
|
Rule::WrongTupleLengthVersionComparison,
|
||||||
|
]) {
|
||||||
|
if let Expr::BoolOp(ast::ExprBoolOp { values, .. }) = test.as_ref() {
|
||||||
|
for value in values {
|
||||||
|
flake8_pyi::rules::unrecognized_version_info(self, value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
flake8_pyi::rules::unrecognized_version_info(self, test);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.any_enabled(&[
|
||||||
|
Rule::UnrecognizedPlatformCheck,
|
||||||
|
Rule::UnrecognizedPlatformName,
|
||||||
|
]) {
|
||||||
|
if let Expr::BoolOp(ast::ExprBoolOp { values, .. }) = test.as_ref() {
|
||||||
|
for value in values {
|
||||||
|
flake8_pyi::rules::unrecognized_platform(self, value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
flake8_pyi::rules::unrecognized_platform(self, test);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.enabled(Rule::BadVersionInfoComparison) {
|
||||||
|
if let Expr::BoolOp(ast::ExprBoolOp { values, .. }) = test.as_ref() {
|
||||||
|
for value in values {
|
||||||
|
flake8_pyi::rules::bad_version_info_comparison(self, value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
flake8_pyi::rules::bad_version_info_comparison(self, test);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.enabled(Rule::ComplexIfStatementInStub) {
|
||||||
|
if let Expr::BoolOp(ast::ExprBoolOp { values, .. }) = test.as_ref() {
|
||||||
|
for value in values {
|
||||||
|
flake8_pyi::rules::complex_if_statement_in_stub(self, value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
flake8_pyi::rules::complex_if_statement_in_stub(self, test);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Stmt::Assert(ast::StmtAssert {
|
Stmt::Assert(ast::StmtAssert {
|
||||||
test,
|
test,
|
||||||
|
|
@ -1409,7 +1448,7 @@ where
|
||||||
Stmt::With(ast::StmtWith { items, body, .. })
|
Stmt::With(ast::StmtWith { items, body, .. })
|
||||||
| Stmt::AsyncWith(ast::StmtAsyncWith { items, body, .. }) => {
|
| Stmt::AsyncWith(ast::StmtAsyncWith { items, body, .. }) => {
|
||||||
if self.enabled(Rule::AssertRaisesException) {
|
if self.enabled(Rule::AssertRaisesException) {
|
||||||
flake8_bugbear::rules::assert_raises_exception(self, stmt, items);
|
flake8_bugbear::rules::assert_raises_exception(self, items);
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::PytestRaisesWithMultipleStatements) {
|
if self.enabled(Rule::PytestRaisesWithMultipleStatements) {
|
||||||
flake8_pytest_style::rules::complex_raises(self, stmt, items, body);
|
flake8_pytest_style::rules::complex_raises(self, stmt, items, body);
|
||||||
|
|
@ -1490,6 +1529,12 @@ where
|
||||||
if self.enabled(Rule::IncorrectDictIterator) {
|
if self.enabled(Rule::IncorrectDictIterator) {
|
||||||
perflint::rules::incorrect_dict_iterator(self, target, iter);
|
perflint::rules::incorrect_dict_iterator(self, target, iter);
|
||||||
}
|
}
|
||||||
|
if self.enabled(Rule::ManualListComprehension) {
|
||||||
|
perflint::rules::manual_list_comprehension(self, target, body);
|
||||||
|
}
|
||||||
|
if self.enabled(Rule::ManualListCopy) {
|
||||||
|
perflint::rules::manual_list_copy(self, target, body);
|
||||||
|
}
|
||||||
if self.enabled(Rule::UnnecessaryListCast) {
|
if self.enabled(Rule::UnnecessaryListCast) {
|
||||||
perflint::rules::unnecessary_list_cast(self, iter);
|
perflint::rules::unnecessary_list_cast(self, iter);
|
||||||
}
|
}
|
||||||
|
|
@ -1528,9 +1573,7 @@ where
|
||||||
pyupgrade::rules::os_error_alias_handlers(self, handlers);
|
pyupgrade::rules::os_error_alias_handlers(self, handlers);
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::PytestAssertInExcept) {
|
if self.enabled(Rule::PytestAssertInExcept) {
|
||||||
self.diagnostics.extend(
|
flake8_pytest_style::rules::assert_in_exception_handler(self, handlers);
|
||||||
flake8_pytest_style::rules::assert_in_exception_handler(handlers),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::SuppressibleException) {
|
if self.enabled(Rule::SuppressibleException) {
|
||||||
flake8_simplify::rules::suppressible_exception(
|
flake8_simplify::rules::suppressible_exception(
|
||||||
|
|
@ -1571,11 +1614,7 @@ where
|
||||||
flake8_bugbear::rules::assignment_to_os_environ(self, targets);
|
flake8_bugbear::rules::assignment_to_os_environ(self, targets);
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::HardcodedPasswordString) {
|
if self.enabled(Rule::HardcodedPasswordString) {
|
||||||
if let Some(diagnostic) =
|
flake8_bandit::rules::assign_hardcoded_password_string(self, value, targets);
|
||||||
flake8_bandit::rules::assign_hardcoded_password_string(value, targets)
|
|
||||||
{
|
|
||||||
self.diagnostics.push(diagnostic);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::GlobalStatement) {
|
if self.enabled(Rule::GlobalStatement) {
|
||||||
for target in targets.iter() {
|
for target in targets.iter() {
|
||||||
|
|
@ -2107,6 +2146,7 @@ where
|
||||||
&& self.settings.target_version >= PythonVersion::Py37
|
&& self.settings.target_version >= PythonVersion::Py37
|
||||||
&& !self.semantic.future_annotations()
|
&& !self.semantic.future_annotations()
|
||||||
&& self.semantic.in_annotation()
|
&& self.semantic.in_annotation()
|
||||||
|
&& !self.settings.pyupgrade.keep_runtime_typing
|
||||||
{
|
{
|
||||||
flake8_future_annotations::rules::future_rewritable_type_annotation(
|
flake8_future_annotations::rules::future_rewritable_type_annotation(
|
||||||
self, value,
|
self, value,
|
||||||
|
|
@ -2117,7 +2157,8 @@ where
|
||||||
if self.settings.target_version >= PythonVersion::Py310
|
if self.settings.target_version >= PythonVersion::Py310
|
||||||
|| (self.settings.target_version >= PythonVersion::Py37
|
|| (self.settings.target_version >= PythonVersion::Py37
|
||||||
&& self.semantic.future_annotations()
|
&& self.semantic.future_annotations()
|
||||||
&& self.semantic.in_annotation())
|
&& self.semantic.in_annotation()
|
||||||
|
&& !self.settings.pyupgrade.keep_runtime_typing)
|
||||||
{
|
{
|
||||||
pyupgrade::rules::use_pep604_annotation(
|
pyupgrade::rules::use_pep604_annotation(
|
||||||
self, expr, slice, operator,
|
self, expr, slice, operator,
|
||||||
|
|
@ -2193,6 +2234,9 @@ where
|
||||||
if self.enabled(Rule::NumpyDeprecatedTypeAlias) {
|
if self.enabled(Rule::NumpyDeprecatedTypeAlias) {
|
||||||
numpy::rules::deprecated_type_alias(self, expr);
|
numpy::rules::deprecated_type_alias(self, expr);
|
||||||
}
|
}
|
||||||
|
if self.enabled(Rule::NumpyDeprecatedFunction) {
|
||||||
|
numpy::rules::deprecated_function(self, expr);
|
||||||
|
}
|
||||||
if self.is_stub {
|
if self.is_stub {
|
||||||
if self.enabled(Rule::CollectionsNamedTuple) {
|
if self.enabled(Rule::CollectionsNamedTuple) {
|
||||||
flake8_pyi::rules::collections_named_tuple(self, expr);
|
flake8_pyi::rules::collections_named_tuple(self, expr);
|
||||||
|
|
@ -2212,6 +2256,7 @@ where
|
||||||
&& self.settings.target_version >= PythonVersion::Py37
|
&& self.settings.target_version >= PythonVersion::Py37
|
||||||
&& !self.semantic.future_annotations()
|
&& !self.semantic.future_annotations()
|
||||||
&& self.semantic.in_annotation()
|
&& self.semantic.in_annotation()
|
||||||
|
&& !self.settings.pyupgrade.keep_runtime_typing
|
||||||
{
|
{
|
||||||
flake8_future_annotations::rules::future_rewritable_type_annotation(
|
flake8_future_annotations::rules::future_rewritable_type_annotation(
|
||||||
self, expr,
|
self, expr,
|
||||||
|
|
@ -2222,7 +2267,8 @@ where
|
||||||
if self.settings.target_version >= PythonVersion::Py39
|
if self.settings.target_version >= PythonVersion::Py39
|
||||||
|| (self.settings.target_version >= PythonVersion::Py37
|
|| (self.settings.target_version >= PythonVersion::Py37
|
||||||
&& self.semantic.future_annotations()
|
&& self.semantic.future_annotations()
|
||||||
&& self.semantic.in_annotation())
|
&& self.semantic.in_annotation()
|
||||||
|
&& !self.settings.pyupgrade.keep_runtime_typing)
|
||||||
{
|
{
|
||||||
pyupgrade::rules::use_pep585_annotation(
|
pyupgrade::rules::use_pep585_annotation(
|
||||||
self,
|
self,
|
||||||
|
|
@ -2287,6 +2333,7 @@ where
|
||||||
&& self.settings.target_version >= PythonVersion::Py37
|
&& self.settings.target_version >= PythonVersion::Py37
|
||||||
&& !self.semantic.future_annotations()
|
&& !self.semantic.future_annotations()
|
||||||
&& self.semantic.in_annotation()
|
&& self.semantic.in_annotation()
|
||||||
|
&& !self.settings.pyupgrade.keep_runtime_typing
|
||||||
{
|
{
|
||||||
flake8_future_annotations::rules::future_rewritable_type_annotation(
|
flake8_future_annotations::rules::future_rewritable_type_annotation(
|
||||||
self, expr,
|
self, expr,
|
||||||
|
|
@ -2297,7 +2344,8 @@ where
|
||||||
if self.settings.target_version >= PythonVersion::Py39
|
if self.settings.target_version >= PythonVersion::Py39
|
||||||
|| (self.settings.target_version >= PythonVersion::Py37
|
|| (self.settings.target_version >= PythonVersion::Py37
|
||||||
&& self.semantic.future_annotations()
|
&& self.semantic.future_annotations()
|
||||||
&& self.semantic.in_annotation())
|
&& self.semantic.in_annotation()
|
||||||
|
&& !self.settings.pyupgrade.keep_runtime_typing)
|
||||||
{
|
{
|
||||||
pyupgrade::rules::use_pep585_annotation(self, expr, &replacement);
|
pyupgrade::rules::use_pep585_annotation(self, expr, &replacement);
|
||||||
}
|
}
|
||||||
|
|
@ -2315,6 +2363,9 @@ where
|
||||||
if self.enabled(Rule::NumpyDeprecatedTypeAlias) {
|
if self.enabled(Rule::NumpyDeprecatedTypeAlias) {
|
||||||
numpy::rules::deprecated_type_alias(self, expr);
|
numpy::rules::deprecated_type_alias(self, expr);
|
||||||
}
|
}
|
||||||
|
if self.enabled(Rule::NumpyDeprecatedFunction) {
|
||||||
|
numpy::rules::deprecated_function(self, expr);
|
||||||
|
}
|
||||||
if self.enabled(Rule::DeprecatedMockImport) {
|
if self.enabled(Rule::DeprecatedMockImport) {
|
||||||
pyupgrade::rules::deprecated_mock_attribute(self, expr);
|
pyupgrade::rules::deprecated_mock_attribute(self, expr);
|
||||||
}
|
}
|
||||||
|
|
@ -2533,9 +2584,7 @@ where
|
||||||
flake8_pie::rules::unnecessary_dict_kwargs(self, expr, keywords);
|
flake8_pie::rules::unnecessary_dict_kwargs(self, expr, keywords);
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::ExecBuiltin) {
|
if self.enabled(Rule::ExecBuiltin) {
|
||||||
if let Some(diagnostic) = flake8_bandit::rules::exec_used(expr, func) {
|
flake8_bandit::rules::exec_used(self, func);
|
||||||
self.diagnostics.push(diagnostic);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::BadFilePermissions) {
|
if self.enabled(Rule::BadFilePermissions) {
|
||||||
flake8_bandit::rules::bad_file_permissions(self, func, args, keywords);
|
flake8_bandit::rules::bad_file_permissions(self, func, args, keywords);
|
||||||
|
|
@ -2558,8 +2607,7 @@ where
|
||||||
flake8_bandit::rules::jinja2_autoescape_false(self, func, args, keywords);
|
flake8_bandit::rules::jinja2_autoescape_false(self, func, args, keywords);
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::HardcodedPasswordFuncArg) {
|
if self.enabled(Rule::HardcodedPasswordFuncArg) {
|
||||||
self.diagnostics
|
flake8_bandit::rules::hardcoded_password_func_arg(self, keywords);
|
||||||
.extend(flake8_bandit::rules::hardcoded_password_func_arg(keywords));
|
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::HardcodedSQLExpression) {
|
if self.enabled(Rule::HardcodedSQLExpression) {
|
||||||
flake8_bandit::rules::hardcoded_sql_expression(self, expr);
|
flake8_bandit::rules::hardcoded_sql_expression(self, expr);
|
||||||
|
|
@ -2692,17 +2740,12 @@ where
|
||||||
flake8_debugger::rules::debugger_call(self, expr, func);
|
flake8_debugger::rules::debugger_call(self, expr, func);
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::PandasUseOfInplaceArgument) {
|
if self.enabled(Rule::PandasUseOfInplaceArgument) {
|
||||||
self.diagnostics.extend(
|
pandas_vet::rules::inplace_argument(self, expr, func, args, keywords);
|
||||||
pandas_vet::rules::inplace_argument(self, expr, func, args, keywords)
|
|
||||||
.into_iter(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
pandas_vet::rules::call(self, func);
|
pandas_vet::rules::call(self, func);
|
||||||
|
|
||||||
if self.enabled(Rule::PandasUseOfPdMerge) {
|
if self.enabled(Rule::PandasUseOfPdMerge) {
|
||||||
if let Some(diagnostic) = pandas_vet::rules::use_of_pd_merge(func) {
|
pandas_vet::rules::use_of_pd_merge(self, func);
|
||||||
self.diagnostics.push(diagnostic);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::CallDatetimeWithoutTzinfo) {
|
if self.enabled(Rule::CallDatetimeWithoutTzinfo) {
|
||||||
flake8_datetimez::rules::call_datetime_without_tzinfo(
|
flake8_datetimez::rules::call_datetime_without_tzinfo(
|
||||||
|
|
@ -2819,16 +2862,13 @@ where
|
||||||
&self.settings.flake8_gettext.functions_names,
|
&self.settings.flake8_gettext.functions_names,
|
||||||
) {
|
) {
|
||||||
if self.enabled(Rule::FStringInGetTextFuncCall) {
|
if self.enabled(Rule::FStringInGetTextFuncCall) {
|
||||||
self.diagnostics
|
flake8_gettext::rules::f_string_in_gettext_func_call(self, args);
|
||||||
.extend(flake8_gettext::rules::f_string_in_gettext_func_call(args));
|
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::FormatInGetTextFuncCall) {
|
if self.enabled(Rule::FormatInGetTextFuncCall) {
|
||||||
self.diagnostics
|
flake8_gettext::rules::format_in_gettext_func_call(self, args);
|
||||||
.extend(flake8_gettext::rules::format_in_gettext_func_call(args));
|
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::PrintfInGetTextFuncCall) {
|
if self.enabled(Rule::PrintfInGetTextFuncCall) {
|
||||||
self.diagnostics
|
flake8_gettext::rules::printf_in_gettext_func_call(self, args);
|
||||||
.extend(flake8_gettext::rules::printf_in_gettext_func_call(args));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::UncapitalizedEnvironmentVariables) {
|
if self.enabled(Rule::UncapitalizedEnvironmentVariables) {
|
||||||
|
|
@ -2869,7 +2909,7 @@ where
|
||||||
flake8_use_pathlib::rules::replaceable_by_pathlib(self, func);
|
flake8_use_pathlib::rules::replaceable_by_pathlib(self, func);
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::NumpyLegacyRandom) {
|
if self.enabled(Rule::NumpyLegacyRandom) {
|
||||||
numpy::rules::numpy_legacy_random(self, func);
|
numpy::rules::legacy_random(self, func);
|
||||||
}
|
}
|
||||||
if self.any_enabled(&[
|
if self.any_enabled(&[
|
||||||
Rule::LoggingStringFormat,
|
Rule::LoggingStringFormat,
|
||||||
|
|
@ -3169,11 +3209,10 @@ where
|
||||||
flake8_2020::rules::compare(self, left, ops, comparators);
|
flake8_2020::rules::compare(self, left, ops, comparators);
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::HardcodedPasswordString) {
|
if self.enabled(Rule::HardcodedPasswordString) {
|
||||||
self.diagnostics.extend(
|
|
||||||
flake8_bandit::rules::compare_to_hardcoded_password_string(
|
flake8_bandit::rules::compare_to_hardcoded_password_string(
|
||||||
|
self,
|
||||||
left,
|
left,
|
||||||
comparators,
|
comparators,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if self.enabled(Rule::ComparisonWithItself) {
|
if self.enabled(Rule::ComparisonWithItself) {
|
||||||
|
|
@ -3194,29 +3233,6 @@ where
|
||||||
if self.enabled(Rule::YodaConditions) {
|
if self.enabled(Rule::YodaConditions) {
|
||||||
flake8_simplify::rules::yoda_conditions(self, expr, left, ops, comparators);
|
flake8_simplify::rules::yoda_conditions(self, expr, left, ops, comparators);
|
||||||
}
|
}
|
||||||
if self.is_stub {
|
|
||||||
if self.any_enabled(&[
|
|
||||||
Rule::UnrecognizedPlatformCheck,
|
|
||||||
Rule::UnrecognizedPlatformName,
|
|
||||||
]) {
|
|
||||||
flake8_pyi::rules::unrecognized_platform(
|
|
||||||
self,
|
|
||||||
expr,
|
|
||||||
left,
|
|
||||||
ops,
|
|
||||||
comparators,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if self.enabled(Rule::BadVersionInfoComparison) {
|
|
||||||
flake8_pyi::rules::bad_version_info_comparison(
|
|
||||||
self,
|
|
||||||
expr,
|
|
||||||
left,
|
|
||||||
ops,
|
|
||||||
comparators,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Expr::Constant(ast::ExprConstant {
|
Expr::Constant(ast::ExprConstant {
|
||||||
value: Constant::Int(_) | Constant::Float(_) | Constant::Complex { .. },
|
value: Constant::Int(_) | Constant::Float(_) | Constant::Complex { .. },
|
||||||
|
|
@ -4422,7 +4438,7 @@ impl<'a> Checker<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_node_delete(&mut self, expr: &'a Expr) {
|
fn handle_node_delete(&mut self, expr: &'a Expr) {
|
||||||
let Expr::Name(ast::ExprName { id, .. } )= expr else {
|
let Expr::Name(ast::ExprName { id, .. }) = expr else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -596,6 +596,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
|
|
||||||
// flake8-pyi
|
// flake8-pyi
|
||||||
(Flake8Pyi, "001") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnprefixedTypeParam),
|
(Flake8Pyi, "001") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnprefixedTypeParam),
|
||||||
|
(Flake8Pyi, "002") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::ComplexIfStatementInStub),
|
||||||
|
(Flake8Pyi, "003") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnrecognizedVersionInfoCheck),
|
||||||
|
(Flake8Pyi, "004") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::PatchVersionComparison),
|
||||||
|
(Flake8Pyi, "005") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::WrongTupleLengthVersionComparison),
|
||||||
(Flake8Pyi, "006") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::BadVersionInfoComparison),
|
(Flake8Pyi, "006") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::BadVersionInfoComparison),
|
||||||
(Flake8Pyi, "007") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnrecognizedPlatformCheck),
|
(Flake8Pyi, "007") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnrecognizedPlatformCheck),
|
||||||
(Flake8Pyi, "008") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnrecognizedPlatformName),
|
(Flake8Pyi, "008") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnrecognizedPlatformName),
|
||||||
|
|
@ -742,6 +746,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
// numpy
|
// numpy
|
||||||
(Numpy, "001") => (RuleGroup::Unspecified, rules::numpy::rules::NumpyDeprecatedTypeAlias),
|
(Numpy, "001") => (RuleGroup::Unspecified, rules::numpy::rules::NumpyDeprecatedTypeAlias),
|
||||||
(Numpy, "002") => (RuleGroup::Unspecified, rules::numpy::rules::NumpyLegacyRandom),
|
(Numpy, "002") => (RuleGroup::Unspecified, rules::numpy::rules::NumpyLegacyRandom),
|
||||||
|
(Numpy, "003") => (RuleGroup::Unspecified, rules::numpy::rules::NumpyDeprecatedFunction),
|
||||||
|
|
||||||
// ruff
|
// ruff
|
||||||
(Ruff, "001") => (RuleGroup::Unspecified, rules::ruff::rules::AmbiguousUnicodeCharacterString),
|
(Ruff, "001") => (RuleGroup::Unspecified, rules::ruff::rules::AmbiguousUnicodeCharacterString),
|
||||||
|
|
@ -756,6 +761,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Ruff, "011") => (RuleGroup::Unspecified, rules::ruff::rules::StaticKeyDictComprehension),
|
(Ruff, "011") => (RuleGroup::Unspecified, rules::ruff::rules::StaticKeyDictComprehension),
|
||||||
(Ruff, "012") => (RuleGroup::Unspecified, rules::ruff::rules::MutableClassDefault),
|
(Ruff, "012") => (RuleGroup::Unspecified, rules::ruff::rules::MutableClassDefault),
|
||||||
(Ruff, "013") => (RuleGroup::Unspecified, rules::ruff::rules::ImplicitOptional),
|
(Ruff, "013") => (RuleGroup::Unspecified, rules::ruff::rules::ImplicitOptional),
|
||||||
|
#[cfg(feature = "unreachable-code")]
|
||||||
|
(Ruff, "014") => (RuleGroup::Nursery, rules::ruff::rules::UnreachableCode),
|
||||||
(Ruff, "100") => (RuleGroup::Unspecified, rules::ruff::rules::UnusedNOQA),
|
(Ruff, "100") => (RuleGroup::Unspecified, rules::ruff::rules::UnusedNOQA),
|
||||||
(Ruff, "200") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidPyprojectToml),
|
(Ruff, "200") => (RuleGroup::Unspecified, rules::ruff::rules::InvalidPyprojectToml),
|
||||||
|
|
||||||
|
|
@ -788,6 +795,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Perflint, "101") => (RuleGroup::Unspecified, rules::perflint::rules::UnnecessaryListCast),
|
(Perflint, "101") => (RuleGroup::Unspecified, rules::perflint::rules::UnnecessaryListCast),
|
||||||
(Perflint, "102") => (RuleGroup::Unspecified, rules::perflint::rules::IncorrectDictIterator),
|
(Perflint, "102") => (RuleGroup::Unspecified, rules::perflint::rules::IncorrectDictIterator),
|
||||||
(Perflint, "203") => (RuleGroup::Unspecified, rules::perflint::rules::TryExceptInLoop),
|
(Perflint, "203") => (RuleGroup::Unspecified, rules::perflint::rules::TryExceptInLoop),
|
||||||
|
(Perflint, "401") => (RuleGroup::Unspecified, rules::perflint::rules::ManualListComprehension),
|
||||||
|
(Perflint, "402") => (RuleGroup::Unspecified, rules::perflint::rules::ManualListCopy),
|
||||||
|
|
||||||
// flake8-fixme
|
// flake8-fixme
|
||||||
(Flake8Fixme, "001") => (RuleGroup::Unspecified, rules::flake8_fixme::rules::LineContainsFixme),
|
(Flake8Fixme, "001") => (RuleGroup::Unspecified, rules::flake8_fixme::rules::LineContainsFixme),
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use ruff_python_ast::docstrings::{leading_space, leading_words};
|
||||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||||
use strum_macros::EnumIter;
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
use ruff_python_whitespace::{UniversalNewlineIterator, UniversalNewlines};
|
use ruff_python_whitespace::{Line, UniversalNewlineIterator, UniversalNewlines};
|
||||||
|
|
||||||
use crate::docstrings::styles::SectionStyle;
|
use crate::docstrings::styles::SectionStyle;
|
||||||
use crate::docstrings::{Docstring, DocstringBody};
|
use crate::docstrings::{Docstring, DocstringBody};
|
||||||
|
|
@ -144,15 +144,13 @@ impl<'a> SectionContexts<'a> {
|
||||||
|
|
||||||
let mut contexts = Vec::new();
|
let mut contexts = Vec::new();
|
||||||
let mut last: Option<SectionContextData> = None;
|
let mut last: Option<SectionContextData> = None;
|
||||||
let mut previous_line = None;
|
|
||||||
|
|
||||||
for line in contents.universal_newlines() {
|
let mut lines = contents.universal_newlines().peekable();
|
||||||
if previous_line.is_none() {
|
|
||||||
// skip the first line
|
|
||||||
previous_line = Some(line.as_str());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Skip the first line, which is the summary.
|
||||||
|
let mut previous_line = lines.next();
|
||||||
|
|
||||||
|
while let Some(line) = lines.next() {
|
||||||
if let Some(section_kind) = suspected_as_section(&line, style) {
|
if let Some(section_kind) = suspected_as_section(&line, style) {
|
||||||
let indent = leading_space(&line);
|
let indent = leading_space(&line);
|
||||||
let section_name = leading_words(&line);
|
let section_name = leading_words(&line);
|
||||||
|
|
@ -162,7 +160,8 @@ impl<'a> SectionContexts<'a> {
|
||||||
if is_docstring_section(
|
if is_docstring_section(
|
||||||
&line,
|
&line,
|
||||||
section_name_range,
|
section_name_range,
|
||||||
previous_line.unwrap_or_default(),
|
previous_line.as_ref(),
|
||||||
|
lines.peek(),
|
||||||
) {
|
) {
|
||||||
if let Some(mut last) = last.take() {
|
if let Some(mut last) = last.take() {
|
||||||
last.range = TextRange::new(last.range.start(), line.start());
|
last.range = TextRange::new(last.range.start(), line.start());
|
||||||
|
|
@ -178,7 +177,7 @@ impl<'a> SectionContexts<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
previous_line = Some(line.as_str());
|
previous_line = Some(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(mut last) = last.take() {
|
if let Some(mut last) = last.take() {
|
||||||
|
|
@ -388,7 +387,13 @@ fn suspected_as_section(line: &str, style: SectionStyle) -> Option<SectionKind>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the suspected context is really a section header.
|
/// Check if the suspected context is really a section header.
|
||||||
fn is_docstring_section(line: &str, section_name_range: TextRange, previous_lines: &str) -> bool {
|
fn is_docstring_section(
|
||||||
|
line: &Line,
|
||||||
|
section_name_range: TextRange,
|
||||||
|
previous_line: Option<&Line>,
|
||||||
|
next_line: Option<&Line>,
|
||||||
|
) -> bool {
|
||||||
|
// Determine whether the current line looks like a section header, e.g., "Args:".
|
||||||
let section_name_suffix = line[usize::from(section_name_range.end())..].trim();
|
let section_name_suffix = line[usize::from(section_name_range.end())..].trim();
|
||||||
let this_looks_like_a_section_name =
|
let this_looks_like_a_section_name =
|
||||||
section_name_suffix == ":" || section_name_suffix.is_empty();
|
section_name_suffix == ":" || section_name_suffix.is_empty();
|
||||||
|
|
@ -396,13 +401,29 @@ fn is_docstring_section(line: &str, section_name_range: TextRange, previous_line
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let prev_line = previous_lines.trim();
|
// Determine whether the next line is an underline, e.g., "-----".
|
||||||
let prev_line_ends_with_punctuation = [',', ';', '.', '-', '\\', '/', ']', '}', ')']
|
let next_line_is_underline = next_line.map_or(false, |next_line| {
|
||||||
|
let next_line = next_line.trim();
|
||||||
|
if next_line.is_empty() {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
let next_line_is_underline = next_line.chars().all(|char| matches!(char, '-' | '='));
|
||||||
|
next_line_is_underline
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if next_line_is_underline {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine whether the previous line looks like the end of a paragraph.
|
||||||
|
let previous_line_looks_like_end_of_paragraph = previous_line.map_or(true, |previous_line| {
|
||||||
|
let previous_line = previous_line.trim();
|
||||||
|
let previous_line_ends_with_punctuation = [',', ';', '.', '-', '\\', '/', ']', '}', ')']
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.any(|char| prev_line.ends_with(char));
|
.any(|char| previous_line.ends_with(char));
|
||||||
let prev_line_looks_like_end_of_paragraph =
|
previous_line_ends_with_punctuation || previous_line.is_empty()
|
||||||
prev_line_ends_with_punctuation || prev_line.is_empty();
|
});
|
||||||
if !prev_line_looks_like_end_of_paragraph {
|
if !previous_line_looks_like_end_of_paragraph {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -333,7 +333,7 @@ pub(crate) fn infer_plugins_from_codes(selectors: &HashSet<RuleSelector>) -> Vec
|
||||||
for selector in selectors {
|
for selector in selectors {
|
||||||
if selector
|
if selector
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.any(|rule| Linter::from(plugin).into_iter().any(|r| r == rule))
|
.any(|rule| Linter::from(plugin).rules().any(|r| r == rule))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -268,7 +268,8 @@ impl Notebook {
|
||||||
.markers()
|
.markers()
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.find(|m| m.source <= *offset) else {
|
.find(|m| m.source <= *offset)
|
||||||
|
else {
|
||||||
// There are no markers above the current offset, so we can
|
// There are no markers above the current offset, so we can
|
||||||
// stop here.
|
// stop here.
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ impl Rule {
|
||||||
pub fn from_code(code: &str) -> Result<Self, FromCodeError> {
|
pub fn from_code(code: &str) -> Result<Self, FromCodeError> {
|
||||||
let (linter, code) = Linter::parse_code(code).ok_or(FromCodeError::Unknown)?;
|
let (linter, code) = Linter::parse_code(code).ok_or(FromCodeError::Unknown)?;
|
||||||
let prefix: RuleCodePrefix = RuleCodePrefix::parse(&linter, code)?;
|
let prefix: RuleCodePrefix = RuleCodePrefix::parse(&linter, code)?;
|
||||||
Ok(prefix.into_iter().next().unwrap())
|
Ok(prefix.rules().next().unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use ruff_macros::CacheKey;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
use std::iter::FusedIterator;
|
use std::iter::FusedIterator;
|
||||||
|
|
||||||
const RULESET_SIZE: usize = 10;
|
const RULESET_SIZE: usize = 11;
|
||||||
|
|
||||||
/// A set of [`Rule`]s.
|
/// A set of [`Rule`]s.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -158,16 +158,16 @@ impl IntoIterator for &RuleSelector {
|
||||||
}
|
}
|
||||||
RuleSelector::C => RuleSelectorIter::Chain(
|
RuleSelector::C => RuleSelectorIter::Chain(
|
||||||
Linter::Flake8Comprehensions
|
Linter::Flake8Comprehensions
|
||||||
.into_iter()
|
.rules()
|
||||||
.chain(Linter::McCabe.into_iter()),
|
.chain(Linter::McCabe.rules()),
|
||||||
),
|
),
|
||||||
RuleSelector::T => RuleSelectorIter::Chain(
|
RuleSelector::T => RuleSelectorIter::Chain(
|
||||||
Linter::Flake8Debugger
|
Linter::Flake8Debugger
|
||||||
.into_iter()
|
.rules()
|
||||||
.chain(Linter::Flake8Print.into_iter()),
|
.chain(Linter::Flake8Print.rules()),
|
||||||
),
|
),
|
||||||
RuleSelector::Linter(linter) => RuleSelectorIter::Vec(linter.into_iter()),
|
RuleSelector::Linter(linter) => RuleSelectorIter::Vec(linter.rules()),
|
||||||
RuleSelector::Prefix { prefix, .. } => RuleSelectorIter::Vec(prefix.into_iter()),
|
RuleSelector::Prefix { prefix, .. } => RuleSelectorIter::Vec(prefix.clone().rules()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -346,7 +346,7 @@ mod clap_completion {
|
||||||
let prefix = p.linter().common_prefix();
|
let prefix = p.linter().common_prefix();
|
||||||
let code = p.short_code();
|
let code = p.short_code();
|
||||||
|
|
||||||
let mut rules_iter = p.into_iter();
|
let mut rules_iter = p.rules();
|
||||||
let rule1 = rules_iter.next();
|
let rule1 = rules_iter.next();
|
||||||
let rule2 = rules_iter.next();
|
let rule2 = rules_iter.next();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -457,11 +457,7 @@ pub(crate) fn definition(
|
||||||
// TODO(charlie): Consider using the AST directly here rather than `Definition`.
|
// TODO(charlie): Consider using the AST directly here rather than `Definition`.
|
||||||
// We could adhere more closely to `flake8-annotations` by defining public
|
// We could adhere more closely to `flake8-annotations` by defining public
|
||||||
// vs. secret vs. protected.
|
// vs. secret vs. protected.
|
||||||
let Definition::Member(Member {
|
let Definition::Member(Member { kind, stmt, .. }) = definition else {
|
||||||
kind,
|
|
||||||
stmt,
|
|
||||||
..
|
|
||||||
}) = definition else {
|
|
||||||
return vec![];
|
return vec![];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ mod tests {
|
||||||
#[test_case(Rule::SubprocessPopenWithShellEqualsTrue, Path::new("S602.py"))]
|
#[test_case(Rule::SubprocessPopenWithShellEqualsTrue, Path::new("S602.py"))]
|
||||||
#[test_case(Rule::SubprocessWithoutShellEqualsTrue, Path::new("S603.py"))]
|
#[test_case(Rule::SubprocessWithoutShellEqualsTrue, Path::new("S603.py"))]
|
||||||
#[test_case(Rule::SuspiciousPickleUsage, Path::new("S301.py"))]
|
#[test_case(Rule::SuspiciousPickleUsage, Path::new("S301.py"))]
|
||||||
|
#[test_case(Rule::SuspiciousEvalUsage, Path::new("S307.py"))]
|
||||||
#[test_case(Rule::SuspiciousTelnetUsage, Path::new("S312.py"))]
|
#[test_case(Rule::SuspiciousTelnetUsage, Path::new("S312.py"))]
|
||||||
#[test_case(Rule::TryExceptContinue, Path::new("S112.py"))]
|
#[test_case(Rule::TryExceptContinue, Path::new("S112.py"))]
|
||||||
#[test_case(Rule::TryExceptPass, Path::new("S110.py"))]
|
#[test_case(Rule::TryExceptPass, Path::new("S110.py"))]
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
use rustpython_parser::ast::{self, Expr, Ranged};
|
use rustpython_parser::ast::{Expr, Ranged};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct ExecBuiltin;
|
pub struct ExecBuiltin;
|
||||||
|
|
||||||
|
|
@ -14,12 +16,16 @@ impl Violation for ExecBuiltin {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// S102
|
/// S102
|
||||||
pub(crate) fn exec_used(expr: &Expr, func: &Expr) -> Option<Diagnostic> {
|
pub(crate) fn exec_used(checker: &mut Checker, func: &Expr) {
|
||||||
let Expr::Name(ast::ExprName { id, .. }) = func else {
|
if checker
|
||||||
return None;
|
.semantic()
|
||||||
};
|
.resolve_call_path(func)
|
||||||
if id != "exec" {
|
.map_or(false, |call_path| {
|
||||||
return None;
|
matches!(call_path.as_slice(), ["" | "builtin", "exec"])
|
||||||
|
})
|
||||||
|
{
|
||||||
|
checker
|
||||||
|
.diagnostics
|
||||||
|
.push(Diagnostic::new(ExecBuiltin, func.range()));
|
||||||
}
|
}
|
||||||
Some(Diagnostic::new(ExecBuiltin, expr.range()))
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use rustpython_parser::ast::{Arg, ArgWithDefault, Arguments, Expr, Ranged};
|
use rustpython_parser::ast::{Arg, ArgWithDefault, Arguments, Expr, Ranged};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
|
|
@ -36,9 +37,7 @@ fn check_password_kwarg(arg: &Arg, default: &Expr) -> Option<Diagnostic> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// S107
|
/// S107
|
||||||
pub(crate) fn hardcoded_password_default(arguments: &Arguments) -> Vec<Diagnostic> {
|
pub(crate) fn hardcoded_password_default(checker: &mut Checker, arguments: &Arguments) {
|
||||||
let mut diagnostics: Vec<Diagnostic> = Vec::new();
|
|
||||||
|
|
||||||
for ArgWithDefault {
|
for ArgWithDefault {
|
||||||
def,
|
def,
|
||||||
default,
|
default,
|
||||||
|
|
@ -53,9 +52,7 @@ pub(crate) fn hardcoded_password_default(arguments: &Arguments) -> Vec<Diagnosti
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if let Some(diagnostic) = check_password_kwarg(def, default) {
|
if let Some(diagnostic) = check_password_kwarg(def, default) {
|
||||||
diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
diagnostics
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use rustpython_parser::ast::{Keyword, Ranged};
|
use rustpython_parser::ast::{Keyword, Ranged};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
|
|
@ -22,10 +23,10 @@ impl Violation for HardcodedPasswordFuncArg {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// S106
|
/// S106
|
||||||
pub(crate) fn hardcoded_password_func_arg(keywords: &[Keyword]) -> Vec<Diagnostic> {
|
pub(crate) fn hardcoded_password_func_arg(checker: &mut Checker, keywords: &[Keyword]) {
|
||||||
keywords
|
checker
|
||||||
.iter()
|
.diagnostics
|
||||||
.filter_map(|keyword| {
|
.extend(keywords.iter().filter_map(|keyword| {
|
||||||
string_literal(&keyword.value).filter(|string| !string.is_empty())?;
|
string_literal(&keyword.value).filter(|string| !string.is_empty())?;
|
||||||
let arg = keyword.arg.as_ref()?;
|
let arg = keyword.arg.as_ref()?;
|
||||||
if !matches_password_name(arg) {
|
if !matches_password_name(arg) {
|
||||||
|
|
@ -37,6 +38,5 @@ pub(crate) fn hardcoded_password_func_arg(keywords: &[Keyword]) -> Vec<Diagnosti
|
||||||
},
|
},
|
||||||
keyword.range(),
|
keyword.range(),
|
||||||
))
|
))
|
||||||
})
|
}));
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ use rustpython_parser::ast::{self, Constant, Expr, Ranged};
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
use super::super::helpers::{matches_password_name, string_literal};
|
use super::super::helpers::{matches_password_name, string_literal};
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
|
|
@ -47,12 +49,13 @@ fn password_target(target: &Expr) -> Option<&str> {
|
||||||
|
|
||||||
/// S105
|
/// S105
|
||||||
pub(crate) fn compare_to_hardcoded_password_string(
|
pub(crate) fn compare_to_hardcoded_password_string(
|
||||||
|
checker: &mut Checker,
|
||||||
left: &Expr,
|
left: &Expr,
|
||||||
comparators: &[Expr],
|
comparators: &[Expr],
|
||||||
) -> Vec<Diagnostic> {
|
) {
|
||||||
comparators
|
checker
|
||||||
.iter()
|
.diagnostics
|
||||||
.filter_map(|comp| {
|
.extend(comparators.iter().filter_map(|comp| {
|
||||||
string_literal(comp).filter(|string| !string.is_empty())?;
|
string_literal(comp).filter(|string| !string.is_empty())?;
|
||||||
let Some(name) = password_target(left) else {
|
let Some(name) = password_target(left) else {
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -63,29 +66,29 @@ pub(crate) fn compare_to_hardcoded_password_string(
|
||||||
},
|
},
|
||||||
comp.range(),
|
comp.range(),
|
||||||
))
|
))
|
||||||
})
|
}));
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// S105
|
/// S105
|
||||||
pub(crate) fn assign_hardcoded_password_string(
|
pub(crate) fn assign_hardcoded_password_string(
|
||||||
|
checker: &mut Checker,
|
||||||
value: &Expr,
|
value: &Expr,
|
||||||
targets: &[Expr],
|
targets: &[Expr],
|
||||||
) -> Option<Diagnostic> {
|
) {
|
||||||
if string_literal(value)
|
if string_literal(value)
|
||||||
.filter(|string| !string.is_empty())
|
.filter(|string| !string.is_empty())
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
for target in targets {
|
for target in targets {
|
||||||
if let Some(name) = password_target(target) {
|
if let Some(name) = password_target(target) {
|
||||||
return Some(Diagnostic::new(
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
HardcodedPasswordString {
|
HardcodedPasswordString {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
},
|
},
|
||||||
value.range(),
|
value.range(),
|
||||||
));
|
));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ fn unparse_string_format_expression(checker: &mut Checker, expr: &Expr) -> Optio
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
// Only evaluate the full BinOp, not the nested components.
|
// Only evaluate the full BinOp, not the nested components.
|
||||||
let Expr::BinOp(_ )= parent else {
|
let Expr::BinOp(_) = parent else {
|
||||||
if any_over_expr(expr, &has_string_literal) {
|
if any_over_expr(expr, &has_string_literal) {
|
||||||
return Some(checker.generator().expr(expr));
|
return Some(checker.generator().expr(expr));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,21 +21,6 @@ impl Violation for RequestWithNoCertValidation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const REQUESTS_HTTP_VERBS: [&str; 7] = ["get", "options", "head", "post", "put", "patch", "delete"];
|
|
||||||
const HTTPX_METHODS: [&str; 11] = [
|
|
||||||
"get",
|
|
||||||
"options",
|
|
||||||
"head",
|
|
||||||
"post",
|
|
||||||
"put",
|
|
||||||
"patch",
|
|
||||||
"delete",
|
|
||||||
"request",
|
|
||||||
"stream",
|
|
||||||
"Client",
|
|
||||||
"AsyncClient",
|
|
||||||
];
|
|
||||||
|
|
||||||
/// S501
|
/// S501
|
||||||
pub(crate) fn request_with_no_cert_validation(
|
pub(crate) fn request_with_no_cert_validation(
|
||||||
checker: &mut Checker,
|
checker: &mut Checker,
|
||||||
|
|
@ -46,16 +31,13 @@ pub(crate) fn request_with_no_cert_validation(
|
||||||
if let Some(target) = checker
|
if let Some(target) = checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_call_path(func)
|
||||||
.and_then(|call_path| {
|
.and_then(|call_path| match call_path.as_slice() {
|
||||||
if call_path.len() == 2 {
|
["requests", "get" | "options" | "head" | "post" | "put" | "patch" | "delete"] => {
|
||||||
if call_path[0] == "requests" && REQUESTS_HTTP_VERBS.contains(&call_path[1]) {
|
Some("requests")
|
||||||
return Some("requests");
|
|
||||||
}
|
}
|
||||||
if call_path[0] == "httpx" && HTTPX_METHODS.contains(&call_path[1]) {
|
["httpx", "get" | "options" | "head" | "post" | "put" | "patch" | "delete" | "request"
|
||||||
return Some("httpx");
|
| "stream" | "Client" | "AsyncClient"] => Some("httpx"),
|
||||||
}
|
_ => None,
|
||||||
}
|
|
||||||
None
|
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
let call_args = SimpleCallArgs::new(args, keywords);
|
let call_args = SimpleCallArgs::new(args, keywords);
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,28 @@
|
||||||
use rustpython_parser::ast::{self, Constant, Expr, Keyword, Ranged};
|
use rustpython_parser::ast::{Expr, Keyword, Ranged};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::helpers::SimpleCallArgs;
|
use ruff_python_ast::helpers::{is_const_none, SimpleCallArgs};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct RequestWithoutTimeout {
|
pub struct RequestWithoutTimeout {
|
||||||
pub timeout: Option<String>,
|
implicit: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Violation for RequestWithoutTimeout {
|
impl Violation for RequestWithoutTimeout {
|
||||||
#[derive_message_formats]
|
#[derive_message_formats]
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
let RequestWithoutTimeout { timeout } = self;
|
let RequestWithoutTimeout { implicit } = self;
|
||||||
match timeout {
|
if *implicit {
|
||||||
Some(value) => {
|
format!("Probable use of requests call without timeout")
|
||||||
format!("Probable use of requests call with timeout set to `{value}`")
|
} else {
|
||||||
}
|
format!("Probable use of requests call with timeout set to `None`")
|
||||||
None => format!("Probable use of requests call without timeout"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const HTTP_VERBS: [&str; 7] = ["get", "options", "head", "post", "put", "patch", "delete"];
|
|
||||||
|
|
||||||
/// S113
|
/// S113
|
||||||
pub(crate) fn request_without_timeout(
|
pub(crate) fn request_without_timeout(
|
||||||
checker: &mut Checker,
|
checker: &mut Checker,
|
||||||
|
|
@ -37,30 +34,26 @@ pub(crate) fn request_without_timeout(
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(func)
|
.resolve_call_path(func)
|
||||||
.map_or(false, |call_path| {
|
.map_or(false, |call_path| {
|
||||||
HTTP_VERBS
|
matches!(
|
||||||
.iter()
|
call_path.as_slice(),
|
||||||
.any(|func_name| call_path.as_slice() == ["requests", func_name])
|
[
|
||||||
|
"requests",
|
||||||
|
"get" | "options" | "head" | "post" | "put" | "patch" | "delete"
|
||||||
|
]
|
||||||
|
)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
let call_args = SimpleCallArgs::new(args, keywords);
|
let call_args = SimpleCallArgs::new(args, keywords);
|
||||||
if let Some(timeout_arg) = call_args.keyword_argument("timeout") {
|
if let Some(timeout) = call_args.keyword_argument("timeout") {
|
||||||
if let Some(timeout) = match timeout_arg {
|
if is_const_none(timeout) {
|
||||||
Expr::Constant(ast::ExprConstant {
|
|
||||||
value: value @ Constant::None,
|
|
||||||
..
|
|
||||||
}) => Some(checker.generator().constant(value)),
|
|
||||||
_ => None,
|
|
||||||
} {
|
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
RequestWithoutTimeout {
|
RequestWithoutTimeout { implicit: false },
|
||||||
timeout: Some(timeout),
|
timeout.range(),
|
||||||
},
|
|
||||||
timeout_arg.range(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
RequestWithoutTimeout { timeout: None },
|
RequestWithoutTimeout { implicit: true },
|
||||||
func.range(),
|
func.range(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -219,7 +219,7 @@ impl Violation for SuspiciousFTPLibUsage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// S001
|
/// S301, S302, S303, S304, S305, S306, S307, S308, S310, S311, S312, S313, S314, S315, S316, S317, S318, S319, S320, S321, S323
|
||||||
pub(crate) fn suspicious_function_call(checker: &mut Checker, expr: &Expr) {
|
pub(crate) fn suspicious_function_call(checker: &mut Checker, expr: &Expr) {
|
||||||
let Expr::Call(ast::ExprCall { func, .. }) = expr else {
|
let Expr::Call(ast::ExprCall { func, .. }) = expr else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -246,7 +246,7 @@ pub(crate) fn suspicious_function_call(checker: &mut Checker, expr: &Expr) {
|
||||||
// Mktemp
|
// Mktemp
|
||||||
["tempfile", "mktemp"] => Some(SuspiciousMktempUsage.into()),
|
["tempfile", "mktemp"] => Some(SuspiciousMktempUsage.into()),
|
||||||
// Eval
|
// Eval
|
||||||
["eval"] => Some(SuspiciousEvalUsage.into()),
|
["" | "builtins", "eval"] => Some(SuspiciousEvalUsage.into()),
|
||||||
// MarkSafe
|
// MarkSafe
|
||||||
["django", "utils", "safestring", "mark_safe"] => Some(SuspiciousMarkSafeUsage.into()),
|
["django", "utils", "safestring", "mark_safe"] => Some(SuspiciousMarkSafeUsage.into()),
|
||||||
// URLOpen
|
// URLOpen
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ S102.py:3:5: S102 Use of `exec` detected
|
||||||
1 | def fn():
|
1 | def fn():
|
||||||
2 | # Error
|
2 | # Error
|
||||||
3 | exec('x = 2')
|
3 | exec('x = 2')
|
||||||
| ^^^^^^^^^^^^^ S102
|
| ^^^^ S102
|
||||||
4 |
|
4 |
|
||||||
5 | exec('y = 3')
|
5 | exec('y = 3')
|
||||||
|
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ S102.py:5:1: S102 Use of `exec` detected
|
||||||
3 | exec('x = 2')
|
3 | exec('x = 2')
|
||||||
4 |
|
4 |
|
||||||
5 | exec('y = 3')
|
5 | exec('y = 3')
|
||||||
| ^^^^^^^^^^^^^ S102
|
| ^^^^ S102
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff/src/rules/flake8_bandit/mod.rs
|
||||||
|
---
|
||||||
|
S307.py:3:7: S307 Use of possibly insecure function; consider using `ast.literal_eval`
|
||||||
|
|
|
||||||
|
1 | import os
|
||||||
|
2 |
|
||||||
|
3 | print(eval("1+1")) # S307
|
||||||
|
| ^^^^^^^^^^^ S307
|
||||||
|
4 | print(eval("os.getcwd()")) # S307
|
||||||
|
|
|
||||||
|
|
||||||
|
S307.py:4:7: S307 Use of possibly insecure function; consider using `ast.literal_eval`
|
||||||
|
|
|
||||||
|
3 | print(eval("1+1")) # S307
|
||||||
|
4 | print(eval("os.getcwd()")) # S307
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^ S307
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -161,19 +161,19 @@ pub(crate) fn abstract_base_class(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (
|
let (Stmt::FunctionDef(ast::StmtFunctionDef {
|
||||||
Stmt::FunctionDef(ast::StmtFunctionDef {
|
|
||||||
decorator_list,
|
|
||||||
body,
|
|
||||||
name: method_name,
|
|
||||||
..
|
|
||||||
}) | Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
|
||||||
decorator_list,
|
decorator_list,
|
||||||
body,
|
body,
|
||||||
name: method_name,
|
name: method_name,
|
||||||
..
|
..
|
||||||
})
|
})
|
||||||
) = stmt else {
|
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||||
|
decorator_list,
|
||||||
|
body,
|
||||||
|
name: method_name,
|
||||||
|
..
|
||||||
|
})) = stmt
|
||||||
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,20 @@
|
||||||
use rustpython_parser::ast::{self, Expr, Ranged, Stmt, WithItem};
|
use std::fmt;
|
||||||
|
|
||||||
|
use rustpython_parser::ast::{self, Expr, Ranged, WithItem};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub(crate) enum AssertionKind {
|
|
||||||
AssertRaises,
|
|
||||||
PytestRaises,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for `self.assertRaises(Exception)` or `pytest.raises(Exception)`.
|
/// Checks for `assertRaises` and `pytest.raises` context managers that catch
|
||||||
|
/// `Exception` or `BaseException`.
|
||||||
///
|
///
|
||||||
/// ## Why is this bad?
|
/// ## Why is this bad?
|
||||||
/// These forms catch every `Exception`, which can lead to tests passing even
|
/// These forms catch every `Exception`, which can lead to tests passing even
|
||||||
/// if, e.g., the code being tested is never executed due to a typo.
|
/// if, e.g., the code under consideration raises a `SyntaxError` or
|
||||||
|
/// `IndentationError`.
|
||||||
///
|
///
|
||||||
/// Either assert for a more specific exception (builtin or custom), or use
|
/// Either assert for a more specific exception (builtin or custom), or use
|
||||||
/// `assertRaisesRegex` or `pytest.raises(..., match=<REGEX>)` respectively.
|
/// `assertRaisesRegex` or `pytest.raises(..., match=<REGEX>)` respectively.
|
||||||
|
|
@ -32,30 +30,61 @@ pub(crate) enum AssertionKind {
|
||||||
/// ```
|
/// ```
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct AssertRaisesException {
|
pub struct AssertRaisesException {
|
||||||
kind: AssertionKind,
|
assertion: AssertionKind,
|
||||||
|
exception: ExceptionKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Violation for AssertRaisesException {
|
impl Violation for AssertRaisesException {
|
||||||
#[derive_message_formats]
|
#[derive_message_formats]
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
match self.kind {
|
let AssertRaisesException {
|
||||||
AssertionKind::AssertRaises => {
|
assertion,
|
||||||
format!("`assertRaises(Exception)` should be considered evil")
|
exception,
|
||||||
|
} = self;
|
||||||
|
format!("`{assertion}({exception})` should be considered evil")
|
||||||
}
|
}
|
||||||
AssertionKind::PytestRaises => {
|
}
|
||||||
format!("`pytest.raises(Exception)` should be considered evil")
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
enum AssertionKind {
|
||||||
|
AssertRaises,
|
||||||
|
PytestRaises,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for AssertionKind {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
AssertionKind::AssertRaises => fmt.write_str("assertRaises"),
|
||||||
|
AssertionKind::PytestRaises => fmt.write_str("pytest.raises"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
enum ExceptionKind {
|
||||||
|
BaseException,
|
||||||
|
Exception,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ExceptionKind {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
ExceptionKind::BaseException => fmt.write_str("BaseException"),
|
||||||
|
ExceptionKind::Exception => fmt.write_str("Exception"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// B017
|
/// B017
|
||||||
pub(crate) fn assert_raises_exception(checker: &mut Checker, stmt: &Stmt, items: &[WithItem]) {
|
pub(crate) fn assert_raises_exception(checker: &mut Checker, items: &[WithItem]) {
|
||||||
let Some(item) = items.first() else {
|
for item in items {
|
||||||
return;
|
let Expr::Call(ast::ExprCall {
|
||||||
};
|
func,
|
||||||
let item_context = &item.context_expr;
|
args,
|
||||||
let Expr::Call(ast::ExprCall { func, args, keywords, range: _ }) = &item_context else {
|
keywords,
|
||||||
|
range: _,
|
||||||
|
}) = &item.context_expr
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if args.len() != 1 {
|
if args.len() != 1 {
|
||||||
|
|
@ -65,18 +94,19 @@ pub(crate) fn assert_raises_exception(checker: &mut Checker, stmt: &Stmt, items:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !checker
|
let Some(exception) = checker
|
||||||
.semantic()
|
.semantic()
|
||||||
.resolve_call_path(args.first().unwrap())
|
.resolve_call_path(args.first().unwrap())
|
||||||
.map_or(false, |call_path| {
|
.and_then(|call_path| match call_path.as_slice() {
|
||||||
matches!(call_path.as_slice(), ["", "Exception"])
|
["", "Exception"] => Some(ExceptionKind::Exception),
|
||||||
|
["", "BaseException"] => Some(ExceptionKind::BaseException),
|
||||||
|
_ => None,
|
||||||
})
|
})
|
||||||
{
|
else {
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
|
|
||||||
let kind = {
|
let assertion = if matches!(func.as_ref(), Expr::Attribute(ast::ExprAttribute { attr, .. }) if attr == "assertRaises")
|
||||||
if matches!(func.as_ref(), Expr::Attribute(ast::ExprAttribute { attr, .. }) if attr == "assertRaises")
|
|
||||||
{
|
{
|
||||||
AssertionKind::AssertRaises
|
AssertionKind::AssertRaises
|
||||||
} else if checker
|
} else if checker
|
||||||
|
|
@ -92,11 +122,14 @@ pub(crate) fn assert_raises_exception(checker: &mut Checker, stmt: &Stmt, items:
|
||||||
AssertionKind::PytestRaises
|
AssertionKind::PytestRaises
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
AssertRaisesException { kind },
|
AssertRaisesException {
|
||||||
stmt.range(),
|
assertion,
|
||||||
|
exception,
|
||||||
|
},
|
||||||
|
item.range(),
|
||||||
));
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ pub(crate) fn assignment_to_os_environ(checker: &mut Checker, targets: &[Expr])
|
||||||
if attr != "environ" {
|
if attr != "environ" {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let Expr::Name(ast::ExprName { id, .. } )= value.as_ref() else {
|
let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if id != "os" {
|
if id != "os" {
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,11 @@ pub(crate) fn duplicate_exceptions(checker: &mut Checker, handlers: &[ExceptHand
|
||||||
let mut seen: FxHashSet<CallPath> = FxHashSet::default();
|
let mut seen: FxHashSet<CallPath> = FxHashSet::default();
|
||||||
let mut duplicates: FxHashMap<CallPath, Vec<&Expr>> = FxHashMap::default();
|
let mut duplicates: FxHashMap<CallPath, Vec<&Expr>> = FxHashMap::default();
|
||||||
for handler in handlers {
|
for handler in handlers {
|
||||||
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { type_: Some(type_), .. }) = handler else {
|
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
|
||||||
|
type_: Some(type_),
|
||||||
|
..
|
||||||
|
}) = handler
|
||||||
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
match type_.as_ref() {
|
match type_.as_ref() {
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ impl Violation for ExceptWithNonExceptionClasses {
|
||||||
/// This should leave any unstarred iterables alone (subsequently raising a
|
/// This should leave any unstarred iterables alone (subsequently raising a
|
||||||
/// warning for B029).
|
/// warning for B029).
|
||||||
fn flatten_starred_iterables(expr: &Expr) -> Vec<&Expr> {
|
fn flatten_starred_iterables(expr: &Expr) -> Vec<&Expr> {
|
||||||
let Expr::Tuple(ast::ExprTuple { elts, .. } )= expr else {
|
let Expr::Tuple(ast::ExprTuple { elts, .. }) = expr else {
|
||||||
return vec![expr];
|
return vec![expr];
|
||||||
};
|
};
|
||||||
let mut flattened_exprs: Vec<&Expr> = Vec::with_capacity(elts.len());
|
let mut flattened_exprs: Vec<&Expr> = Vec::with_capacity(elts.len());
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
use rustc_hash::FxHashSet;
|
|
||||||
use rustpython_parser::ast::{self, Comprehension, Expr, ExprContext, Ranged, Stmt};
|
use rustpython_parser::ast::{self, Comprehension, Expr, ExprContext, Ranged, Stmt};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::helpers::collect_arg_names;
|
use ruff_python_ast::helpers::includes_arg_name;
|
||||||
use ruff_python_ast::types::Node;
|
use ruff_python_ast::types::Node;
|
||||||
use ruff_python_ast::visitor;
|
use ruff_python_ast::visitor;
|
||||||
use ruff_python_ast::visitor::Visitor;
|
use ruff_python_ast::visitor::Visitor;
|
||||||
|
|
@ -58,19 +57,17 @@ impl Violation for FunctionUsesLoopVariable {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct LoadedNamesVisitor<'a> {
|
struct LoadedNamesVisitor<'a> {
|
||||||
// Tuple of: name, defining expression, and defining range.
|
loaded: Vec<&'a ast::ExprName>,
|
||||||
loaded: Vec<(&'a str, &'a Expr)>,
|
stored: Vec<&'a ast::ExprName>,
|
||||||
// Tuple of: name, defining expression, and defining range.
|
|
||||||
stored: Vec<(&'a str, &'a Expr)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Visitor` to collect all used identifiers in a statement.
|
/// `Visitor` to collect all used identifiers in a statement.
|
||||||
impl<'a> Visitor<'a> for LoadedNamesVisitor<'a> {
|
impl<'a> Visitor<'a> for LoadedNamesVisitor<'a> {
|
||||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Name(ast::ExprName { id, ctx, range: _ }) => match ctx {
|
Expr::Name(name) => match &name.ctx {
|
||||||
ExprContext::Load => self.loaded.push((id, expr)),
|
ExprContext::Load => self.loaded.push(name),
|
||||||
ExprContext::Store => self.stored.push((id, expr)),
|
ExprContext::Store => self.stored.push(name),
|
||||||
ExprContext::Del => {}
|
ExprContext::Del => {}
|
||||||
},
|
},
|
||||||
_ => visitor::walk_expr(self, expr),
|
_ => visitor::walk_expr(self, expr),
|
||||||
|
|
@ -80,7 +77,7 @@ impl<'a> Visitor<'a> for LoadedNamesVisitor<'a> {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct SuspiciousVariablesVisitor<'a> {
|
struct SuspiciousVariablesVisitor<'a> {
|
||||||
names: Vec<(&'a str, &'a Expr)>,
|
names: Vec<&'a ast::ExprName>,
|
||||||
safe_functions: Vec<&'a Expr>,
|
safe_functions: Vec<&'a Expr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,17 +92,20 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||||
let mut visitor = LoadedNamesVisitor::default();
|
let mut visitor = LoadedNamesVisitor::default();
|
||||||
visitor.visit_body(body);
|
visitor.visit_body(body);
|
||||||
|
|
||||||
// Collect all argument names.
|
|
||||||
let mut arg_names = collect_arg_names(args);
|
|
||||||
arg_names.extend(visitor.stored.iter().map(|(id, ..)| id));
|
|
||||||
|
|
||||||
// Treat any non-arguments as "suspicious".
|
// Treat any non-arguments as "suspicious".
|
||||||
self.names.extend(
|
self.names
|
||||||
visitor
|
.extend(visitor.loaded.into_iter().filter(|loaded| {
|
||||||
.loaded
|
if visitor.stored.iter().any(|stored| stored.id == loaded.id) {
|
||||||
.into_iter()
|
return false;
|
||||||
.filter(|(id, ..)| !arg_names.contains(id)),
|
}
|
||||||
);
|
|
||||||
|
if includes_arg_name(&loaded.id, args) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Stmt::Return(ast::StmtReturn {
|
Stmt::Return(ast::StmtReturn {
|
||||||
|
|
@ -132,10 +132,9 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||||
}) => {
|
}) => {
|
||||||
match func.as_ref() {
|
match func.as_ref() {
|
||||||
Expr::Name(ast::ExprName { id, .. }) => {
|
Expr::Name(ast::ExprName { id, .. }) => {
|
||||||
let id = id.as_str();
|
if matches!(id.as_str(), "filter" | "reduce" | "map") {
|
||||||
if id == "filter" || id == "reduce" || id == "map" {
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
if matches!(arg, Expr::Lambda(_)) {
|
if arg.is_lambda_expr() {
|
||||||
self.safe_functions.push(arg);
|
self.safe_functions.push(arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -159,7 +158,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||||
|
|
||||||
for keyword in keywords {
|
for keyword in keywords {
|
||||||
if keyword.arg.as_ref().map_or(false, |arg| arg == "key")
|
if keyword.arg.as_ref().map_or(false, |arg| arg == "key")
|
||||||
&& matches!(keyword.value, Expr::Lambda(_))
|
&& keyword.value.is_lambda_expr()
|
||||||
{
|
{
|
||||||
self.safe_functions.push(&keyword.value);
|
self.safe_functions.push(&keyword.value);
|
||||||
}
|
}
|
||||||
|
|
@ -175,17 +174,19 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||||
let mut visitor = LoadedNamesVisitor::default();
|
let mut visitor = LoadedNamesVisitor::default();
|
||||||
visitor.visit_expr(body);
|
visitor.visit_expr(body);
|
||||||
|
|
||||||
// Collect all argument names.
|
|
||||||
let mut arg_names = collect_arg_names(args);
|
|
||||||
arg_names.extend(visitor.stored.iter().map(|(id, ..)| id));
|
|
||||||
|
|
||||||
// Treat any non-arguments as "suspicious".
|
// Treat any non-arguments as "suspicious".
|
||||||
self.names.extend(
|
self.names
|
||||||
visitor
|
.extend(visitor.loaded.into_iter().filter(|loaded| {
|
||||||
.loaded
|
if visitor.stored.iter().any(|stored| stored.id == loaded.id) {
|
||||||
.iter()
|
return false;
|
||||||
.filter(|(id, ..)| !arg_names.contains(id)),
|
}
|
||||||
);
|
|
||||||
|
if includes_arg_name(&loaded.id, args) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -198,7 +199,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct NamesFromAssignmentsVisitor<'a> {
|
struct NamesFromAssignmentsVisitor<'a> {
|
||||||
names: FxHashSet<&'a str>,
|
names: Vec<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Visitor` to collect all names used in an assignment expression.
|
/// `Visitor` to collect all names used in an assignment expression.
|
||||||
|
|
@ -206,7 +207,7 @@ impl<'a> Visitor<'a> for NamesFromAssignmentsVisitor<'a> {
|
||||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Name(ast::ExprName { id, .. }) => {
|
Expr::Name(ast::ExprName { id, .. }) => {
|
||||||
self.names.insert(id.as_str());
|
self.names.push(id.as_str());
|
||||||
}
|
}
|
||||||
Expr::Starred(ast::ExprStarred { value, .. }) => {
|
Expr::Starred(ast::ExprStarred { value, .. }) => {
|
||||||
self.visit_expr(value);
|
self.visit_expr(value);
|
||||||
|
|
@ -223,7 +224,7 @@ impl<'a> Visitor<'a> for NamesFromAssignmentsVisitor<'a> {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct AssignedNamesVisitor<'a> {
|
struct AssignedNamesVisitor<'a> {
|
||||||
names: FxHashSet<&'a str>,
|
names: Vec<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Visitor` to collect all used identifiers in a statement.
|
/// `Visitor` to collect all used identifiers in a statement.
|
||||||
|
|
@ -257,7 +258,7 @@ impl<'a> Visitor<'a> for AssignedNamesVisitor<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||||
if matches!(expr, Expr::Lambda(_)) {
|
if expr.is_lambda_expr() {
|
||||||
// Don't recurse.
|
// Don't recurse.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -300,15 +301,15 @@ pub(crate) fn function_uses_loop_variable<'a>(checker: &mut Checker<'a>, node: &
|
||||||
|
|
||||||
// If a variable was used in a function or lambda body, and assigned in the
|
// If a variable was used in a function or lambda body, and assigned in the
|
||||||
// loop, flag it.
|
// loop, flag it.
|
||||||
for (name, expr) in suspicious_variables {
|
for name in suspicious_variables {
|
||||||
if reassigned_in_loop.contains(name) {
|
if reassigned_in_loop.contains(&name.id.as_str()) {
|
||||||
if !checker.flake8_bugbear_seen.contains(&expr) {
|
if !checker.flake8_bugbear_seen.contains(&name) {
|
||||||
checker.flake8_bugbear_seen.push(expr);
|
checker.flake8_bugbear_seen.push(name);
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
FunctionUsesLoopVariable {
|
FunctionUsesLoopVariable {
|
||||||
name: name.to_string(),
|
name: name.id.to_string(),
|
||||||
},
|
},
|
||||||
expr.range(),
|
name.range(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ pub(crate) fn getattr_with_constant(
|
||||||
func: &Expr,
|
func: &Expr,
|
||||||
args: &[Expr],
|
args: &[Expr],
|
||||||
) {
|
) {
|
||||||
let Expr::Name(ast::ExprName { id, .. } )= func else {
|
let Expr::Name(ast::ExprName { id, .. }) = func else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if id != "getattr" {
|
if id != "getattr" {
|
||||||
|
|
@ -76,7 +76,8 @@ pub(crate) fn getattr_with_constant(
|
||||||
let Expr::Constant(ast::ExprConstant {
|
let Expr::Constant(ast::ExprConstant {
|
||||||
value: Constant::Str(value),
|
value: Constant::Str(value),
|
||||||
..
|
..
|
||||||
} )= arg else {
|
}) = arg
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !is_identifier(value) {
|
if !is_identifier(value) {
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ pub(crate) fn mutable_argument_default(checker: &mut Checker, arguments: &Argume
|
||||||
.chain(&arguments.args)
|
.chain(&arguments.args)
|
||||||
.chain(&arguments.kwonlyargs)
|
.chain(&arguments.kwonlyargs)
|
||||||
{
|
{
|
||||||
let Some(default)= default else {
|
let Some(default) = default else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,11 @@ pub(crate) fn redundant_tuple_in_exception_handler(
|
||||||
handlers: &[ExceptHandler],
|
handlers: &[ExceptHandler],
|
||||||
) {
|
) {
|
||||||
for handler in handlers {
|
for handler in handlers {
|
||||||
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { type_: Some(type_), .. }) = handler else {
|
let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
|
||||||
|
type_: Some(type_),
|
||||||
|
..
|
||||||
|
}) = handler
|
||||||
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Expr::Tuple(ast::ExprTuple { elts, .. }) = type_.as_ref() else {
|
let Expr::Tuple(ast::ExprTuple { elts, .. }) = type_.as_ref() else {
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,8 @@ pub(crate) fn setattr_with_constant(
|
||||||
let Expr::Constant(ast::ExprConstant {
|
let Expr::Constant(ast::ExprConstant {
|
||||||
value: Constant::Str(name),
|
value: Constant::Str(name),
|
||||||
..
|
..
|
||||||
} )= name else {
|
}) = name
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !is_identifier(name) {
|
if !is_identifier(name) {
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ pub(crate) fn star_arg_unpacking_after_keyword_arg(
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
for arg in args {
|
for arg in args {
|
||||||
let Expr::Starred (_) = arg else {
|
let Expr::Starred(_) = arg else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if arg.start() <= keyword.start() {
|
if arg.start() <= keyword.start() {
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,8 @@ pub(crate) fn strip_with_multi_characters(
|
||||||
let Expr::Constant(ast::ExprConstant {
|
let Expr::Constant(ast::ExprConstant {
|
||||||
value: Constant::Str(value),
|
value: Constant::Str(value),
|
||||||
..
|
..
|
||||||
} )= &args[0] else {
|
}) = &args[0]
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ pub(crate) fn unary_prefix_increment(
|
||||||
if !matches!(op, UnaryOp::UAdd) {
|
if !matches!(op, UnaryOp::UAdd) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let Expr::UnaryOp(ast::ExprUnaryOp { op, .. })= operand else {
|
let Expr::UnaryOp(ast::ExprUnaryOp { op, .. }) = operand else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !matches!(op, UnaryOp::UAdd) {
|
if !matches!(op, UnaryOp::UAdd) {
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,8 @@ pub(crate) fn unreliable_callable_check(
|
||||||
let Expr::Constant(ast::ExprConstant {
|
let Expr::Constant(ast::ExprConstant {
|
||||||
value: Constant::Str(s),
|
value: Constant::Str(s),
|
||||||
..
|
..
|
||||||
}) = &args[1] else
|
}) = &args[1]
|
||||||
{
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if s != "__call__" {
|
if s != "__call__" {
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,13 @@ pub(crate) fn zip_without_explicit_strict(
|
||||||
/// Return `true` if the [`Expr`] appears to be an infinite iterator (e.g., a call to
|
/// Return `true` if the [`Expr`] appears to be an infinite iterator (e.g., a call to
|
||||||
/// `itertools.cycle` or similar).
|
/// `itertools.cycle` or similar).
|
||||||
fn is_infinite_iterator(arg: &Expr, semantic: &SemanticModel) -> bool {
|
fn is_infinite_iterator(arg: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
let Expr::Call(ast::ExprCall { func, args, keywords, .. }) = &arg else {
|
let Expr::Call(ast::ExprCall {
|
||||||
|
func,
|
||||||
|
args,
|
||||||
|
keywords,
|
||||||
|
..
|
||||||
|
}) = &arg
|
||||||
|
else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,38 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
|
source: crates/ruff/src/rules/flake8_bugbear/mod.rs
|
||||||
---
|
---
|
||||||
B017.py:23:9: B017 `assertRaises(Exception)` should be considered evil
|
B017.py:23:14: B017 `assertRaises(Exception)` should be considered evil
|
||||||
|
|
|
|
||||||
21 | class Foobar(unittest.TestCase):
|
21 | class Foobar(unittest.TestCase):
|
||||||
22 | def evil_raises(self) -> None:
|
22 | def evil_raises(self) -> None:
|
||||||
23 | with self.assertRaises(Exception):
|
23 | with self.assertRaises(Exception):
|
||||||
| _________^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B017
|
||||||
24 | | raise Exception("Evil I say!")
|
24 | raise Exception("Evil I say!")
|
||||||
| |__________________________________________^ B017
|
|
||||||
25 |
|
|
||||||
26 | def context_manager_raises(self) -> None:
|
|
||||||
|
|
|
|
||||||
|
|
||||||
B017.py:41:5: B017 `pytest.raises(Exception)` should be considered evil
|
B017.py:27:14: B017 `assertRaises(BaseException)` should be considered evil
|
||||||
|
|
|
|
||||||
40 | def test_pytest_raises():
|
26 | def also_evil_raises(self) -> None:
|
||||||
41 | with pytest.raises(Exception):
|
27 | with self.assertRaises(BaseException):
|
||||||
| _____^
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B017
|
||||||
42 | | raise ValueError("Hello")
|
28 | raise Exception("Evil I say!")
|
||||||
| |_________________________________^ B017
|
|
|
||||||
43 |
|
|
||||||
44 | with pytest.raises(Exception, "hello"):
|
B017.py:45:10: B017 `pytest.raises(Exception)` should be considered evil
|
||||||
|
|
|
||||||
|
44 | def test_pytest_raises():
|
||||||
|
45 | with pytest.raises(Exception):
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ B017
|
||||||
|
46 | raise ValueError("Hello")
|
||||||
|
|
|
||||||
|
|
||||||
|
B017.py:48:10: B017 `pytest.raises(Exception)` should be considered evil
|
||||||
|
|
|
||||||
|
46 | raise ValueError("Hello")
|
||||||
|
47 |
|
||||||
|
48 | with pytest.raises(Exception), pytest.raises(ValueError):
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ B017
|
||||||
|
49 | raise ValueError("Hello")
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,8 @@ pub(crate) fn fix_unnecessary_generator_dict(
|
||||||
// Extract the (k, v) from `(k, v) for ...`.
|
// Extract the (k, v) from `(k, v) for ...`.
|
||||||
let generator_exp = match_generator_exp(&arg.value)?;
|
let generator_exp = match_generator_exp(&arg.value)?;
|
||||||
let tuple = match_tuple(&generator_exp.elt)?;
|
let tuple = match_tuple(&generator_exp.elt)?;
|
||||||
let [Element::Simple { value: key, .. }, Element::Simple { value, .. }] = &tuple.elements[..] else {
|
let [Element::Simple { value: key, .. }, Element::Simple { value, .. }] = &tuple.elements[..]
|
||||||
|
else {
|
||||||
bail!("Expected tuple to contain two elements");
|
bail!("Expected tuple to contain two elements");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -188,9 +189,10 @@ pub(crate) fn fix_unnecessary_list_comprehension_dict(
|
||||||
|
|
||||||
let tuple = match_tuple(&list_comp.elt)?;
|
let tuple = match_tuple(&list_comp.elt)?;
|
||||||
|
|
||||||
let [Element::Simple {
|
let [Element::Simple { value: key, .. }, Element::Simple { value, .. }] = &tuple.elements[..]
|
||||||
value: key, ..
|
else {
|
||||||
}, Element::Simple { value, .. }] = &tuple.elements[..] else { bail!("Expected tuple with two elements"); };
|
bail!("Expected tuple with two elements");
|
||||||
|
};
|
||||||
|
|
||||||
tree = Expression::DictComp(Box::new(DictComp {
|
tree = Expression::DictComp(Box::new(DictComp {
|
||||||
key: Box::new(key.clone()),
|
key: Box::new(key.clone()),
|
||||||
|
|
@ -982,14 +984,10 @@ pub(crate) fn fix_unnecessary_map(
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(Element::Simple { value: key, .. }) = &tuple.elements.get(0) else {
|
let Some(Element::Simple { value: key, .. }) = &tuple.elements.get(0) else {
|
||||||
bail!(
|
bail!("Expected tuple to contain a key as the first element");
|
||||||
"Expected tuple to contain a key as the first element"
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
let Some(Element::Simple { value, .. }) = &tuple.elements.get(1) else {
|
let Some(Element::Simple { value, .. }) = &tuple.elements.get(1) else {
|
||||||
bail!(
|
bail!("Expected tuple to contain a key as the second element");
|
||||||
"Expected tuple to contain a key as the second element"
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
(key, value)
|
(key, value)
|
||||||
|
|
@ -1063,9 +1061,7 @@ pub(crate) fn fix_unnecessary_comprehension_any_all(
|
||||||
let call = match_call_mut(&mut tree)?;
|
let call = match_call_mut(&mut tree)?;
|
||||||
|
|
||||||
let Expression::ListComp(list_comp) = &call.args[0].value else {
|
let Expression::ListComp(list_comp) = &call.args[0].value else {
|
||||||
bail!(
|
bail!("Expected Expression::ListComp");
|
||||||
"Expected Expression::ListComp"
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut new_empty_lines = vec![];
|
let mut new_empty_lines = vec![];
|
||||||
|
|
|
||||||
|
|
@ -66,11 +66,13 @@ pub(crate) fn unnecessary_comprehension_any_all(
|
||||||
if !keywords.is_empty() {
|
if !keywords.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let Expr::Name(ast::ExprName { id, .. } )= func else {
|
let Expr::Name(ast::ExprName { id, .. }) = func else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if (matches!(id.as_str(), "all" | "any")) && args.len() == 1 {
|
if (matches!(id.as_str(), "all" | "any")) && args.len() == 1 {
|
||||||
let (Expr::ListComp(ast::ExprListComp { elt, .. } )| Expr::SetComp(ast::ExprSetComp { elt, .. })) = &args[0] else {
|
let (Expr::ListComp(ast::ExprListComp { elt, .. })
|
||||||
|
| Expr::SetComp(ast::ExprSetComp { elt, .. })) = &args[0]
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if contains_await(elt) {
|
if contains_await(elt) {
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ pub(crate) fn unnecessary_double_cast_or_process(
|
||||||
let Some(arg) = args.first() else {
|
let Some(arg) = args.first() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Expr::Call(ast::ExprCall { func, ..} )= arg else {
|
let Expr::Call(ast::ExprCall { func, .. }) = arg else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(inner) = helpers::expr_name(func) else {
|
let Some(inner) = helpers::expr_name(func) else {
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,9 @@ pub(crate) fn unnecessary_generator_dict(
|
||||||
args: &[Expr],
|
args: &[Expr],
|
||||||
keywords: &[Keyword],
|
keywords: &[Keyword],
|
||||||
) {
|
) {
|
||||||
let Some(argument) = helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords) else {
|
let Some(argument) =
|
||||||
|
helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords)
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let Expr::GeneratorExp(ast::ExprGeneratorExp { elt, .. }) = argument {
|
if let Expr::GeneratorExp(ast::ExprGeneratorExp { elt, .. }) = argument {
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,9 @@ pub(crate) fn unnecessary_generator_list(
|
||||||
args: &[Expr],
|
args: &[Expr],
|
||||||
keywords: &[Keyword],
|
keywords: &[Keyword],
|
||||||
) {
|
) {
|
||||||
let Some(argument) = helpers::exactly_one_argument_with_matching_function("list", func, args, keywords) else {
|
let Some(argument) =
|
||||||
|
helpers::exactly_one_argument_with_matching_function("list", func, args, keywords)
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !checker.semantic().is_builtin("list") {
|
if !checker.semantic().is_builtin("list") {
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,9 @@ pub(crate) fn unnecessary_generator_set(
|
||||||
args: &[Expr],
|
args: &[Expr],
|
||||||
keywords: &[Keyword],
|
keywords: &[Keyword],
|
||||||
) {
|
) {
|
||||||
let Some(argument) = helpers::exactly_one_argument_with_matching_function("set", func, args, keywords) else {
|
let Some(argument) =
|
||||||
|
helpers::exactly_one_argument_with_matching_function("set", func, args, keywords)
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !checker.semantic().is_builtin("set") {
|
if !checker.semantic().is_builtin("set") {
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,9 @@ pub(crate) fn unnecessary_list_comprehension_dict(
|
||||||
args: &[Expr],
|
args: &[Expr],
|
||||||
keywords: &[Keyword],
|
keywords: &[Keyword],
|
||||||
) {
|
) {
|
||||||
let Some(argument) = helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords) else {
|
let Some(argument) =
|
||||||
|
helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords)
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !checker.semantic().is_builtin("dict") {
|
if !checker.semantic().is_builtin("dict") {
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,9 @@ pub(crate) fn unnecessary_list_comprehension_set(
|
||||||
args: &[Expr],
|
args: &[Expr],
|
||||||
keywords: &[Keyword],
|
keywords: &[Keyword],
|
||||||
) {
|
) {
|
||||||
let Some(argument) = helpers::exactly_one_argument_with_matching_function("set", func, args, keywords) else {
|
let Some(argument) =
|
||||||
|
helpers::exactly_one_argument_with_matching_function("set", func, args, keywords)
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !checker.semantic().is_builtin("set") {
|
if !checker.semantic().is_builtin("set") {
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,9 @@ pub(crate) fn unnecessary_literal_dict(
|
||||||
args: &[Expr],
|
args: &[Expr],
|
||||||
keywords: &[Keyword],
|
keywords: &[Keyword],
|
||||||
) {
|
) {
|
||||||
let Some(argument) = helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords) else {
|
let Some(argument) =
|
||||||
|
helpers::exactly_one_argument_with_matching_function("dict", func, args, keywords)
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !checker.semantic().is_builtin("dict") {
|
if !checker.semantic().is_builtin("dict") {
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,9 @@ pub(crate) fn unnecessary_literal_set(
|
||||||
args: &[Expr],
|
args: &[Expr],
|
||||||
keywords: &[Keyword],
|
keywords: &[Keyword],
|
||||||
) {
|
) {
|
||||||
let Some(argument) = helpers::exactly_one_argument_with_matching_function("set", func, args, keywords) else {
|
let Some(argument) =
|
||||||
|
helpers::exactly_one_argument_with_matching_function("set", func, args, keywords)
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !checker.semantic().is_builtin("set") {
|
if !checker.semantic().is_builtin("set") {
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,9 @@ pub(crate) fn unnecessary_map(
|
||||||
if args.len() != 2 {
|
if args.len() != 2 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let Some(argument) = helpers::first_argument_with_matching_function("map", func, args) else {
|
let Some(argument) =
|
||||||
|
helpers::first_argument_with_matching_function("map", func, args)
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let Expr::Lambda(_) = argument {
|
if let Expr::Lambda(_) = argument {
|
||||||
|
|
@ -155,7 +157,9 @@ pub(crate) fn unnecessary_map(
|
||||||
|
|
||||||
if args.len() == 1 {
|
if args.len() == 1 {
|
||||||
if let Expr::Call(ast::ExprCall { func, args, .. }) = &args[0] {
|
if let Expr::Call(ast::ExprCall { func, args, .. }) = &args[0] {
|
||||||
let Some(argument) = helpers::first_argument_with_matching_function("map", func, args) else {
|
let Some(argument) =
|
||||||
|
helpers::first_argument_with_matching_function("map", func, args)
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let Expr::Lambda(ast::ExprLambda { body, .. }) = argument {
|
if let Expr::Lambda(ast::ExprLambda { body, .. }) = argument {
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,13 @@ pub(crate) fn unnecessary_subscript_reversal(
|
||||||
let Expr::Subscript(ast::ExprSubscript { slice, .. }) = first_arg else {
|
let Expr::Subscript(ast::ExprSubscript { slice, .. }) = first_arg else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Expr::Slice(ast::ExprSlice { lower, upper, step, range: _ }) = slice.as_ref() else {
|
let Expr::Slice(ast::ExprSlice {
|
||||||
|
lower,
|
||||||
|
upper,
|
||||||
|
step,
|
||||||
|
range: _,
|
||||||
|
}) = slice.as_ref()
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if lower.is_some() || upper.is_some() {
|
if lower.is_some() || upper.is_some() {
|
||||||
|
|
@ -77,13 +83,15 @@ pub(crate) fn unnecessary_subscript_reversal(
|
||||||
op: UnaryOp::USub,
|
op: UnaryOp::USub,
|
||||||
operand,
|
operand,
|
||||||
range: _,
|
range: _,
|
||||||
}) = step.as_ref() else {
|
}) = step.as_ref()
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Expr::Constant(ast::ExprConstant {
|
let Expr::Constant(ast::ExprConstant {
|
||||||
value: Constant::Int(val),
|
value: Constant::Int(val),
|
||||||
..
|
..
|
||||||
}) = operand.as_ref() else {
|
}) = operand.as_ref()
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if *val != BigInt::from(1) {
|
if *val != BigInt::from(1) {
|
||||||
|
|
|
||||||
|
|
@ -49,11 +49,13 @@ pub(crate) fn call_datetime_strptime_without_zone(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (Some(grandparent), Some(parent)) = (checker.semantic().expr_grandparent(), checker.semantic().expr_parent()) else {
|
let (Some(grandparent), Some(parent)) = (
|
||||||
checker.diagnostics.push(Diagnostic::new(
|
checker.semantic().expr_grandparent(),
|
||||||
CallDatetimeStrptimeWithoutZone,
|
checker.semantic().expr_parent(),
|
||||||
location,
|
) else {
|
||||||
));
|
checker
|
||||||
|
.diagnostics
|
||||||
|
.push(Diagnostic::new(CallDatetimeStrptimeWithoutZone, location));
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ pub(crate) fn locals_in_render_function(
|
||||||
|
|
||||||
fn is_locals_call(expr: &Expr, semantic: &SemanticModel) -> bool {
|
fn is_locals_call(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||||
let Expr::Call(ast::ExprCall { func, .. }) = expr else {
|
let Expr::Call(ast::ExprCall { func, .. }) = expr else {
|
||||||
return false
|
return false;
|
||||||
};
|
};
|
||||||
semantic.resolve_call_path(func).map_or(false, |call_path| {
|
semantic.resolve_call_path(func).map_or(false, |call_path| {
|
||||||
matches!(call_path.as_slice(), ["", "locals"])
|
matches!(call_path.as_slice(), ["", "locals"])
|
||||||
|
|
|
||||||
|
|
@ -52,21 +52,20 @@ impl Violation for DjangoModelWithoutDunderStr {
|
||||||
|
|
||||||
/// DJ008
|
/// DJ008
|
||||||
pub(crate) fn model_without_dunder_str(
|
pub(crate) fn model_without_dunder_str(
|
||||||
checker: &Checker,
|
checker: &mut Checker,
|
||||||
bases: &[Expr],
|
ast::StmtClassDef {
|
||||||
body: &[Stmt],
|
name, bases, body, ..
|
||||||
class_location: &Stmt,
|
}: &ast::StmtClassDef,
|
||||||
) -> Option<Diagnostic> {
|
) {
|
||||||
if !is_non_abstract_model(bases, body, checker.semantic()) {
|
if !is_non_abstract_model(bases, body, checker.semantic()) {
|
||||||
return None;
|
return;
|
||||||
}
|
}
|
||||||
if !has_dunder_method(body) {
|
if has_dunder_method(body) {
|
||||||
return Some(Diagnostic::new(
|
return;
|
||||||
DjangoModelWithoutDunderStr,
|
|
||||||
class_location.range(),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
None
|
checker
|
||||||
|
.diagnostics
|
||||||
|
.push(Diagnostic::new(DjangoModelWithoutDunderStr, name.range()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_dunder_method(body: &[Stmt]) -> bool {
|
fn has_dunder_method(body: &[Stmt]) -> bool {
|
||||||
|
|
@ -96,18 +95,18 @@ fn is_non_abstract_model(bases: &[Expr], body: &[Stmt], semantic: &SemanticModel
|
||||||
/// Check if class is abstract, in terms of Django model inheritance.
|
/// Check if class is abstract, in terms of Django model inheritance.
|
||||||
fn is_model_abstract(body: &[Stmt]) -> bool {
|
fn is_model_abstract(body: &[Stmt]) -> bool {
|
||||||
for element in body.iter() {
|
for element in body.iter() {
|
||||||
let Stmt::ClassDef(ast::StmtClassDef {name, body, ..}) = element else {
|
let Stmt::ClassDef(ast::StmtClassDef { name, body, .. }) = element else {
|
||||||
continue
|
continue;
|
||||||
};
|
};
|
||||||
if name != "Meta" {
|
if name != "Meta" {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for element in body.iter() {
|
for element in body.iter() {
|
||||||
let Stmt::Assign(ast::StmtAssign {targets, value, ..}) = element else {
|
let Stmt::Assign(ast::StmtAssign { targets, value, .. }) = element else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
for target in targets.iter() {
|
for target in targets.iter() {
|
||||||
let Expr::Name(ast::ExprName {id , ..}) = target else {
|
let Expr::Name(ast::ExprName { id, .. }) = target else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if id != "abstract" {
|
if id != "abstract" {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use rustpython_parser::ast::{self, Decorator, Expr, Ranged};
|
use rustpython_parser::ast::{Decorator, Ranged};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::call_path::CallPath;
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks that Django's `@receiver` decorator is listed first, prior to
|
/// Checks that Django's `@receiver` decorator is listed first, prior to
|
||||||
|
|
@ -48,25 +49,19 @@ impl Violation for DjangoNonLeadingReceiverDecorator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// DJ013
|
/// DJ013
|
||||||
pub(crate) fn non_leading_receiver_decorator<'a, F>(
|
pub(crate) fn non_leading_receiver_decorator(checker: &mut Checker, decorator_list: &[Decorator]) {
|
||||||
decorator_list: &'a [Decorator],
|
|
||||||
resolve_call_path: F,
|
|
||||||
) -> Vec<Diagnostic>
|
|
||||||
where
|
|
||||||
F: Fn(&'a Expr) -> Option<CallPath<'a>>,
|
|
||||||
{
|
|
||||||
let mut diagnostics = vec![];
|
|
||||||
let mut seen_receiver = false;
|
let mut seen_receiver = false;
|
||||||
for (i, decorator) in decorator_list.iter().enumerate() {
|
for (i, decorator) in decorator_list.iter().enumerate() {
|
||||||
let is_receiver = match &decorator.expression {
|
let is_receiver = decorator.expression.as_call_expr().map_or(false, |call| {
|
||||||
Expr::Call(ast::ExprCall { func, .. }) => resolve_call_path(func)
|
checker
|
||||||
|
.semantic()
|
||||||
|
.resolve_call_path(&call.func)
|
||||||
.map_or(false, |call_path| {
|
.map_or(false, |call_path| {
|
||||||
matches!(call_path.as_slice(), ["django", "dispatch", "receiver"])
|
matches!(call_path.as_slice(), ["django", "dispatch", "receiver"])
|
||||||
}),
|
})
|
||||||
_ => false,
|
});
|
||||||
};
|
|
||||||
if i > 0 && is_receiver && !seen_receiver {
|
if i > 0 && is_receiver && !seen_receiver {
|
||||||
diagnostics.push(Diagnostic::new(
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
DjangoNonLeadingReceiverDecorator,
|
DjangoNonLeadingReceiverDecorator,
|
||||||
decorator.range(),
|
decorator.range(),
|
||||||
));
|
));
|
||||||
|
|
@ -77,5 +72,4 @@ where
|
||||||
seen_receiver = true;
|
seen_receiver = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
diagnostics
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,24 +51,14 @@ impl Violation for DjangoNullableModelStringField {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const NOT_NULL_TRUE_FIELDS: [&str; 6] = [
|
|
||||||
"CharField",
|
|
||||||
"TextField",
|
|
||||||
"SlugField",
|
|
||||||
"EmailField",
|
|
||||||
"FilePathField",
|
|
||||||
"URLField",
|
|
||||||
];
|
|
||||||
|
|
||||||
/// DJ001
|
/// DJ001
|
||||||
pub(crate) fn nullable_model_string_field(checker: &Checker, body: &[Stmt]) -> Vec<Diagnostic> {
|
pub(crate) fn nullable_model_string_field(checker: &mut Checker, body: &[Stmt]) {
|
||||||
let mut errors = Vec::new();
|
|
||||||
for statement in body.iter() {
|
for statement in body.iter() {
|
||||||
let Stmt::Assign(ast::StmtAssign {value, ..}) = statement else {
|
let Stmt::Assign(ast::StmtAssign { value, .. }) = statement else {
|
||||||
continue
|
continue;
|
||||||
};
|
};
|
||||||
if let Some(field_name) = is_nullable_field(checker, value) {
|
if let Some(field_name) = is_nullable_field(checker, value) {
|
||||||
errors.push(Diagnostic::new(
|
checker.diagnostics.push(Diagnostic::new(
|
||||||
DjangoNullableModelStringField {
|
DjangoNullableModelStringField {
|
||||||
field_name: field_name.to_string(),
|
field_name: field_name.to_string(),
|
||||||
},
|
},
|
||||||
|
|
@ -76,11 +66,10 @@ pub(crate) fn nullable_model_string_field(checker: &Checker, body: &[Stmt]) -> V
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
errors
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_nullable_field<'a>(checker: &'a Checker, value: &'a Expr) -> Option<&'a str> {
|
fn is_nullable_field<'a>(checker: &'a Checker, value: &'a Expr) -> Option<&'a str> {
|
||||||
let Expr::Call(ast::ExprCall {func, keywords, ..}) = value else {
|
let Expr::Call(ast::ExprCall { func, keywords, .. }) = value else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -88,7 +77,10 @@ fn is_nullable_field<'a>(checker: &'a Checker, value: &'a Expr) -> Option<&'a st
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
if !NOT_NULL_TRUE_FIELDS.contains(&valid_field_name) {
|
if !matches!(
|
||||||
|
valid_field_name,
|
||||||
|
"CharField" | "TextField" | "SlugField" | "EmailField" | "FilePathField" | "URLField"
|
||||||
|
) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,7 +89,7 @@ fn is_nullable_field<'a>(checker: &'a Checker, value: &'a Expr) -> Option<&'a st
|
||||||
let mut unique_key = false;
|
let mut unique_key = false;
|
||||||
for keyword in keywords.iter() {
|
for keyword in keywords.iter() {
|
||||||
let Some(argument) = &keyword.arg else {
|
let Some(argument) = &keyword.arg else {
|
||||||
continue
|
continue;
|
||||||
};
|
};
|
||||||
if !is_const_true(&keyword.value) {
|
if !is_const_true(&keyword.value) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -63,20 +63,23 @@ use super::helpers;
|
||||||
/// [Django Style Guide]: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/#model-style
|
/// [Django Style Guide]: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/#model-style
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct DjangoUnorderedBodyContentInModel {
|
pub struct DjangoUnorderedBodyContentInModel {
|
||||||
elem_type: ContentType,
|
element_type: ContentType,
|
||||||
before: ContentType,
|
prev_element_type: ContentType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Violation for DjangoUnorderedBodyContentInModel {
|
impl Violation for DjangoUnorderedBodyContentInModel {
|
||||||
#[derive_message_formats]
|
#[derive_message_formats]
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
let DjangoUnorderedBodyContentInModel { elem_type, before } = self;
|
let DjangoUnorderedBodyContentInModel {
|
||||||
format!("Order of model's inner classes, methods, and fields does not follow the Django Style Guide: {elem_type} should come before {before}")
|
element_type,
|
||||||
|
prev_element_type,
|
||||||
|
} = self;
|
||||||
|
format!("Order of model's inner classes, methods, and fields does not follow the Django Style Guide: {element_type} should come before {prev_element_type}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
|
||||||
pub(crate) enum ContentType {
|
enum ContentType {
|
||||||
FieldDeclaration,
|
FieldDeclaration,
|
||||||
ManagerDeclaration,
|
ManagerDeclaration,
|
||||||
MetaClass,
|
MetaClass,
|
||||||
|
|
@ -149,24 +152,38 @@ pub(crate) fn unordered_body_content_in_model(
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let mut elements_type_found = Vec::new();
|
|
||||||
|
// Track all the element types we've seen so far.
|
||||||
|
let mut element_types = Vec::new();
|
||||||
|
let mut prev_element_type = None;
|
||||||
for element in body.iter() {
|
for element in body.iter() {
|
||||||
let Some(current_element_type) = get_element_type(element, checker.semantic()) else {
|
let Some(element_type) = get_element_type(element, checker.semantic()) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let Some(&element_type) = elements_type_found
|
|
||||||
|
// Skip consecutive elements of the same type. It's less noisy to only report
|
||||||
|
// violations at type boundaries (e.g., avoid raising a violation for _every_
|
||||||
|
// field declaration that's out of order).
|
||||||
|
if prev_element_type == Some(element_type) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev_element_type = Some(element_type);
|
||||||
|
|
||||||
|
if let Some(&prev_element_type) = element_types
|
||||||
.iter()
|
.iter()
|
||||||
.find(|&&element_type| element_type > current_element_type) else {
|
.find(|&&prev_element_type| prev_element_type > element_type)
|
||||||
elements_type_found.push(current_element_type);
|
{
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let diagnostic = Diagnostic::new(
|
let diagnostic = Diagnostic::new(
|
||||||
DjangoUnorderedBodyContentInModel {
|
DjangoUnorderedBodyContentInModel {
|
||||||
elem_type: current_element_type,
|
element_type,
|
||||||
before: element_type,
|
prev_element_type,
|
||||||
},
|
},
|
||||||
element.range(),
|
element.range(),
|
||||||
);
|
);
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
|
} else {
|
||||||
|
element_types.push(element_type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,58 +1,26 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff/src/rules/flake8_django/mod.rs
|
source: crates/ruff/src/rules/flake8_django/mod.rs
|
||||||
---
|
---
|
||||||
DJ008.py:6:1: DJ008 Model does not define `__str__` method
|
DJ008.py:6:7: DJ008 Model does not define `__str__` method
|
||||||
|
|
|
|
||||||
5 | # Models without __str__
|
5 | # Models without __str__
|
||||||
6 | / class TestModel1(models.Model):
|
6 | class TestModel1(models.Model):
|
||||||
7 | | new_field = models.CharField(max_length=10)
|
| ^^^^^^^^^^ DJ008
|
||||||
8 | |
|
7 | new_field = models.CharField(max_length=10)
|
||||||
9 | | class Meta:
|
|
||||||
10 | | verbose_name = "test model"
|
|
||||||
11 | | verbose_name_plural = "test models"
|
|
||||||
12 | |
|
|
||||||
13 | | @property
|
|
||||||
14 | | def my_brand_new_property(self):
|
|
||||||
15 | | return 1
|
|
||||||
16 | |
|
|
||||||
17 | | def my_beautiful_method(self):
|
|
||||||
18 | | return 2
|
|
||||||
| |________________^ DJ008
|
|
||||||
|
|
|
|
||||||
|
|
||||||
DJ008.py:21:1: DJ008 Model does not define `__str__` method
|
DJ008.py:21:7: DJ008 Model does not define `__str__` method
|
||||||
|
|
|
|
||||||
21 | / class TestModel2(Model):
|
21 | class TestModel2(Model):
|
||||||
22 | | new_field = models.CharField(max_length=10)
|
| ^^^^^^^^^^ DJ008
|
||||||
23 | |
|
22 | new_field = models.CharField(max_length=10)
|
||||||
24 | | class Meta:
|
|
||||||
25 | | verbose_name = "test model"
|
|
||||||
26 | | verbose_name_plural = "test models"
|
|
||||||
27 | |
|
|
||||||
28 | | @property
|
|
||||||
29 | | def my_brand_new_property(self):
|
|
||||||
30 | | return 1
|
|
||||||
31 | |
|
|
||||||
32 | | def my_beautiful_method(self):
|
|
||||||
33 | | return 2
|
|
||||||
| |________________^ DJ008
|
|
||||||
|
|
|
|
||||||
|
|
||||||
DJ008.py:36:1: DJ008 Model does not define `__str__` method
|
DJ008.py:36:7: DJ008 Model does not define `__str__` method
|
||||||
|
|
|
|
||||||
36 | / class TestModel3(Model):
|
36 | class TestModel3(Model):
|
||||||
37 | | new_field = models.CharField(max_length=10)
|
| ^^^^^^^^^^ DJ008
|
||||||
38 | |
|
37 | new_field = models.CharField(max_length=10)
|
||||||
39 | | class Meta:
|
|
||||||
40 | | abstract = False
|
|
||||||
41 | |
|
|
||||||
42 | | @property
|
|
||||||
43 | | def my_brand_new_property(self):
|
|
||||||
44 | | return 1
|
|
||||||
45 | |
|
|
||||||
46 | | def my_beautiful_method(self):
|
|
||||||
47 | | return 2
|
|
||||||
| |________________^ DJ008
|
|
||||||
|
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,4 +37,21 @@ DJ012.py:69:5: DJ012 Order of model's inner classes, methods, and fields does no
|
||||||
| |____________^ DJ012
|
| |____________^ DJ012
|
||||||
|
|
|
|
||||||
|
|
||||||
|
DJ012.py:123:5: DJ012 Order of model's inner classes, methods, and fields does not follow the Django Style Guide: field declaration should come before `Meta` class
|
||||||
|
|
|
||||||
|
121 | verbose_name = "test"
|
||||||
|
122 |
|
||||||
|
123 | first_name = models.CharField(max_length=32)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DJ012
|
||||||
|
124 | last_name = models.CharField(max_length=32)
|
||||||
|
|
|
||||||
|
|
||||||
|
DJ012.py:129:5: DJ012 Order of model's inner classes, methods, and fields does not follow the Django Style Guide: field declaration should come before `Meta` class
|
||||||
|
|
|
||||||
|
127 | pass
|
||||||
|
128 |
|
||||||
|
129 | middle_name = models.CharField(max_length=32)
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DJ012
|
||||||
|
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ use rustpython_parser::ast::{Expr, Ranged};
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct FStringInGetTextFuncCall;
|
pub struct FStringInGetTextFuncCall;
|
||||||
|
|
||||||
|
|
@ -14,11 +16,12 @@ impl Violation for FStringInGetTextFuncCall {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// INT001
|
/// INT001
|
||||||
pub(crate) fn f_string_in_gettext_func_call(args: &[Expr]) -> Option<Diagnostic> {
|
pub(crate) fn f_string_in_gettext_func_call(checker: &mut Checker, args: &[Expr]) {
|
||||||
if let Some(first) = args.first() {
|
if let Some(first) = args.first() {
|
||||||
if first.is_joined_str_expr() {
|
if first.is_joined_str_expr() {
|
||||||
return Some(Diagnostic::new(FStringInGetTextFuncCall {}, first.range()));
|
checker
|
||||||
|
.diagnostics
|
||||||
|
.push(Diagnostic::new(FStringInGetTextFuncCall {}, first.range()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ use rustpython_parser::ast::{self, Expr, Ranged};
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
#[violation]
|
#[violation]
|
||||||
pub struct FormatInGetTextFuncCall;
|
pub struct FormatInGetTextFuncCall;
|
||||||
|
|
||||||
|
|
@ -14,15 +16,16 @@ impl Violation for FormatInGetTextFuncCall {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// INT002
|
/// INT002
|
||||||
pub(crate) fn format_in_gettext_func_call(args: &[Expr]) -> Option<Diagnostic> {
|
pub(crate) fn format_in_gettext_func_call(checker: &mut Checker, args: &[Expr]) {
|
||||||
if let Some(first) = args.first() {
|
if let Some(first) = args.first() {
|
||||||
if let Expr::Call(ast::ExprCall { func, .. }) = &first {
|
if let Expr::Call(ast::ExprCall { func, .. }) = &first {
|
||||||
if let Expr::Attribute(ast::ExprAttribute { attr, .. }) = func.as_ref() {
|
if let Expr::Attribute(ast::ExprAttribute { attr, .. }) = func.as_ref() {
|
||||||
if attr == "format" {
|
if attr == "format" {
|
||||||
return Some(Diagnostic::new(FormatInGetTextFuncCall {}, first.range()));
|
checker
|
||||||
|
.diagnostics
|
||||||
|
.push(Diagnostic::new(FormatInGetTextFuncCall {}, first.range()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use rustpython_parser::ast::{self, Constant, Expr, Operator, Ranged};
|
use rustpython_parser::ast::{self, Constant, Expr, Operator, Ranged};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
|
|
@ -14,7 +15,7 @@ impl Violation for PrintfInGetTextFuncCall {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// INT003
|
/// INT003
|
||||||
pub(crate) fn printf_in_gettext_func_call(args: &[Expr]) -> Option<Diagnostic> {
|
pub(crate) fn printf_in_gettext_func_call(checker: &mut Checker, args: &[Expr]) {
|
||||||
if let Some(first) = args.first() {
|
if let Some(first) = args.first() {
|
||||||
if let Expr::BinOp(ast::ExprBinOp {
|
if let Expr::BinOp(ast::ExprBinOp {
|
||||||
op: Operator::Mod { .. },
|
op: Operator::Mod { .. },
|
||||||
|
|
@ -27,9 +28,10 @@ pub(crate) fn printf_in_gettext_func_call(args: &[Expr]) -> Option<Diagnostic> {
|
||||||
..
|
..
|
||||||
}) = left.as_ref()
|
}) = left.as_ref()
|
||||||
{
|
{
|
||||||
return Some(Diagnostic::new(PrintfInGetTextFuncCall {}, first.range()));
|
checker
|
||||||
|
.diagnostics
|
||||||
|
.push(Diagnostic::new(PrintfInGetTextFuncCall {}, first.range()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,12 @@ impl AlwaysAutofixableViolation for MultipleStartsEndsWith {
|
||||||
|
|
||||||
/// PIE810
|
/// PIE810
|
||||||
pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
|
pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
|
||||||
let Expr::BoolOp(ast::ExprBoolOp { op: BoolOp::Or, values, range: _ }) = expr else {
|
let Expr::BoolOp(ast::ExprBoolOp {
|
||||||
|
op: BoolOp::Or,
|
||||||
|
values,
|
||||||
|
range: _,
|
||||||
|
}) = expr
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -70,24 +75,25 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
|
||||||
func,
|
func,
|
||||||
args,
|
args,
|
||||||
keywords,
|
keywords,
|
||||||
range: _
|
range: _,
|
||||||
}) = &call else {
|
}) = &call
|
||||||
continue
|
else {
|
||||||
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if !(args.len() == 1 && keywords.is_empty()) {
|
if !(args.len() == 1 && keywords.is_empty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Expr::Attribute(ast::ExprAttribute { value, attr, .. } )= func.as_ref() else {
|
let Expr::Attribute(ast::ExprAttribute { value, attr, .. }) = func.as_ref() else {
|
||||||
continue
|
continue;
|
||||||
};
|
};
|
||||||
if attr != "startswith" && attr != "endswith" {
|
if attr != "startswith" && attr != "endswith" {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Expr::Name(ast::ExprName { id: arg_name, .. } )= value.as_ref() else {
|
let Expr::Name(ast::ExprName { id: arg_name, .. }) = value.as_ref() else {
|
||||||
continue
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
duplicates
|
duplicates
|
||||||
|
|
@ -110,8 +116,17 @@ pub(crate) fn multiple_starts_ends_with(checker: &mut Checker, expr: &Expr) {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|index| &values[*index])
|
.map(|index| &values[*index])
|
||||||
.map(|expr| {
|
.map(|expr| {
|
||||||
let Expr::Call(ast::ExprCall { func: _, args, keywords: _, range: _}) = expr else {
|
let Expr::Call(ast::ExprCall {
|
||||||
unreachable!("{}", format!("Indices should only contain `{attr_name}` calls"))
|
func: _,
|
||||||
|
args,
|
||||||
|
keywords: _,
|
||||||
|
range: _,
|
||||||
|
}) = expr
|
||||||
|
else {
|
||||||
|
unreachable!(
|
||||||
|
"{}",
|
||||||
|
format!("Indices should only contain `{attr_name}` calls")
|
||||||
|
)
|
||||||
};
|
};
|
||||||
args.get(0)
|
args.get(0)
|
||||||
.unwrap_or_else(|| panic!("`{attr_name}` should have one argument"))
|
.unwrap_or_else(|| panic!("`{attr_name}` should have one argument"))
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ mod tests {
|
||||||
#[test_case(Rule::BadVersionInfoComparison, Path::new("PYI006.pyi"))]
|
#[test_case(Rule::BadVersionInfoComparison, Path::new("PYI006.pyi"))]
|
||||||
#[test_case(Rule::CollectionsNamedTuple, Path::new("PYI024.py"))]
|
#[test_case(Rule::CollectionsNamedTuple, Path::new("PYI024.py"))]
|
||||||
#[test_case(Rule::CollectionsNamedTuple, Path::new("PYI024.pyi"))]
|
#[test_case(Rule::CollectionsNamedTuple, Path::new("PYI024.pyi"))]
|
||||||
|
#[test_case(Rule::ComplexIfStatementInStub, Path::new("PYI002.py"))]
|
||||||
|
#[test_case(Rule::ComplexIfStatementInStub, Path::new("PYI002.pyi"))]
|
||||||
#[test_case(Rule::DocstringInStub, Path::new("PYI021.py"))]
|
#[test_case(Rule::DocstringInStub, Path::new("PYI021.py"))]
|
||||||
#[test_case(Rule::DocstringInStub, Path::new("PYI021.pyi"))]
|
#[test_case(Rule::DocstringInStub, Path::new("PYI021.pyi"))]
|
||||||
#[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.py"))]
|
#[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.py"))]
|
||||||
|
|
@ -56,6 +58,8 @@ mod tests {
|
||||||
#[test_case(Rule::TSuffixedTypeAlias, Path::new("PYI043.pyi"))]
|
#[test_case(Rule::TSuffixedTypeAlias, Path::new("PYI043.pyi"))]
|
||||||
#[test_case(Rule::FutureAnnotationsInStub, Path::new("PYI044.py"))]
|
#[test_case(Rule::FutureAnnotationsInStub, Path::new("PYI044.py"))]
|
||||||
#[test_case(Rule::FutureAnnotationsInStub, Path::new("PYI044.pyi"))]
|
#[test_case(Rule::FutureAnnotationsInStub, Path::new("PYI044.pyi"))]
|
||||||
|
#[test_case(Rule::PatchVersionComparison, Path::new("PYI004.py"))]
|
||||||
|
#[test_case(Rule::PatchVersionComparison, Path::new("PYI004.pyi"))]
|
||||||
#[test_case(Rule::TypeCommentInStub, Path::new("PYI033.py"))]
|
#[test_case(Rule::TypeCommentInStub, Path::new("PYI033.py"))]
|
||||||
#[test_case(Rule::TypeCommentInStub, Path::new("PYI033.pyi"))]
|
#[test_case(Rule::TypeCommentInStub, Path::new("PYI033.pyi"))]
|
||||||
#[test_case(Rule::TypedArgumentDefaultInStub, Path::new("PYI011.py"))]
|
#[test_case(Rule::TypedArgumentDefaultInStub, Path::new("PYI011.py"))]
|
||||||
|
|
@ -72,6 +76,10 @@ mod tests {
|
||||||
#[test_case(Rule::UnrecognizedPlatformCheck, Path::new("PYI007.pyi"))]
|
#[test_case(Rule::UnrecognizedPlatformCheck, Path::new("PYI007.pyi"))]
|
||||||
#[test_case(Rule::UnrecognizedPlatformName, Path::new("PYI008.py"))]
|
#[test_case(Rule::UnrecognizedPlatformName, Path::new("PYI008.py"))]
|
||||||
#[test_case(Rule::UnrecognizedPlatformName, Path::new("PYI008.pyi"))]
|
#[test_case(Rule::UnrecognizedPlatformName, Path::new("PYI008.pyi"))]
|
||||||
|
#[test_case(Rule::UnrecognizedVersionInfoCheck, Path::new("PYI003.py"))]
|
||||||
|
#[test_case(Rule::UnrecognizedVersionInfoCheck, Path::new("PYI003.pyi"))]
|
||||||
|
#[test_case(Rule::WrongTupleLengthVersionComparison, Path::new("PYI005.py"))]
|
||||||
|
#[test_case(Rule::WrongTupleLengthVersionComparison, Path::new("PYI005.pyi"))]
|
||||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||||
let diagnostics = test_path(
|
let diagnostics = test_path(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use rustpython_parser::ast::{CmpOp, Expr, Ranged};
|
use rustpython_parser::ast::{self, CmpOp, Expr, Ranged};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Violation};
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
@ -52,19 +52,23 @@ pub struct BadVersionInfoComparison;
|
||||||
impl Violation for BadVersionInfoComparison {
|
impl Violation for BadVersionInfoComparison {
|
||||||
#[derive_message_formats]
|
#[derive_message_formats]
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
format!("Use `<` or `>=` for version info comparisons")
|
format!("Use `<` or `>=` for `sys.version_info` comparisons")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// PYI006
|
/// PYI006
|
||||||
pub(crate) fn bad_version_info_comparison(
|
pub(crate) fn bad_version_info_comparison(checker: &mut Checker, test: &Expr) {
|
||||||
checker: &mut Checker,
|
let Expr::Compare(ast::ExprCompare {
|
||||||
expr: &Expr,
|
left,
|
||||||
left: &Expr,
|
ops,
|
||||||
ops: &[CmpOp],
|
comparators,
|
||||||
comparators: &[Expr],
|
..
|
||||||
) {
|
}) = test
|
||||||
let ([op], [_right]) = (ops, comparators) else {
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let ([op], [_right]) = (ops.as_slice(), comparators.as_slice()) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -78,8 +82,11 @@ pub(crate) fn bad_version_info_comparison(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !matches!(op, CmpOp::Lt | CmpOp::GtE) {
|
if matches!(op, CmpOp::Lt | CmpOp::GtE) {
|
||||||
let diagnostic = Diagnostic::new(BadVersionInfoComparison, expr.range());
|
return;
|
||||||
checker.diagnostics.push(diagnostic);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checker
|
||||||
|
.diagnostics
|
||||||
|
.push(Diagnostic::new(BadVersionInfoComparison, test.range()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
use rustpython_parser::ast::{self, Expr, Ranged};
|
||||||
|
|
||||||
|
use ruff_diagnostics::{Diagnostic, Violation};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for `if` statements with complex conditionals in stubs.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Stub files support simple conditionals to test for differences in Python
|
||||||
|
/// versions and platforms. However, type checkers only understand a limited
|
||||||
|
/// subset of these conditionals; complex conditionals may result in false
|
||||||
|
/// positives or false negatives.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// import sys
|
||||||
|
///
|
||||||
|
/// if (2, 7) < sys.version_info < (3, 5):
|
||||||
|
/// ...
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// import sys
|
||||||
|
///
|
||||||
|
/// if sys.version_info < (3, 5):
|
||||||
|
/// ...
|
||||||
|
/// ```
|
||||||
|
#[violation]
|
||||||
|
pub struct ComplexIfStatementInStub;
|
||||||
|
|
||||||
|
impl Violation for ComplexIfStatementInStub {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"`if`` test must be a simple comparison against `sys.platform` or `sys.version_info`"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PYI002
|
||||||
|
pub(crate) fn complex_if_statement_in_stub(checker: &mut Checker, test: &Expr) {
|
||||||
|
let Expr::Compare(ast::ExprCompare {
|
||||||
|
left, comparators, ..
|
||||||
|
}) = test
|
||||||
|
else {
|
||||||
|
checker
|
||||||
|
.diagnostics
|
||||||
|
.push(Diagnostic::new(ComplexIfStatementInStub, test.range()));
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if comparators.len() != 1 {
|
||||||
|
checker
|
||||||
|
.diagnostics
|
||||||
|
.push(Diagnostic::new(ComplexIfStatementInStub, test.range()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if left.is_subscript_expr() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if checker
|
||||||
|
.semantic()
|
||||||
|
.resolve_call_path(left)
|
||||||
|
.map_or(false, |call_path| {
|
||||||
|
matches!(call_path.as_slice(), ["sys", "version_info" | "platform"])
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
checker
|
||||||
|
.diagnostics
|
||||||
|
.push(Diagnostic::new(ComplexIfStatementInStub, test.range()));
|
||||||
|
}
|
||||||
|
|
@ -70,15 +70,12 @@ pub(crate) fn iter_method_return_iterable(checker: &mut Checker, definition: &De
|
||||||
kind: MemberKind::Method,
|
kind: MemberKind::Method,
|
||||||
stmt,
|
stmt,
|
||||||
..
|
..
|
||||||
}) = definition else {
|
}) = definition
|
||||||
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Stmt::FunctionDef(ast::StmtFunctionDef {
|
let Stmt::FunctionDef(ast::StmtFunctionDef { name, returns, .. }) = stmt else {
|
||||||
name,
|
|
||||||
returns,
|
|
||||||
..
|
|
||||||
}) = stmt else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue