Compare commits
400 Commits
v0.0.281
...
main-backu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e87032d57d | ||
|
|
0c2abf8316 | ||
|
|
6e85e0010f | ||
|
|
2ea0a0b28a | ||
|
|
10abc8fd92 | ||
|
|
61195bc7b1 | ||
|
|
9040554be5 | ||
|
|
06ba1cf0ad | ||
|
|
7a63a8f45a | ||
|
|
c1bc0dc83d | ||
|
|
90573975f3 | ||
|
|
802df97d55 | ||
|
|
6f4ee32807 | ||
|
|
7ec979d022 | ||
|
|
fab9cc5294 | ||
|
|
ff7bab589e | ||
|
|
593b46be5e | ||
|
|
13196fc500 | ||
|
|
5ef4ccd632 | ||
|
|
e363fb860e | ||
|
|
4888d800fb | ||
|
|
4d03b9b5b2 | ||
|
|
fb365c642b | ||
|
|
db04fd4157 | ||
|
|
ff3e8ead36 | ||
|
|
1bbec2b967 | ||
|
|
510be51cfa | ||
|
|
3b4c8fffe5 | ||
|
|
126652b684 | ||
|
|
57e8712d76 | ||
|
|
78c6ede1c9 | ||
|
|
a843a00f6b | ||
|
|
f846f1ea42 | ||
|
|
1d4b7a395f | ||
|
|
ce3ce0734b | ||
|
|
b0e119f049 | ||
|
|
1f5e707829 | ||
|
|
ed7acfe477 | ||
|
|
c31b58eb39 | ||
|
|
c0a3a20c63 | ||
|
|
05ae26b935 | ||
|
|
b996b21ffc | ||
|
|
2d1f69cbb9 | ||
|
|
0f2e295f3a | ||
|
|
c174bbf1f2 | ||
|
|
8078663b6c | ||
|
|
f60e204b73 | ||
|
|
08ebbe40d7 | ||
|
|
ed3b4eb72b | ||
|
|
f0d200c8a1 | ||
|
|
8d74eee750 | ||
|
|
21aa0b8d84 | ||
|
|
9cb00518e5 | ||
|
|
6f65c5cba7 | ||
|
|
8a415fa61e | ||
|
|
5054cbe84f | ||
|
|
69d27d924c | ||
|
|
5270020423 | ||
|
|
edcfcb4a74 | ||
|
|
40a603208f | ||
|
|
b2f95e2848 | ||
|
|
a2e3209c42 | ||
|
|
5e9e8a7589 | ||
|
|
fdec727f80 | ||
|
|
3fbf4f6804 | ||
|
|
fe25708d89 | ||
|
|
342cd19f50 | ||
|
|
4a2c4aad0b | ||
|
|
ae3a477c97 | ||
|
|
531aeb3511 | ||
|
|
4de0cb1827 | ||
|
|
5493c9f4e3 | ||
|
|
d23611db65 | ||
|
|
7d384d88d0 | ||
|
|
b81273e9bc | ||
|
|
335780aeea | ||
|
|
e1f02fced7 | ||
|
|
d4084fb17a | ||
|
|
41a0ef8740 | ||
|
|
25cc1da319 | ||
|
|
33a3c407a9 | ||
|
|
fac0c25343 | ||
|
|
4ff779c298 | ||
|
|
726884b287 | ||
|
|
851f23668f | ||
|
|
3654cf0bdf | ||
|
|
6c5c311bab | ||
|
|
531e41ae2c | ||
|
|
b48834fe2d | ||
|
|
205ee80033 | ||
|
|
fc301ab1b0 | ||
|
|
e820928f11 | ||
|
|
ff17f6e178 | ||
|
|
3bdf8a940a | ||
|
|
9d47d3d212 | ||
|
|
611dcc2e9b | ||
|
|
53de75efc3 | ||
|
|
0c7d16b61a | ||
|
|
735c06d5f4 | ||
|
|
02f13abf50 | ||
|
|
10dda125ff | ||
|
|
27e3873dc2 | ||
|
|
dd4cc25227 | ||
|
|
b78001c953 | ||
|
|
718354673e | ||
|
|
192379cede | ||
|
|
a983f4383f | ||
|
|
947fb53d0b | ||
|
|
2baad9ead6 | ||
|
|
2af9805662 | ||
|
|
5b2af304a2 | ||
|
|
cbe4e8c5f3 | ||
|
|
aabc96dde9 | ||
|
|
99e108dd53 | ||
|
|
d6b6df5d1c | ||
|
|
822cac5aa0 | ||
|
|
aa101e4f26 | ||
|
|
cbc4cb286b | ||
|
|
6fa3d0f90a | ||
|
|
455bcc01a0 | ||
|
|
d495cd9129 | ||
|
|
d8822d1091 | ||
|
|
75f6ce1ae5 | ||
|
|
41e9e7280a | ||
|
|
17c8abcec1 | ||
|
|
e000b1c304 | ||
|
|
243ca16b34 | ||
|
|
4de9580b92 | ||
|
|
5cf85f0b9d | ||
|
|
4dc030ba9d | ||
|
|
1d366d52ab | ||
|
|
a3d9d8cb14 | ||
|
|
09a6afdd04 | ||
|
|
a14e43e03a | ||
|
|
f47dfca4e3 | ||
|
|
58c35ab458 | ||
|
|
7b8844bd3e | ||
|
|
6d7358090b | ||
|
|
9f1a538eba | ||
|
|
48920a034e | ||
|
|
13d6e275ef | ||
|
|
96eb80f5cf | ||
|
|
a73bac9ed1 | ||
|
|
6b60f85cc4 | ||
|
|
39b2dbe04d | ||
|
|
e1f70100ac | ||
|
|
d66d935879 | ||
|
|
e28f333f23 | ||
|
|
0adcdd995d | ||
|
|
e6eb49ffb0 | ||
|
|
80109b1fe0 | ||
|
|
bd64603950 | ||
|
|
5b0e92d725 | ||
|
|
ae9d3c3193 | ||
|
|
3873414b30 | ||
|
|
a9f4b59f40 | ||
|
|
6d6155413b | ||
|
|
f9b5469642 | ||
|
|
ef38eb6b1a | ||
|
|
0ea53825db | ||
|
|
9d6ae774f8 | ||
|
|
55fc0e83f3 | ||
|
|
713dd2b91e | ||
|
|
969ea23d67 | ||
|
|
b80bbec8e6 | ||
|
|
8f425e9ce2 | ||
|
|
d7e2e7361e | ||
|
|
c37e0c7f03 | ||
|
|
0d7b94817d | ||
|
|
f43e5b72e2 | ||
|
|
a19f294b0c | ||
|
|
7ebef61c47 | ||
|
|
b61f4d7b69 | ||
|
|
e26369a34e | ||
|
|
97a08ee77b | ||
|
|
cb8c6fb78d | ||
|
|
8580e4ebb5 | ||
|
|
39fc23cf92 | ||
|
|
66e3080173 | ||
|
|
1511b6631b | ||
|
|
2a8aa6f308 | ||
|
|
dc628cab8f | ||
|
|
c137bc9d77 | ||
|
|
60180fd54c | ||
|
|
c7ed645cc6 | ||
|
|
8caa28f0f8 | ||
|
|
8aa3bc93f3 | ||
|
|
f39ffef370 | ||
|
|
e093d2bee6 | ||
|
|
ca5b474d45 | ||
|
|
2b43d45bd5 | ||
|
|
4bdc2d47c1 | ||
|
|
09b82e41d5 | ||
|
|
cc6d8a1c58 | ||
|
|
07918f0a9a | ||
|
|
a0786ea872 | ||
|
|
4713b2b3ab | ||
|
|
e7f14ab9b8 | ||
|
|
56c73cc63d | ||
|
|
659f4dd8bf | ||
|
|
bd158089e0 | ||
|
|
a73bee7aae | ||
|
|
1468fe46ab | ||
|
|
c9364718b4 | ||
|
|
38cf933bcb | ||
|
|
838990ae15 | ||
|
|
aa0290bbfc | ||
|
|
f74e44d1e8 | ||
|
|
d9df131720 | ||
|
|
f2ffe12f8c | ||
|
|
6dba8430be | ||
|
|
b26365b215 | ||
|
|
2d019930e9 | ||
|
|
d5fc7c4c87 | ||
|
|
393869c47c | ||
|
|
4edd2bf78a | ||
|
|
d3c4551629 | ||
|
|
581f6e176c | ||
|
|
ac4d3c076c | ||
|
|
e5fe037e38 | ||
|
|
872b9d4765 | ||
|
|
674eeec29c | ||
|
|
4f1e7c6291 | ||
|
|
884a7bdb15 | ||
|
|
7885344bcf | ||
|
|
e8fef39861 | ||
|
|
509cf7ed0d | ||
|
|
a4a5366504 | ||
|
|
2dfd053bed | ||
|
|
7f552e4594 | ||
|
|
9efa872023 | ||
|
|
fd8468c5eb | ||
|
|
958c7e33ad | ||
|
|
0f311cd5e5 | ||
|
|
0365752bf3 | ||
|
|
6d140426c1 | ||
|
|
33a62789f7 | ||
|
|
84dff79ddc | ||
|
|
300710f7db | ||
|
|
515dceb07b | ||
|
|
2858c315bf | ||
|
|
3b0fd61b3b | ||
|
|
9030679193 | ||
|
|
71ba4226c1 | ||
|
|
3d03439618 | ||
|
|
39f410c909 | ||
|
|
4222b13e6c | ||
|
|
d691527ead | ||
|
|
40e2150f7e | ||
|
|
92bf96608c | ||
|
|
fac6a857f6 | ||
|
|
68586f8e3c | ||
|
|
f8787a9377 | ||
|
|
530a20cc96 | ||
|
|
6cb57b2075 | ||
|
|
92b2574d52 | ||
|
|
95fb938bd6 | ||
|
|
9683314264 | ||
|
|
d9bbeeb9b3 | ||
|
|
68116d5c11 | ||
|
|
e164a41723 | ||
|
|
aa32a73c5b | ||
|
|
9d7d629cef | ||
|
|
439298e735 | ||
|
|
16173bf581 | ||
|
|
7e7b2eadee | ||
|
|
80116c768d | ||
|
|
200390c1ab | ||
|
|
24d2ab8b0a | ||
|
|
41f21a7b5d | ||
|
|
63e4a36e27 | ||
|
|
15ab44384c | ||
|
|
5380b85579 | ||
|
|
b707f53f23 | ||
|
|
6439867f78 | ||
|
|
1904d095f9 | ||
|
|
7e8f683808 | ||
|
|
37b1894834 | ||
|
|
201d08583a | ||
|
|
107b2e11ae | ||
|
|
661b210391 | ||
|
|
9aed9143fe | ||
|
|
c21d0d9283 | ||
|
|
313fd7d28c | ||
|
|
3aa0096212 | ||
|
|
53dec88029 | ||
|
|
bd0c15d34e | ||
|
|
ce0be73841 | ||
|
|
b2ac4f60f1 | ||
|
|
1fdfa5fe1b | ||
|
|
c0f390ebc6 | ||
|
|
c387b5d523 | ||
|
|
ea95e1a715 | ||
|
|
c16e08d59b | ||
|
|
ff00460ff4 | ||
|
|
f4672e4256 | ||
|
|
6e89b3ab1a | ||
|
|
6a174dae45 | ||
|
|
8936ab2f8d | ||
|
|
165b979733 | ||
|
|
06c92bb899 | ||
|
|
1a657731dd | ||
|
|
d8cfc7e84f | ||
|
|
de2e88656e | ||
|
|
052dee72b8 | ||
|
|
751f9d304f | ||
|
|
d6f9dd0763 | ||
|
|
5fc8c4d0b1 | ||
|
|
1b7a272b77 | ||
|
|
3abdc87076 | ||
|
|
99b02be35a | ||
|
|
f99167d4ed | ||
|
|
816e1e711c | ||
|
|
e2b28d07c8 | ||
|
|
28785784b2 | ||
|
|
6f6b7b2312 | ||
|
|
93cb05ebda | ||
|
|
e8200ab674 | ||
|
|
492f09298f | ||
|
|
35eea0b8ec | ||
|
|
1eeddb521e | ||
|
|
96a50810a6 | ||
|
|
51b7dbb89c | ||
|
|
28a8c3a062 | ||
|
|
4e5626dfd5 | ||
|
|
f979d8dbc3 | ||
|
|
cc084b4fec | ||
|
|
bbeec36fdb | ||
|
|
b6c230f3ca | ||
|
|
a5b59f3c9d | ||
|
|
d5a208ca9d | ||
|
|
fa41a1e2f6 | ||
|
|
952d70b9d1 | ||
|
|
02953b9fe6 | ||
|
|
8adc74fe26 | ||
|
|
48c0cb5599 | ||
|
|
2e33a3d0e9 | ||
|
|
1cc342e4ed | ||
|
|
519718e65d | ||
|
|
5da5490b19 | ||
|
|
518cf728c3 | ||
|
|
3397737a76 | ||
|
|
6211a3a3a8 | ||
|
|
e64d7d196d | ||
|
|
f8b45e48e1 | ||
|
|
f99e4789ed | ||
|
|
86b847204e | ||
|
|
26b529f9dc | ||
|
|
b21ed24025 | ||
|
|
a414677892 | ||
|
|
bfe4795b6c | ||
|
|
40690b9761 | ||
|
|
1fd898c14c | ||
|
|
42b95a9a95 | ||
|
|
e8230efe1a | ||
|
|
7fcc18daea | ||
|
|
2b7bf79d29 | ||
|
|
904fc477f1 | ||
|
|
bfac0355dc | ||
|
|
c16e650071 | ||
|
|
acde8bb625 | ||
|
|
7f99404618 | ||
|
|
3742f9117b | ||
|
|
a66902406f | ||
|
|
51b6571ee1 | ||
|
|
2345bc895d | ||
|
|
53c48bf6b9 | ||
|
|
ffacac05bb | ||
|
|
3bae0823f7 | ||
|
|
29644a30d7 | ||
|
|
07712eda58 | ||
|
|
76728bb69d | ||
|
|
721c2709c8 | ||
|
|
7865cace06 | ||
|
|
a9330765fa | ||
|
|
9941eef853 | ||
|
|
e494175cd2 | ||
|
|
dd7560c494 | ||
|
|
6603b4a124 | ||
|
|
dab7e20a33 | ||
|
|
13f3ec5b2b | ||
|
|
452cd308d5 | ||
|
|
3c469eaa26 | ||
|
|
0c5bf8e470 | ||
|
|
3f52fdd50a | ||
|
|
f0c0897f23 | ||
|
|
a35d4dc22a | ||
|
|
5fe1bd3900 | ||
|
|
3737af157f | ||
|
|
ed01cd63ee | ||
|
|
1dd57971a9 | ||
|
|
d1ae5a7448 | ||
|
|
e89d6febea | ||
|
|
4dcad05aa0 | ||
|
|
b0d270b881 | ||
|
|
e3ccef2504 | ||
|
|
dcdd964e0c | ||
|
|
107442cc06 | ||
|
|
10957879db | ||
|
|
f0148f46d0 |
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@@ -50,10 +50,6 @@ jobs:
|
||||
- crates/ruff_formatter/**
|
||||
- crates/ruff_python_trivia/**
|
||||
- crates/ruff_python_ast/**
|
||||
- crates/ruff_source_file/**
|
||||
- crates/ruff_python_index/**
|
||||
- crates/ruff_text_size/**
|
||||
- crates/ruff_python_parser/**
|
||||
|
||||
|
||||
cargo-fmt:
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -794,7 +794,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.281"
|
||||
version = "0.0.280"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2035,7 +2035,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.281"
|
||||
version = "0.0.280"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2134,7 +2134,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.281"
|
||||
version = "0.0.280"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
|
||||
@@ -140,7 +140,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.281
|
||||
rev: v0.0.280
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.281"
|
||||
version = "0.0.280"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.281"
|
||||
version = "0.0.280"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import typing
|
||||
import sys
|
||||
from typing import TypeAlias
|
||||
|
||||
|
||||
_UnusedPrivateTypeAlias: TypeAlias = int | None
|
||||
_T: typing.TypeAlias = str
|
||||
|
||||
# OK
|
||||
_UsedPrivateTypeAlias: TypeAlias = int | None
|
||||
|
||||
def func(arg: _UsedPrivateTypeAlias) -> _UsedPrivateTypeAlias:
|
||||
...
|
||||
|
||||
|
||||
if sys.version_info > (3, 9):
|
||||
_PrivateTypeAlias: TypeAlias = str | None
|
||||
else:
|
||||
_PrivateTypeAlias: TypeAlias = float | None
|
||||
|
||||
|
||||
def func2(arg: _PrivateTypeAlias) -> None: ...
|
||||
@@ -1,22 +0,0 @@
|
||||
import typing
|
||||
import sys
|
||||
from typing import TypeAlias
|
||||
|
||||
|
||||
_UnusedPrivateTypeAlias: TypeAlias = int | None
|
||||
_T: typing.TypeAlias = str
|
||||
|
||||
# OK
|
||||
_UsedPrivateTypeAlias: TypeAlias = int | None
|
||||
|
||||
def func(arg: _UsedPrivateTypeAlias) -> _UsedPrivateTypeAlias:
|
||||
...
|
||||
|
||||
|
||||
if sys.version_info > (3, 9):
|
||||
_PrivateTypeAlias: TypeAlias = str | None
|
||||
else:
|
||||
_PrivateTypeAlias: TypeAlias = float | None
|
||||
|
||||
|
||||
def func2(arg: _PrivateTypeAlias) -> None: ...
|
||||
@@ -1,18 +0,0 @@
|
||||
import typing
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class _UnusedTypedDict(TypedDict):
|
||||
foo: str
|
||||
|
||||
|
||||
class _UnusedTypedDict2(typing.TypedDict):
|
||||
bar: int
|
||||
|
||||
|
||||
class _UsedTypedDict(TypedDict):
|
||||
foo: bytes
|
||||
|
||||
|
||||
class _CustomClass(_UsedTypedDict):
|
||||
bar: list[int]
|
||||
@@ -1,32 +0,0 @@
|
||||
import sys
|
||||
import typing
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
class _UnusedTypedDict(TypedDict):
|
||||
foo: str
|
||||
|
||||
|
||||
class _UnusedTypedDict2(typing.TypedDict):
|
||||
bar: int
|
||||
|
||||
|
||||
# OK
|
||||
class _UsedTypedDict(TypedDict):
|
||||
foo: bytes
|
||||
|
||||
|
||||
class _CustomClass(_UsedTypedDict):
|
||||
bar: list[int]
|
||||
|
||||
|
||||
if sys.version_info >= (3, 10):
|
||||
class _UsedTypedDict2(TypedDict):
|
||||
foo: int
|
||||
else:
|
||||
class _UsedTypedDict2(TypedDict):
|
||||
foo: float
|
||||
|
||||
|
||||
class _CustomClass2(_UsedTypedDict2):
|
||||
bar: list[int]
|
||||
@@ -1,15 +1,7 @@
|
||||
import contextlib
|
||||
import pathlib
|
||||
import pathlib as pl
|
||||
from pathlib import Path
|
||||
from pathlib import Path as P
|
||||
|
||||
# SIM115
|
||||
f = open("foo.txt")
|
||||
f = Path("foo.txt").open()
|
||||
f = pathlib.Path("foo.txt").open()
|
||||
f = pl.Path("foo.txt").open()
|
||||
f = P("foo.txt").open()
|
||||
data = f.read()
|
||||
f.close()
|
||||
|
||||
|
||||
@@ -30,13 +30,3 @@ for key in list(obj.keys()):
|
||||
(k for k in obj.keys()) # SIM118
|
||||
|
||||
key in (obj or {}).keys() # SIM118
|
||||
|
||||
from typing import KeysView
|
||||
|
||||
|
||||
class Foo:
|
||||
def keys(self) -> KeysView[object]:
|
||||
...
|
||||
|
||||
def __contains__(self, key: object) -> bool:
|
||||
return key in self.keys() # OK
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
import bar
|
||||
import foo
|
||||
@@ -1,2 +0,0 @@
|
||||
import foo
|
||||
import bar
|
||||
@@ -1,23 +1,28 @@
|
||||
# PERF203
|
||||
for i in range(10):
|
||||
try:
|
||||
try: # PERF203
|
||||
print(f"{i}")
|
||||
except:
|
||||
print("error")
|
||||
|
||||
# OK
|
||||
try:
|
||||
for i in range(10):
|
||||
print(f"{i}")
|
||||
except:
|
||||
print("error")
|
||||
|
||||
# OK
|
||||
i = 0
|
||||
while i < 10:
|
||||
while i < 10: # PERF203
|
||||
try:
|
||||
print(f"{i}")
|
||||
except:
|
||||
print("error")
|
||||
|
||||
i += 1
|
||||
|
||||
try:
|
||||
i = 0
|
||||
while i < 10:
|
||||
print(f"{i}")
|
||||
i += 1
|
||||
except:
|
||||
print("error")
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#: E241
|
||||
a = (1, 2)
|
||||
#: Okay
|
||||
b = (1, 20)
|
||||
#: E242
|
||||
a = (1, 2) # tab before 2
|
||||
#: Okay
|
||||
b = (1, 20) # space before 20
|
||||
#: E241 E241 E241
|
||||
# issue 135
|
||||
more_spaces = [a, b,
|
||||
ef, +h,
|
||||
c, -d]
|
||||
@@ -1,103 +0,0 @@
|
||||
"""Test type parameters and aliases"""
|
||||
|
||||
# Type parameters in type alias statements
|
||||
|
||||
from some_module import Bar
|
||||
|
||||
type Foo[T] = T # OK
|
||||
type Foo[T] = list[T] # OK
|
||||
type Foo[T: ForwardA] = T # OK
|
||||
type Foo[*Ts] = Bar[Ts] # OK
|
||||
type Foo[**P] = Bar[P] # OK
|
||||
class ForwardA: ...
|
||||
|
||||
# Types used in aliased assignment must exist
|
||||
|
||||
type Foo = DoesNotExist # F821: Undefined name `DoesNotExist`
|
||||
type Foo = list[DoesNotExist] # F821: Undefined name `DoesNotExist`
|
||||
|
||||
# Type parameters do not escape alias scopes
|
||||
|
||||
type Foo[T] = T
|
||||
T # F821: Undefined name `T` - not accessible afterward alias scope
|
||||
|
||||
# Type parameters in functions
|
||||
|
||||
def foo[T](t: T) -> T: return t # OK
|
||||
async def afoo[T](t: T) -> T: return t # OK
|
||||
def with_forward_ref[T: ForwardB](t: T) -> T: return t # OK
|
||||
def can_access_inside[T](t: T) -> T: # OK
|
||||
print(T) # OK
|
||||
return t # OK
|
||||
class ForwardB: ...
|
||||
|
||||
|
||||
# Type parameters do not escape function scopes
|
||||
|
||||
from some_library import some_decorator
|
||||
|
||||
@some_decorator(T) # F821: Undefined name `T` - not accessible in decorators
|
||||
|
||||
def foo[T](t: T) -> None: ...
|
||||
T # F821: Undefined name `T` - not accessible afterward function scope
|
||||
|
||||
|
||||
# Type parameters in classes
|
||||
|
||||
class Foo[T](list[T]): ... # OK
|
||||
class UsesForward[T: ForwardC](list[T]): ... # OK
|
||||
class ForwardC: ...
|
||||
class WithinBody[T](list[T]): # OK
|
||||
t = T # OK
|
||||
x: T # OK
|
||||
|
||||
def foo(self, x: T) -> T: # OK
|
||||
return x
|
||||
|
||||
def foo(self):
|
||||
T # OK
|
||||
|
||||
|
||||
# Type parameters do not escape class scopes
|
||||
|
||||
from some_library import some_decorator
|
||||
@some_decorator(T) # F821: Undefined name `T` - not accessible in decorators
|
||||
|
||||
class Foo[T](list[T]): ...
|
||||
T # F821: Undefined name `T` - not accessible after class scope
|
||||
|
||||
# Types specified in bounds should exist
|
||||
|
||||
type Foo[T: DoesNotExist] = T # F821: Undefined name `DoesNotExist`
|
||||
def foo[T: DoesNotExist](t: T) -> T: return t # F821: Undefined name `DoesNotExist`
|
||||
class Foo[T: DoesNotExist](list[T]): ... # F821: Undefined name `DoesNotExist`
|
||||
|
||||
type Foo[T: (DoesNotExist1, DoesNotExist2)] = T # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
def foo[T: (DoesNotExist1, DoesNotExist2)](t: T) -> T: return t # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
class Foo[T: (DoesNotExist1, DoesNotExist2)](list[T]): ... # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
|
||||
# Type parameters in nested classes
|
||||
|
||||
class Parent[T]:
|
||||
t = T # OK
|
||||
|
||||
def can_use_class_variable(self, x: t) -> t: # OK
|
||||
return x
|
||||
|
||||
class Child:
|
||||
def can_access_parent_type_parameter(self, x: T) -> T: # OK
|
||||
T # OK
|
||||
return x
|
||||
|
||||
def cannot_access_parent_variable(self, x: t) -> t: # F821: Undefined name `T`
|
||||
t # F821: Undefined name `t`
|
||||
return x
|
||||
|
||||
# Type parameters in nested functions
|
||||
|
||||
def can_access_inside_nested[T](t: T) -> T: # OK
|
||||
def bar(x: T) -> T: # OK
|
||||
T # OK
|
||||
return x
|
||||
|
||||
bar(t)
|
||||
@@ -63,10 +63,3 @@ def main():
|
||||
|
||||
for sys in range(5):
|
||||
pass
|
||||
|
||||
|
||||
import requests_mock as rm
|
||||
|
||||
|
||||
def requests_mock(requests_mock: rm.Mocker):
|
||||
print(rm.ANY)
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
class Person:
|
||||
def __init__(self):
|
||||
self.name = "monty"
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, Person) and other.name == self.name
|
||||
|
||||
class Language:
|
||||
def __init__(self):
|
||||
self.name = "python"
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, Language) and other.name == self.name
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
@@ -2,9 +2,21 @@ x = range(10)
|
||||
|
||||
# RUF015
|
||||
list(x)[0]
|
||||
list(x)[:1]
|
||||
list(x)[:1:1]
|
||||
list(x)[:1:2]
|
||||
tuple(x)[0]
|
||||
tuple(x)[:1]
|
||||
tuple(x)[:1:1]
|
||||
tuple(x)[:1:2]
|
||||
list(i for i in x)[0]
|
||||
list(i for i in x)[:1]
|
||||
list(i for i in x)[:1:1]
|
||||
list(i for i in x)[:1:2]
|
||||
[i for i in x][0]
|
||||
[i for i in x][:1]
|
||||
[i for i in x][:1:1]
|
||||
[i for i in x][:1:2]
|
||||
|
||||
# OK (not indexing (solely) the first element)
|
||||
list(x)
|
||||
@@ -17,9 +29,6 @@ list(x)[::]
|
||||
[i for i in x]
|
||||
[i for i in x][1]
|
||||
[i for i in x][-1]
|
||||
[i for i in x][:1]
|
||||
[i for i in x][:1:1]
|
||||
[i for i in x][:1:2]
|
||||
[i for i in x][1:]
|
||||
[i for i in x][:3:2]
|
||||
[i for i in x][::2]
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import os # ruff: noqa: F401
|
||||
|
||||
|
||||
def f():
|
||||
x = 1
|
||||
@@ -1,6 +1,6 @@
|
||||
use itertools::Itertools;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use itertools::Itertools;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
@@ -59,30 +59,35 @@ fn apply_fixes<'a>(
|
||||
})
|
||||
.sorted_by(|(rule1, fix1), (rule2, fix2)| cmp_fix(*rule1, *rule2, fix1, fix2))
|
||||
{
|
||||
let mut edits = fix
|
||||
.edits()
|
||||
.iter()
|
||||
.filter(|edit| !applied.contains(edit))
|
||||
.peekable();
|
||||
// If we already applied an identical fix as part of another correction, skip
|
||||
// any re-application.
|
||||
if fix.edits().iter().all(|edit| applied.contains(edit)) {
|
||||
*fixed.entry(rule).or_default() += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the fix contains at least one new edit, enforce isolation and positional requirements.
|
||||
if let Some(first) = edits.peek() {
|
||||
// If this fix requires isolation, and we've already applied another fix in the
|
||||
// same isolation group, skip it.
|
||||
if let IsolationLevel::Group(id) = fix.isolation() {
|
||||
if !isolated.insert(id) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Best-effort approach: if this fix overlaps with a fix we've already applied,
|
||||
// skip it.
|
||||
if last_pos.map_or(false, |last_pos| {
|
||||
fix.min_start()
|
||||
.map_or(false, |fix_location| last_pos >= fix_location)
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this fix overlaps with a fix we've already applied, skip it.
|
||||
if last_pos.map_or(false, |last_pos| last_pos >= first.start()) {
|
||||
// If this fix requires isolation, and we've already applied another fix in the
|
||||
// same isolation group, skip it.
|
||||
if let IsolationLevel::Group(id) = fix.isolation() {
|
||||
if !isolated.insert(id) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let mut applied_edits = Vec::with_capacity(fix.edits().len());
|
||||
for edit in edits {
|
||||
for edit in fix
|
||||
.edits()
|
||||
.iter()
|
||||
.sorted_unstable_by_key(|edit| edit.start())
|
||||
{
|
||||
// Add all contents from `last_pos` to `fix.location`.
|
||||
let slice = locator.slice(TextRange::new(last_pos.unwrap_or_default(), edit.start()));
|
||||
output.push_str(slice);
|
||||
@@ -98,10 +103,9 @@ fn apply_fixes<'a>(
|
||||
|
||||
// Track that the edit was applied.
|
||||
last_pos = Some(edit.end());
|
||||
applied_edits.push(edit);
|
||||
applied.insert(edit);
|
||||
}
|
||||
|
||||
applied.extend(applied_edits.drain(..));
|
||||
*fixed.entry(rule).or_default() += 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use ruff_diagnostics::{Diagnostic, Fix};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::{flake8_import_conventions, flake8_pyi, pyflakes, pylint};
|
||||
use ruff_diagnostics::{Diagnostic, Fix};
|
||||
|
||||
/// Run lint rules over the [`Binding`]s.
|
||||
pub(crate) fn bindings(checker: &mut Checker) {
|
||||
@@ -11,7 +10,9 @@ pub(crate) fn bindings(checker: &mut Checker) {
|
||||
Rule::InvalidAllObject,
|
||||
Rule::UnaliasedCollectionsAbcSetImport,
|
||||
Rule::UnconventionalImportAlias,
|
||||
Rule::UnusedPrivateTypeVar,
|
||||
Rule::UnusedVariable,
|
||||
Rule::UnusedPrivateProtocol,
|
||||
]) {
|
||||
return;
|
||||
}
|
||||
@@ -64,6 +65,20 @@ pub(crate) fn bindings(checker: &mut Checker) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateTypeVar) {
|
||||
if let Some(diagnostic) =
|
||||
flake8_pyi::rules::unused_private_type_var(checker, binding)
|
||||
{
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateProtocol) {
|
||||
if let Some(diagnostic) =
|
||||
flake8_pyi::rules::unused_private_protocol(checker, binding)
|
||||
{
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use ruff_python_semantic::{Binding, BindingKind, ScopeKind};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::codes::Rule;
|
||||
use crate::rules::{flake8_pyi, flake8_type_checking, flake8_unused_arguments, pyflakes, pylint};
|
||||
use crate::rules::{flake8_type_checking, flake8_unused_arguments, pyflakes, pylint};
|
||||
|
||||
/// Run lint rules over all deferred scopes in the [`SemanticModel`].
|
||||
pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
@@ -24,10 +24,6 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
Rule::UnusedImport,
|
||||
Rule::UnusedLambdaArgument,
|
||||
Rule::UnusedMethodArgument,
|
||||
Rule::UnusedPrivateProtocol,
|
||||
Rule::UnusedPrivateTypeAlias,
|
||||
Rule::UnusedPrivateTypeVar,
|
||||
Rule::UnusedPrivateTypedDict,
|
||||
Rule::UnusedStaticMethodArgument,
|
||||
Rule::UnusedVariable,
|
||||
]) {
|
||||
@@ -218,21 +214,6 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
|
||||
if checker.is_stub {
|
||||
if checker.enabled(Rule::UnusedPrivateTypeVar) {
|
||||
flake8_pyi::rules::unused_private_type_var(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateProtocol) {
|
||||
flake8_pyi::rules::unused_private_protocol(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateTypeAlias) {
|
||||
flake8_pyi::rules::unused_private_type_alias(checker, scope, &mut diagnostics);
|
||||
}
|
||||
if checker.enabled(Rule::UnusedPrivateTypedDict) {
|
||||
flake8_pyi::rules::unused_private_typed_dict(checker, scope, &mut diagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(
|
||||
scope.kind,
|
||||
ScopeKind::Function(_) | ScopeKind::AsyncFunction(_) | ScopeKind::Lambda(_)
|
||||
|
||||
@@ -396,9 +396,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
flake8_django::rules::model_without_dunder_str(checker, class_def);
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::EqWithoutHash) {
|
||||
pylint::rules::object_without_hash_method(checker, class_def);
|
||||
}
|
||||
if checker.enabled(Rule::GlobalStatement) {
|
||||
pylint::rules::global_statement(checker, name);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use ruff_python_ast::{Expr, TypeParam};
|
||||
use ruff_python_semantic::{ScopeId, Snapshot};
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use ruff_python_semantic::{ScopeId, Snapshot};
|
||||
|
||||
/// A collection of AST nodes that are deferred for later analysis.
|
||||
/// Used to, e.g., store functions, whose bodies shouldn't be analyzed until all
|
||||
/// module-level definitions have been analyzed.
|
||||
@@ -10,7 +11,6 @@ pub(crate) struct Deferred<'a> {
|
||||
pub(crate) scopes: Vec<ScopeId>,
|
||||
pub(crate) string_type_definitions: Vec<(TextRange, &'a str, Snapshot)>,
|
||||
pub(crate) future_type_definitions: Vec<(&'a Expr, Snapshot)>,
|
||||
pub(crate) type_param_definitions: Vec<(&'a TypeParam, Snapshot)>,
|
||||
pub(crate) functions: Vec<Snapshot>,
|
||||
pub(crate) lambdas: Vec<(&'a Expr, Snapshot)>,
|
||||
pub(crate) for_loops: Vec<Snapshot>,
|
||||
|
||||
@@ -453,14 +453,12 @@ where
|
||||
args,
|
||||
decorator_list,
|
||||
returns,
|
||||
type_params,
|
||||
..
|
||||
})
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef {
|
||||
body,
|
||||
args,
|
||||
decorator_list,
|
||||
type_params,
|
||||
returns,
|
||||
..
|
||||
}) => {
|
||||
@@ -474,12 +472,6 @@ where
|
||||
// are enabled.
|
||||
let runtime_annotation = !self.semantic.future_annotations();
|
||||
|
||||
self.semantic.push_scope(ScopeKind::Type);
|
||||
|
||||
for type_param in type_params {
|
||||
self.visit_type_param(type_param);
|
||||
}
|
||||
|
||||
for arg_with_default in args
|
||||
.posonlyargs
|
||||
.iter()
|
||||
@@ -550,25 +542,18 @@ where
|
||||
bases,
|
||||
keywords,
|
||||
decorator_list,
|
||||
type_params,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
for decorator in decorator_list {
|
||||
self.visit_decorator(decorator);
|
||||
}
|
||||
|
||||
self.semantic.push_scope(ScopeKind::Type);
|
||||
|
||||
for type_param in type_params {
|
||||
self.visit_type_param(type_param);
|
||||
}
|
||||
for expr in bases {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
for keyword in keywords {
|
||||
self.visit_keyword(keyword);
|
||||
}
|
||||
for decorator in decorator_list {
|
||||
self.visit_decorator(decorator);
|
||||
}
|
||||
|
||||
let definition = docstrings::extraction::extract_definition(
|
||||
ExtractionTarget::Class,
|
||||
@@ -577,6 +562,7 @@ where
|
||||
&self.semantic.definitions,
|
||||
);
|
||||
self.semantic.push_definition(definition);
|
||||
|
||||
self.semantic.push_scope(ScopeKind::Class(class_def));
|
||||
|
||||
// Extract any global bindings from the class body.
|
||||
@@ -586,20 +572,6 @@ where
|
||||
|
||||
self.visit_body(body);
|
||||
}
|
||||
Stmt::TypeAlias(ast::StmtTypeAlias {
|
||||
range: _range,
|
||||
name,
|
||||
type_params,
|
||||
value,
|
||||
}) => {
|
||||
self.semantic.push_scope(ScopeKind::Type);
|
||||
for type_param in type_params {
|
||||
self.visit_type_param(type_param);
|
||||
}
|
||||
self.visit_expr(value);
|
||||
self.semantic.pop_scope();
|
||||
self.visit_expr(name);
|
||||
}
|
||||
Stmt::Try(ast::StmtTry {
|
||||
body,
|
||||
handlers,
|
||||
@@ -743,9 +715,8 @@ where
|
||||
| Stmt::AsyncFunctionDef(ast::StmtAsyncFunctionDef { name, .. }) => {
|
||||
let scope_id = self.semantic.scope_id;
|
||||
self.deferred.scopes.push(scope_id);
|
||||
self.semantic.pop_scope(); // Function scope
|
||||
self.semantic.pop_scope();
|
||||
self.semantic.pop_definition();
|
||||
self.semantic.pop_scope(); // Type parameter scope
|
||||
self.add_binding(
|
||||
name,
|
||||
stmt.identifier(),
|
||||
@@ -756,9 +727,8 @@ where
|
||||
Stmt::ClassDef(ast::StmtClassDef { name, .. }) => {
|
||||
let scope_id = self.semantic.scope_id;
|
||||
self.deferred.scopes.push(scope_id);
|
||||
self.semantic.pop_scope(); // Class scope
|
||||
self.semantic.pop_scope();
|
||||
self.semantic.pop_definition();
|
||||
self.semantic.pop_scope(); // Type parameter scope
|
||||
self.add_binding(
|
||||
name,
|
||||
stmt.identifier(),
|
||||
@@ -1361,26 +1331,6 @@ where
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_type_param(&mut self, type_param: &'b ast::TypeParam) {
|
||||
// Step 1: Binding
|
||||
match type_param {
|
||||
ast::TypeParam::TypeVar(ast::TypeParamTypeVar { name, range, .. })
|
||||
| ast::TypeParam::TypeVarTuple(ast::TypeParamTypeVarTuple { name, range })
|
||||
| ast::TypeParam::ParamSpec(ast::TypeParamParamSpec { name, range }) => {
|
||||
self.add_binding(
|
||||
name.as_str(),
|
||||
*range,
|
||||
BindingKind::TypeParam,
|
||||
BindingFlags::empty(),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Step 2: Traversal
|
||||
self.deferred
|
||||
.type_param_definitions
|
||||
.push((type_param, self.semantic.snapshot()));
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
@@ -1597,10 +1547,10 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
fn handle_node_load(&mut self, expr: &Expr) {
|
||||
let Expr::Name(expr) = expr else {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = expr else {
|
||||
return;
|
||||
};
|
||||
self.semantic.resolve_load(expr);
|
||||
self.semantic.resolve_load(id, expr.range());
|
||||
}
|
||||
|
||||
fn handle_node_store(&mut self, id: &'a str, expr: &Expr) {
|
||||
@@ -1743,22 +1693,6 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_deferred_type_param_definitions(&mut self) {
|
||||
while !self.deferred.type_param_definitions.is_empty() {
|
||||
let type_params = std::mem::take(&mut self.deferred.type_param_definitions);
|
||||
for (type_param, snapshot) in type_params {
|
||||
self.semantic.restore(snapshot);
|
||||
|
||||
if let ast::TypeParam::TypeVar(ast::TypeParamTypeVar {
|
||||
bound: Some(bound), ..
|
||||
}) = type_param
|
||||
{
|
||||
self.visit_expr(bound);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_deferred_string_type_definitions(&mut self, allocator: &'a typed_arena::Arena<Expr>) {
|
||||
while !self.deferred.string_type_definitions.is_empty() {
|
||||
let type_definitions = std::mem::take(&mut self.deferred.string_type_definitions);
|
||||
@@ -1948,7 +1882,6 @@ pub(crate) fn check_ast(
|
||||
checker.visit_deferred_functions();
|
||||
checker.visit_deferred_lambdas();
|
||||
checker.visit_deferred_future_type_definitions();
|
||||
checker.visit_deferred_type_param_definitions();
|
||||
let allocator = typed_arena::Arena::new();
|
||||
checker.visit_deferred_string_type_definitions(&allocator);
|
||||
checker.visit_exports();
|
||||
|
||||
@@ -9,9 +9,9 @@ use ruff_source_file::Locator;
|
||||
use crate::registry::{AsRule, Rule};
|
||||
use crate::rules::pycodestyle::rules::logical_lines::{
|
||||
extraneous_whitespace, indentation, missing_whitespace, missing_whitespace_after_keyword,
|
||||
missing_whitespace_around_operator, space_after_comma, space_around_operator,
|
||||
whitespace_around_keywords, whitespace_around_named_parameter_equals,
|
||||
whitespace_before_comment, whitespace_before_parameters, LogicalLines, TokenFlags,
|
||||
missing_whitespace_around_operator, space_around_operator, whitespace_around_keywords,
|
||||
whitespace_around_named_parameter_equals, whitespace_before_comment,
|
||||
whitespace_before_parameters, LogicalLines, TokenFlags,
|
||||
};
|
||||
use crate::settings::Settings;
|
||||
|
||||
@@ -61,9 +61,6 @@ pub(crate) fn check_logical_lines(
|
||||
missing_whitespace_around_operator(&line, &mut context);
|
||||
missing_whitespace(&line, should_fix_missing_whitespace, &mut context);
|
||||
}
|
||||
if line.flags().contains(TokenFlags::PUNCTUATION) {
|
||||
space_after_comma(&line, &mut context);
|
||||
}
|
||||
|
||||
if line
|
||||
.flags()
|
||||
|
||||
@@ -84,8 +84,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pycodestyle, "E227") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundBitwiseOrShiftOperator),
|
||||
(Pycodestyle, "E228") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundModuloOperator),
|
||||
(Pycodestyle, "E231") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespace),
|
||||
(Pycodestyle, "E241") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MultipleSpacesAfterComma),
|
||||
(Pycodestyle, "E242") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TabAfterComma),
|
||||
(Pycodestyle, "E251") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::UnexpectedSpacesAroundKeywordParameterEquals),
|
||||
(Pycodestyle, "E252") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::MissingWhitespaceAroundParameterEquals),
|
||||
(Pycodestyle, "E261") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::TooFewSpacesBeforeInlineComment),
|
||||
@@ -225,7 +223,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Pylint, "W0711") => (RuleGroup::Unspecified, rules::pylint::rules::BinaryOpException),
|
||||
(Pylint, "W1508") => (RuleGroup::Unspecified, rules::pylint::rules::InvalidEnvvarDefault),
|
||||
(Pylint, "W1509") => (RuleGroup::Unspecified, rules::pylint::rules::SubprocessPopenPreexecFn),
|
||||
(Pylint, "W1641") => (RuleGroup::Nursery, rules::pylint::rules::EqWithoutHash),
|
||||
(Pylint, "W2901") => (RuleGroup::Unspecified, rules::pylint::rules::RedefinedLoopName),
|
||||
(Pylint, "W3301") => (RuleGroup::Unspecified, rules::pylint::rules::NestedMinMax),
|
||||
|
||||
@@ -654,9 +651,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Pyi, "044") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::FutureAnnotationsInStub),
|
||||
(Flake8Pyi, "045") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::IterMethodReturnIterable),
|
||||
(Flake8Pyi, "046") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnusedPrivateProtocol),
|
||||
(Flake8Pyi, "047") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnusedPrivateTypeAlias),
|
||||
(Flake8Pyi, "048") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::StubBodyMultipleStatements),
|
||||
(Flake8Pyi, "049") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnusedPrivateTypedDict),
|
||||
(Flake8Pyi, "050") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NoReturnArgumentAnnotationInStub),
|
||||
(Flake8Pyi, "052") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnannotatedAssignmentInStub),
|
||||
(Flake8Pyi, "054") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NumericLiteralTooLong),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Display;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, BufWriter, Read, Seek, SeekFrom, Write};
|
||||
use std::iter;
|
||||
@@ -42,20 +41,6 @@ pub fn round_trip(path: &Path) -> anyhow::Result<String> {
|
||||
Ok(String::from_utf8(writer)?)
|
||||
}
|
||||
|
||||
impl Display for SourceValue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SourceValue::String(string) => f.write_str(string),
|
||||
SourceValue::StringArray(string_array) => {
|
||||
for string in string_array {
|
||||
f.write_str(string)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Cell {
|
||||
/// Return the [`SourceValue`] of the cell.
|
||||
fn source(&self) -> &SourceValue {
|
||||
@@ -422,11 +407,6 @@ impl Notebook {
|
||||
self.content = transformed.to_string();
|
||||
}
|
||||
|
||||
/// Return a slice of [`Cell`] in the Jupyter notebook.
|
||||
pub fn cells(&self) -> &[Cell] {
|
||||
&self.raw.cells
|
||||
}
|
||||
|
||||
/// Return `true` if the notebook is a Python notebook, `false` otherwise.
|
||||
pub fn is_python_notebook(&self) -> bool {
|
||||
self.raw
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter};
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use colored::{Color, ColoredString, Colorize, Styles};
|
||||
|
||||
use itertools::Itertools;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use similar::{ChangeTag, TextDiff};
|
||||
|
||||
@@ -38,7 +38,12 @@ impl Display for Diff<'_> {
|
||||
let mut output = String::with_capacity(self.source_code.source_text().len());
|
||||
let mut last_end = TextSize::default();
|
||||
|
||||
for edit in self.fix.edits() {
|
||||
for edit in self
|
||||
.fix
|
||||
.edits()
|
||||
.iter()
|
||||
.sorted_unstable_by_key(|edit| edit.start())
|
||||
{
|
||||
output.push_str(
|
||||
self.source_code
|
||||
.slice(TextRange::new(last_end, edit.start())),
|
||||
|
||||
@@ -12,7 +12,6 @@ use ruff_python_ast::Ranged;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_python_trivia::indentation_at_offset;
|
||||
use ruff_source_file::{LineEnding, Locator};
|
||||
|
||||
use crate::codes::NoqaCode;
|
||||
@@ -249,34 +248,22 @@ impl FileExemption {
|
||||
let path_display = relativize_path(path);
|
||||
warn!("Invalid `# ruff: noqa` directive at {path_display}:{line}: {err}");
|
||||
}
|
||||
Ok(Some(exemption)) => {
|
||||
if indentation_at_offset(range.start(), locator).is_none() {
|
||||
#[allow(deprecated)]
|
||||
let line = locator.compute_line_index(range.start());
|
||||
let path_display = relativize_path(path);
|
||||
warn!("Unexpected `# ruff: noqa` directive at {path_display}:{line}. File-level suppression comments must appear on their own line.");
|
||||
continue;
|
||||
}
|
||||
|
||||
match exemption {
|
||||
ParsedFileExemption::All => {
|
||||
return Some(Self::All);
|
||||
Ok(Some(ParsedFileExemption::All)) => {
|
||||
return Some(Self::All);
|
||||
}
|
||||
Ok(Some(ParsedFileExemption::Codes(codes))) => {
|
||||
exempt_codes.extend(codes.into_iter().filter_map(|code| {
|
||||
if let Ok(rule) = Rule::from_code(get_redirect_target(code).unwrap_or(code))
|
||||
{
|
||||
Some(rule.noqa_code())
|
||||
} else {
|
||||
#[allow(deprecated)]
|
||||
let line = locator.compute_line_index(range.start());
|
||||
let path_display = relativize_path(path);
|
||||
warn!("Invalid code provided to `# ruff: noqa` at {path_display}:{line}: {code}");
|
||||
None
|
||||
}
|
||||
ParsedFileExemption::Codes(codes) => {
|
||||
exempt_codes.extend(codes.into_iter().filter_map(|code| {
|
||||
if let Ok(rule) = Rule::from_code(get_redirect_target(code).unwrap_or(code))
|
||||
{
|
||||
Some(rule.noqa_code())
|
||||
} else {
|
||||
#[allow(deprecated)]
|
||||
let line = locator.compute_line_index(range.start());
|
||||
let path_display = relativize_path(path);
|
||||
warn!("Invalid rule code provided to `# ruff: noqa` at {path_display}:{line}: {code}");
|
||||
None
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
Ok(None) => {}
|
||||
}
|
||||
|
||||
@@ -303,7 +303,6 @@ impl Rule {
|
||||
| Rule::MissingWhitespaceAroundOperator
|
||||
| Rule::MissingWhitespaceAroundParameterEquals
|
||||
| Rule::MultipleLeadingHashesForBlockComment
|
||||
| Rule::MultipleSpacesAfterComma
|
||||
| Rule::MultipleSpacesAfterKeyword
|
||||
| Rule::MultipleSpacesAfterOperator
|
||||
| Rule::MultipleSpacesBeforeKeyword
|
||||
@@ -313,7 +312,6 @@ impl Rule {
|
||||
| Rule::NoSpaceAfterBlockComment
|
||||
| Rule::NoSpaceAfterInlineComment
|
||||
| Rule::OverIndented
|
||||
| Rule::TabAfterComma
|
||||
| Rule::TabAfterKeyword
|
||||
| Rule::TabAfterOperator
|
||||
| Rule::TabBeforeKeyword
|
||||
|
||||
@@ -242,7 +242,6 @@ impl Renamer {
|
||||
// By default, replace the binding's name with the target name.
|
||||
BindingKind::Annotation
|
||||
| BindingKind::Argument
|
||||
| BindingKind::TypeParam
|
||||
| BindingKind::NamedExprAssignment
|
||||
| BindingKind::UnpackedAssignment
|
||||
| BindingKind::Assignment
|
||||
|
||||
@@ -6,35 +6,6 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `jinja2` templates that use `autoescape=False`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `jinja2` templates that use `autoescape=False` are vulnerable to cross-site
|
||||
/// scripting (XSS) attacks that allow attackers to execute arbitrary
|
||||
/// JavaScript.
|
||||
///
|
||||
/// By default, `jinja2` sets `autoescape` to `False`, so it is important to
|
||||
/// set `autoescape=True` or use the `select_autoescape` function to mitigate
|
||||
/// XSS vulnerabilities.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import jinja2
|
||||
///
|
||||
/// jinja2.Environment(loader=jinja2.FileSystemLoader("."))
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import jinja2
|
||||
///
|
||||
/// jinja2.Environment(loader=jinja2.FileSystemLoader("."), autoescape=True)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Jinja documentation: API](https://jinja.palletsprojects.com/en/latest/api/#autoescaping)
|
||||
/// - [Common Weakness Enumeration: CWE-94](https://cwe.mitre.org/data/definitions/94.html)
|
||||
#[violation]
|
||||
pub struct Jinja2AutoescapeFalse {
|
||||
value: bool,
|
||||
|
||||
@@ -6,24 +6,6 @@ use ruff_python_ast::helpers::find_keyword;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for insecure `logging.config.listen` calls.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `logging.config.listen` starts a server that listens for logging
|
||||
/// configuration requests. This is insecure as parts of the configuration are
|
||||
/// passed to the built-in `eval` function, which can be used to execute
|
||||
/// arbitrary code.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import logging
|
||||
///
|
||||
/// logging.config.listen(9999)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `logging.config.listen()`](https://docs.python.org/3/library/logging.config.html#logging.config.listen)
|
||||
#[violation]
|
||||
pub struct LoggingConfigInsecureListen;
|
||||
|
||||
|
||||
@@ -5,25 +5,6 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `paramiko` calls.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// `paramiko` calls allow users to execute arbitrary shell commands on a
|
||||
/// remote machine. If the inputs to these calls are not properly sanitized,
|
||||
/// they can be vulnerable to shell injection attacks.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import paramiko
|
||||
///
|
||||
/// client = paramiko.SSHClient()
|
||||
/// client.exec_command("echo $HOME")
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Common Weakness Enumeration: CWE-78](https://cwe.mitre.org/data/definitions/78.html)
|
||||
/// - [Paramiko documentation: `SSHClient.exec_command()`](https://docs.paramiko.org/en/stable/api/client.html#paramiko.client.SSHClient.exec_command)
|
||||
#[violation]
|
||||
pub struct ParamikoCall;
|
||||
|
||||
|
||||
@@ -75,31 +75,6 @@ impl Violation for StartProcessWithNoShell {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the starting of a process with a partial executable path.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Starting a process with a partial executable path can allow attackers to
|
||||
/// execute arbitrary executable by adjusting the `PATH` environment variable.
|
||||
/// Consider using a full path to the executable instead.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import subprocess
|
||||
///
|
||||
/// subprocess.Popen(["ruff", "check", "file.py"])
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import subprocess
|
||||
///
|
||||
/// subprocess.Popen(["/usr/bin/ruff", "check", "file.py"])
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `subprocess.Popen()`](https://docs.python.org/3/library/subprocess.html#subprocess.Popen)
|
||||
/// - [Common Weakness Enumeration: CWE-78](https://cwe.mitre.org/data/definitions/78.html)
|
||||
#[violation]
|
||||
pub struct StartProcessWithPartialPath;
|
||||
|
||||
@@ -110,29 +85,6 @@ impl Violation for StartProcessWithPartialPath {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for possible wildcard injections in calls to `subprocess.Popen()`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Wildcard injections can lead to unexpected behavior if unintended files are
|
||||
/// matched by the wildcard. Consider using a more specific path instead.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import subprocess
|
||||
///
|
||||
/// subprocess.Popen(["chmod", "777", "*.py"])
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import subprocess
|
||||
///
|
||||
/// subprocess.Popen(["chmod", "777", "main.py"])
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Common Weakness Enumeration: CWE-78](https://cwe.mitre.org/data/definitions/78.html)
|
||||
#[violation]
|
||||
pub struct UnixCommandWildcardInjection;
|
||||
|
||||
|
||||
@@ -97,10 +97,6 @@ mod tests {
|
||||
#[test_case(Rule::UnusedPrivateTypeVar, Path::new("PYI018.pyi"))]
|
||||
#[test_case(Rule::UnusedPrivateProtocol, Path::new("PYI046.py"))]
|
||||
#[test_case(Rule::UnusedPrivateProtocol, Path::new("PYI046.pyi"))]
|
||||
#[test_case(Rule::UnusedPrivateTypeAlias, Path::new("PYI047.py"))]
|
||||
#[test_case(Rule::UnusedPrivateTypeAlias, Path::new("PYI047.pyi"))]
|
||||
#[test_case(Rule::UnusedPrivateTypedDict, Path::new("PYI049.py"))]
|
||||
#[test_case(Rule::UnusedPrivateTypedDict, Path::new("PYI049.pyi"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr, Stmt};
|
||||
use ruff_python_semantic::Scope;
|
||||
use ruff_python_semantic::Binding;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -73,255 +73,68 @@ impl Violation for UnusedPrivateProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the presence of unused private `typing.TypeAlias` definitions.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// A private `typing.TypeAlias` that is defined but not used is likely a
|
||||
/// mistake, and should either be used, made public, or removed to avoid
|
||||
/// confusion.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import typing
|
||||
///
|
||||
/// _UnusedTypeAlias: typing.TypeAlias = int
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import typing
|
||||
///
|
||||
/// _UsedTypeAlias: typing.TypeAlias = int
|
||||
///
|
||||
///
|
||||
/// def func(arg: _UsedTypeAlias) -> _UsedTypeAlias:
|
||||
/// ...
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct UnusedPrivateTypeAlias {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for UnusedPrivateTypeAlias {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let UnusedPrivateTypeAlias { name } = self;
|
||||
format!("Private TypeAlias `{name}` is never used")
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the presence of unused private `typing.TypedDict` definitions.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// A private `typing.TypedDict` that is defined but not used is likely a
|
||||
/// mistake, and should either be used, made public, or removed to avoid
|
||||
/// confusion.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import typing
|
||||
///
|
||||
///
|
||||
/// class _UnusedPrivateTypedDict(typing.TypedDict):
|
||||
/// foo: list[int]
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// import typing
|
||||
///
|
||||
///
|
||||
/// class _UsedPrivateTypedDict(typing.TypedDict):
|
||||
/// foo: set[str]
|
||||
///
|
||||
///
|
||||
/// def func(arg: _UsedPrivateTypedDict) -> _UsedPrivateTypedDict:
|
||||
/// ...
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct UnusedPrivateTypedDict {
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Violation for UnusedPrivateTypedDict {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let UnusedPrivateTypedDict { name } = self;
|
||||
format!("Private TypedDict `{name}` is never used")
|
||||
}
|
||||
}
|
||||
|
||||
/// PYI018
|
||||
pub(crate) fn unused_private_type_var(
|
||||
checker: &Checker,
|
||||
scope: &Scope,
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
) {
|
||||
for binding in scope
|
||||
.binding_ids()
|
||||
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||
{
|
||||
if !(binding.kind.is_assignment() && binding.is_private_declaration()) {
|
||||
continue;
|
||||
}
|
||||
if binding.is_used() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(source) = binding.source else {
|
||||
continue;
|
||||
};
|
||||
let Stmt::Assign(ast::StmtAssign { targets, value, .. }) = checker.semantic().stmts[source]
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let [Expr::Name(ast::ExprName { id, .. })] = &targets[..] else {
|
||||
continue;
|
||||
};
|
||||
let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
if !checker.semantic().match_typing_expr(func, "TypeVar") {
|
||||
continue;
|
||||
}
|
||||
|
||||
diagnostics.push(Diagnostic::new(
|
||||
UnusedPrivateTypeVar {
|
||||
name: id.to_string(),
|
||||
},
|
||||
binding.range,
|
||||
));
|
||||
pub(crate) fn unused_private_type_var(checker: &Checker, binding: &Binding) -> Option<Diagnostic> {
|
||||
if !(binding.kind.is_assignment() && binding.is_private_declaration()) {
|
||||
return None;
|
||||
}
|
||||
if binding.is_used() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let Some(source) = binding.source else {
|
||||
return None;
|
||||
};
|
||||
let Stmt::Assign(ast::StmtAssign { targets, value, .. }) = checker.semantic().stmts[source]
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
let [Expr::Name(ast::ExprName { id, .. })] = &targets[..] else {
|
||||
return None;
|
||||
};
|
||||
let Expr::Call(ast::ExprCall { func, .. }) = value.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
if !checker.semantic().match_typing_expr(func, "TypeVar") {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Diagnostic::new(
|
||||
UnusedPrivateTypeVar {
|
||||
name: id.to_string(),
|
||||
},
|
||||
binding.range,
|
||||
))
|
||||
}
|
||||
|
||||
/// PYI046
|
||||
pub(crate) fn unused_private_protocol(
|
||||
checker: &Checker,
|
||||
scope: &Scope,
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
) {
|
||||
for binding in scope
|
||||
.binding_ids()
|
||||
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||
{
|
||||
if !(binding.kind.is_class_definition() && binding.is_private_declaration()) {
|
||||
continue;
|
||||
}
|
||||
if binding.is_used() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(source) = binding.source else {
|
||||
continue;
|
||||
};
|
||||
let Stmt::ClassDef(ast::StmtClassDef { name, bases, .. }) =
|
||||
checker.semantic().stmts[source]
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !bases
|
||||
.iter()
|
||||
.any(|base| checker.semantic().match_typing_expr(base, "Protocol"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
diagnostics.push(Diagnostic::new(
|
||||
UnusedPrivateProtocol {
|
||||
name: name.to_string(),
|
||||
},
|
||||
binding.range,
|
||||
));
|
||||
pub(crate) fn unused_private_protocol(checker: &Checker, binding: &Binding) -> Option<Diagnostic> {
|
||||
if !(binding.kind.is_class_definition() && binding.is_private_declaration()) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
/// PYI047
|
||||
pub(crate) fn unused_private_type_alias(
|
||||
checker: &Checker,
|
||||
scope: &Scope,
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
) {
|
||||
for binding in scope
|
||||
.binding_ids()
|
||||
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||
{
|
||||
if !(binding.kind.is_assignment() && binding.is_private_declaration()) {
|
||||
continue;
|
||||
}
|
||||
if binding.is_used() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(source) = binding.source else {
|
||||
continue;
|
||||
};
|
||||
let Stmt::AnnAssign(ast::StmtAnnAssign {
|
||||
target, annotation, ..
|
||||
}) = checker.semantic().stmts[source]
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(ast::ExprName { id, .. }) = target.as_name_expr() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !checker
|
||||
.semantic()
|
||||
.match_typing_expr(annotation, "TypeAlias")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
diagnostics.push(Diagnostic::new(
|
||||
UnusedPrivateTypeAlias {
|
||||
name: id.to_string(),
|
||||
},
|
||||
binding.range,
|
||||
));
|
||||
if binding.is_used() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
/// PYI049
|
||||
pub(crate) fn unused_private_typed_dict(
|
||||
checker: &Checker,
|
||||
scope: &Scope,
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
) {
|
||||
for binding in scope
|
||||
.binding_ids()
|
||||
.map(|binding_id| checker.semantic().binding(binding_id))
|
||||
let Some(source) = binding.source else {
|
||||
return None;
|
||||
};
|
||||
let Stmt::ClassDef(ast::StmtClassDef { name, bases, .. }) = checker.semantic().stmts[source]
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if !bases
|
||||
.iter()
|
||||
.any(|base| checker.semantic().match_typing_expr(base, "Protocol"))
|
||||
{
|
||||
if !(binding.kind.is_class_definition() && binding.is_private_declaration()) {
|
||||
continue;
|
||||
}
|
||||
if binding.is_used() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(source) = binding.source else {
|
||||
continue;
|
||||
};
|
||||
let Stmt::ClassDef(ast::StmtClassDef { name, bases, .. }) =
|
||||
checker.semantic().stmts[source]
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !bases
|
||||
.iter()
|
||||
.any(|base| checker.semantic().match_typing_expr(base, "TypedDict"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
diagnostics.push(Diagnostic::new(
|
||||
UnusedPrivateTypedDict {
|
||||
name: name.to_string(),
|
||||
},
|
||||
binding.range,
|
||||
));
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Diagnostic::new(
|
||||
UnusedPrivateProtocol {
|
||||
name: name.to_string(),
|
||||
},
|
||||
binding.range,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
PYI047.pyi:6:1: PYI047 Private TypeAlias `_UnusedPrivateTypeAlias` is never used
|
||||
|
|
||||
6 | _UnusedPrivateTypeAlias: TypeAlias = int | None
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ PYI047
|
||||
7 | _T: typing.TypeAlias = str
|
||||
|
|
||||
|
||||
PYI047.pyi:7:1: PYI047 Private TypeAlias `_T` is never used
|
||||
|
|
||||
6 | _UnusedPrivateTypeAlias: TypeAlias = int | None
|
||||
7 | _T: typing.TypeAlias = str
|
||||
| ^^ PYI047
|
||||
8 |
|
||||
9 | # OK
|
||||
|
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
PYI049.pyi:6:7: PYI049 Private TypedDict `_UnusedTypedDict` is never used
|
||||
|
|
||||
6 | class _UnusedTypedDict(TypedDict):
|
||||
| ^^^^^^^^^^^^^^^^ PYI049
|
||||
7 | foo: str
|
||||
|
|
||||
|
||||
PYI049.pyi:10:7: PYI049 Private TypedDict `_UnusedTypedDict2` is never used
|
||||
|
|
||||
10 | class _UnusedTypedDict2(typing.TypedDict):
|
||||
| ^^^^^^^^^^^^^^^^^ PYI049
|
||||
11 | bar: int
|
||||
|
|
||||
|
||||
|
||||
@@ -69,34 +69,6 @@ impl Violation for PytestCompositeAssertion {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `assert` statements in `except` clauses.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// When testing for exceptions, `pytest.raises()` should be used instead of
|
||||
/// `assert` statements in `except` clauses, as it's more explicit and
|
||||
/// idiomatic. Further, `pytest.raises()` will fail if the exception is _not_
|
||||
/// raised, unlike the `assert` statement.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def test_foo():
|
||||
/// try:
|
||||
/// 1 / 0
|
||||
/// except ZeroDivisionError as e:
|
||||
/// assert e.args
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// def test_foo():
|
||||
/// with pytest.raises(ZeroDivisionError) as exc_info:
|
||||
/// 1 / 0
|
||||
/// assert exc_info.value.args
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [API Reference: `pytest.raises`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-raises)
|
||||
#[violation]
|
||||
pub struct PytestAssertInExcept {
|
||||
name: String,
|
||||
|
||||
@@ -129,47 +129,6 @@ impl Violation for PytestIncorrectFixtureNameUnderscore {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `pytest` test functions that should be decorated with
|
||||
/// `@pytest.mark.usefixtures`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// In `pytest`, fixture injection is used to activate fixtures in a test
|
||||
/// function.
|
||||
///
|
||||
/// Fixtures can be injected either by passing them as parameters to the test
|
||||
/// function, or by using the `@pytest.mark.usefixtures` decorator.
|
||||
///
|
||||
/// If the test function depends on the fixture being activated, but does not
|
||||
/// use it in the test body or otherwise rely on its return value, prefer
|
||||
/// the `@pytest.mark.usefixtures` decorator, to make the dependency explicit
|
||||
/// and avoid the confusion caused by unused arguments.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// @pytest.fixture
|
||||
/// def _patch_something():
|
||||
/// ...
|
||||
///
|
||||
///
|
||||
/// def test_foo(_patch_something):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// @pytest.fixture
|
||||
/// def _patch_something():
|
||||
/// ...
|
||||
///
|
||||
///
|
||||
/// @pytest.mark.usefixtures("_patch_something")
|
||||
/// def test_foo():
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [API Reference: `pytest.mark.usefixtures`](https://docs.pytest.org/en/latest/reference/reference.html#pytest-mark-usefixtures)
|
||||
#[violation]
|
||||
pub struct PytestFixtureParamWithoutValue {
|
||||
name: String,
|
||||
|
||||
@@ -82,25 +82,13 @@ fn key_in_dict(
|
||||
return;
|
||||
}
|
||||
|
||||
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func.as_ref() else {
|
||||
let Expr::Attribute(ast::ExprAttribute { attr, .. }) = func.as_ref() else {
|
||||
return;
|
||||
};
|
||||
if attr != "keys" {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore `self.keys()`, which will almost certainly be intentional, as in:
|
||||
// ```python
|
||||
// def __contains__(self, key: object) -> bool:
|
||||
// return key in self.keys()
|
||||
// ```
|
||||
if value
|
||||
.as_name_expr()
|
||||
.map_or(false, |name| matches!(name.id.as_str(), "self"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Slice exact content to preserve formatting.
|
||||
let left_content = checker.locator().slice(left.range());
|
||||
let Ok(value_content) =
|
||||
|
||||
@@ -105,32 +105,13 @@ fn match_exit_stack(semantic: &SemanticModel) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Return `true` if `func` is the builtin `open` or `pathlib.Path(...).open`.
|
||||
fn is_open(checker: &mut Checker, func: &Expr) -> bool {
|
||||
match func {
|
||||
// pathlib.Path(...).open()
|
||||
Expr::Attribute(ast::ExprAttribute { attr, value, .. }) if attr.as_str() == "open" => {
|
||||
match value.as_ref() {
|
||||
Expr::Call(ast::ExprCall { func, .. }) => checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| {
|
||||
matches!(call_path.as_slice(), ["pathlib", "Path"])
|
||||
}),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
// open(...)
|
||||
Expr::Name(ast::ExprName { id, .. }) => {
|
||||
id.as_str() == "open" && checker.semantic().is_builtin("open")
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// SIM115
|
||||
pub(crate) fn open_file_with_context_handler(checker: &mut Checker, func: &Expr) {
|
||||
if !is_open(checker, func) {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = func else {
|
||||
return;
|
||||
};
|
||||
|
||||
if id.as_str() != "open" {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -139,6 +120,10 @@ pub(crate) fn open_file_with_context_handler(checker: &mut Checker, func: &Expr)
|
||||
return;
|
||||
}
|
||||
|
||||
if !checker.semantic().is_builtin("open") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ex) `with contextlib.ExitStack() as exit_stack: ...`
|
||||
if match_exit_stack(checker.semantic()) {
|
||||
return;
|
||||
|
||||
@@ -1,63 +1,23 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_simplify/mod.rs
|
||||
---
|
||||
SIM115.py:8:5: SIM115 Use context handler for opening files
|
||||
|
|
||||
7 | # SIM115
|
||||
8 | f = open("foo.txt")
|
||||
| ^^^^ SIM115
|
||||
9 | f = Path("foo.txt").open()
|
||||
10 | f = pathlib.Path("foo.txt").open()
|
||||
|
|
||||
SIM115.py:4:5: SIM115 Use context handler for opening files
|
||||
|
|
||||
3 | # SIM115
|
||||
4 | f = open("foo.txt")
|
||||
| ^^^^ SIM115
|
||||
5 | data = f.read()
|
||||
6 | f.close()
|
||||
|
|
||||
|
||||
SIM115.py:9:5: SIM115 Use context handler for opening files
|
||||
SIM115.py:31:9: SIM115 Use context handler for opening files
|
||||
|
|
||||
7 | # SIM115
|
||||
8 | f = open("foo.txt")
|
||||
9 | f = Path("foo.txt").open()
|
||||
| ^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
10 | f = pathlib.Path("foo.txt").open()
|
||||
11 | f = pl.Path("foo.txt").open()
|
||||
|
|
||||
|
||||
SIM115.py:10:5: SIM115 Use context handler for opening files
|
||||
|
|
||||
8 | f = open("foo.txt")
|
||||
9 | f = Path("foo.txt").open()
|
||||
10 | f = pathlib.Path("foo.txt").open()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
11 | f = pl.Path("foo.txt").open()
|
||||
12 | f = P("foo.txt").open()
|
||||
|
|
||||
|
||||
SIM115.py:11:5: SIM115 Use context handler for opening files
|
||||
|
|
||||
9 | f = Path("foo.txt").open()
|
||||
10 | f = pathlib.Path("foo.txt").open()
|
||||
11 | f = pl.Path("foo.txt").open()
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^ SIM115
|
||||
12 | f = P("foo.txt").open()
|
||||
13 | data = f.read()
|
||||
|
|
||||
|
||||
SIM115.py:12:5: SIM115 Use context handler for opening files
|
||||
|
|
||||
10 | f = pathlib.Path("foo.txt").open()
|
||||
11 | f = pl.Path("foo.txt").open()
|
||||
12 | f = P("foo.txt").open()
|
||||
| ^^^^^^^^^^^^^^^^^ SIM115
|
||||
13 | data = f.read()
|
||||
14 | f.close()
|
||||
|
|
||||
|
||||
SIM115.py:39:9: SIM115 Use context handler for opening files
|
||||
|
|
||||
37 | # SIM115
|
||||
38 | with contextlib.ExitStack():
|
||||
39 | f = open("filename")
|
||||
29 | # SIM115
|
||||
30 | with contextlib.ExitStack():
|
||||
31 | f = open("filename")
|
||||
| ^^^^ SIM115
|
||||
40 |
|
||||
41 | # OK
|
||||
32 |
|
||||
33 | # OK
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -265,7 +265,6 @@ SIM118.py:30:8: SIM118 [*] Use `k in obj` instead of `k in obj.keys()`
|
||||
30 |+(k for k in obj) # SIM118
|
||||
31 31 |
|
||||
32 32 | key in (obj or {}).keys() # SIM118
|
||||
33 33 |
|
||||
|
||||
SIM118.py:32:1: SIM118 [*] Use `key in (obj or {})` instead of `key in (obj or {}).keys()`
|
||||
|
|
||||
@@ -273,8 +272,6 @@ SIM118.py:32:1: SIM118 [*] Use `key in (obj or {})` instead of `key in (obj or {
|
||||
31 |
|
||||
32 | key in (obj or {}).keys() # SIM118
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ SIM118
|
||||
33 |
|
||||
34 | from typing import KeysView
|
||||
|
|
||||
= help: Convert to `key in (obj or {})`
|
||||
|
||||
@@ -284,8 +281,5 @@ SIM118.py:32:1: SIM118 [*] Use `key in (obj or {})` instead of `key in (obj or {
|
||||
31 31 |
|
||||
32 |-key in (obj or {}).keys() # SIM118
|
||||
32 |+key in (obj or {}) # SIM118
|
||||
33 33 |
|
||||
34 34 | from typing import KeysView
|
||||
35 35 |
|
||||
|
||||
|
||||
|
||||
@@ -317,7 +317,10 @@ pub(crate) fn unused_arguments(
|
||||
scope: &Scope,
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
) {
|
||||
let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else {
|
||||
let Some(parent) = scope
|
||||
.parent
|
||||
.map(|scope_id| &checker.semantic().scopes[scope_id])
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -9,24 +9,6 @@ use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
use crate::rules::flynt::helpers;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `str#join` calls that can be replaced with f-strings.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// f-strings are more readable and generally preferred over `str#join` calls.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// " ".join((foo, bar))
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// f"{foo} {bar}"
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: f-strings](https://docs.python.org/3/reference/lexical_analysis.html#f-strings)
|
||||
#[violation]
|
||||
pub struct StaticJoinToFString {
|
||||
expr: String,
|
||||
|
||||
@@ -314,8 +314,6 @@ mod tests {
|
||||
|
||||
#[test_case(Path::new("add_newline_before_comments.py"))]
|
||||
#[test_case(Path::new("as_imports_comments.py"))]
|
||||
#[test_case(Path::new("bom_sorted.py"))]
|
||||
#[test_case(Path::new("bom_unsorted.py"))]
|
||||
#[test_case(Path::new("combine_as_imports.py"))]
|
||||
#[test_case(Path::new("combine_import_from.py"))]
|
||||
#[test_case(Path::new("comments.py"))]
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/isort/mod.rs
|
||||
---
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/isort/mod.rs
|
||||
---
|
||||
bom_unsorted.py:1:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
|
|
||||
1 | import foo
|
||||
| _^
|
||||
2 | | import bar
|
||||
|
|
||||
= help: Organize imports
|
||||
|
||||
ℹ Fix
|
||||
1 |-import foo
|
||||
2 |-import bar
|
||||
1 |+import bar
|
||||
2 |+import foo
|
||||
|
||||
|
||||
@@ -9,6 +9,21 @@ use ruff_macros::{CacheKey, CombineOptions, ConfigurationOptions};
|
||||
|
||||
use crate::settings::types::IdentifierPattern;
|
||||
|
||||
const IGNORE_NAMES: [&str; 12] = [
|
||||
"setUp",
|
||||
"tearDown",
|
||||
"setUpClass",
|
||||
"tearDownClass",
|
||||
"setUpModule",
|
||||
"tearDownModule",
|
||||
"asyncSetUp",
|
||||
"asyncTearDown",
|
||||
"setUpTestData",
|
||||
"failureException",
|
||||
"longMessage",
|
||||
"maxDiff",
|
||||
];
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions, CombineOptions,
|
||||
)]
|
||||
@@ -28,14 +43,6 @@ pub struct Options {
|
||||
)]
|
||||
/// A list of names (or patterns) to ignore when considering `pep8-naming` violations.
|
||||
pub ignore_names: Option<Vec<String>>,
|
||||
#[option(
|
||||
default = r#"[]"#,
|
||||
value_type = "list[str]",
|
||||
example = r#"extend-ignore-names = ["callMethod"]"#
|
||||
)]
|
||||
/// Additional names (or patterns) to ignore when considering `pep8-naming` violations,
|
||||
/// in addition to those included in `ignore-names`.
|
||||
pub extend_ignore_names: Option<Vec<String>>,
|
||||
#[option(
|
||||
default = r#"[]"#,
|
||||
value_type = "list[str]",
|
||||
@@ -75,29 +82,12 @@ pub struct Settings {
|
||||
pub staticmethod_decorators: Vec<String>,
|
||||
}
|
||||
|
||||
fn default_ignore_names() -> Vec<String> {
|
||||
vec![
|
||||
"setUp".to_string(),
|
||||
"tearDown".to_string(),
|
||||
"setUpClass".to_string(),
|
||||
"tearDownClass".to_string(),
|
||||
"setUpModule".to_string(),
|
||||
"tearDownModule".to_string(),
|
||||
"asyncSetUp".to_string(),
|
||||
"asyncTearDown".to_string(),
|
||||
"setUpTestData".to_string(),
|
||||
"failureException".to_string(),
|
||||
"longMessage".to_string(),
|
||||
"maxDiff".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ignore_names: default_ignore_names()
|
||||
.into_iter()
|
||||
.map(|name| IdentifierPattern::new(&name).unwrap())
|
||||
ignore_names: IGNORE_NAMES
|
||||
.iter()
|
||||
.map(|name| IdentifierPattern::new(name).unwrap())
|
||||
.collect(),
|
||||
classmethod_decorators: Vec::new(),
|
||||
staticmethod_decorators: Vec::new(),
|
||||
@@ -110,13 +100,18 @@ impl TryFrom<Options> for Settings {
|
||||
|
||||
fn try_from(options: Options) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
ignore_names: options
|
||||
.ignore_names
|
||||
.unwrap_or_else(default_ignore_names)
|
||||
.into_iter()
|
||||
.chain(options.extend_ignore_names.unwrap_or_default().into_iter())
|
||||
.map(|name| IdentifierPattern::new(&name).map_err(SettingsError::InvalidIgnoreName))
|
||||
.collect::<Result<Vec<_>, Self::Error>>()?,
|
||||
ignore_names: match options.ignore_names {
|
||||
Some(names) => names
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
IdentifierPattern::new(&name).map_err(SettingsError::InvalidIgnoreName)
|
||||
})
|
||||
.collect::<Result<Vec<_>, Self::Error>>()?,
|
||||
None => IGNORE_NAMES
|
||||
.into_iter()
|
||||
.map(|name| IdentifierPattern::new(name).unwrap())
|
||||
.collect(),
|
||||
},
|
||||
classmethod_decorators: options.classmethod_decorators.unwrap_or_default(),
|
||||
staticmethod_decorators: options.staticmethod_decorators.unwrap_or_default(),
|
||||
})
|
||||
@@ -157,7 +152,6 @@ impl From<Settings> for Options {
|
||||
.map(|pattern| pattern.as_str().to_owned())
|
||||
.collect(),
|
||||
),
|
||||
extend_ignore_names: None,
|
||||
classmethod_decorators: Some(settings.classmethod_decorators),
|
||||
staticmethod_decorators: Some(settings.staticmethod_decorators),
|
||||
}
|
||||
|
||||
@@ -67,15 +67,14 @@ pub(crate) fn try_except_in_loop(checker: &mut Checker, body: &[Stmt]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let [Stmt::Try(ast::StmtTry { handlers, .. })] = body else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(handler) = handlers.first() else {
|
||||
return;
|
||||
};
|
||||
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(TryExceptInLoop, handler.range()));
|
||||
checker.diagnostics.extend(body.iter().filter_map(|stmt| {
|
||||
if let Stmt::Try(ast::StmtTry { handlers, .. }) = stmt {
|
||||
handlers
|
||||
.iter()
|
||||
.next()
|
||||
.map(|handler| Diagnostic::new(TryExceptInLoop, handler.range()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/perflint/mod.rs
|
||||
---
|
||||
PERF203.py:5:5: PERF203 `try`-`except` within a loop incurs performance overhead
|
||||
PERF203.py:4:5: PERF203 `try`-`except` within a loop incurs performance overhead
|
||||
|
|
||||
3 | try:
|
||||
4 | print(f"{i}")
|
||||
5 | except:
|
||||
2 | try: # PERF203
|
||||
3 | print(f"{i}")
|
||||
4 | except:
|
||||
| _____^
|
||||
6 | | print("error")
|
||||
5 | | print("error")
|
||||
| |______________________^ PERF203
|
||||
7 |
|
||||
8 | # OK
|
||||
6 |
|
||||
7 | try:
|
||||
|
|
||||
|
||||
PERF203.py:17:5: PERF203 `try`-`except` within a loop incurs performance overhead
|
||||
|
|
||||
15 | try:
|
||||
16 | print(f"{i}")
|
||||
17 | except:
|
||||
| _____^
|
||||
18 | | print("error")
|
||||
| |______________________^ PERF203
|
||||
19 |
|
||||
20 | i += 1
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -70,7 +70,6 @@ mod tests {
|
||||
#[test_case(Rule::IndentationWithInvalidMultiple, Path::new("E11.py"))]
|
||||
#[test_case(Rule::IndentationWithInvalidMultipleComment, Path::new("E11.py"))]
|
||||
#[test_case(Rule::MultipleLeadingHashesForBlockComment, Path::new("E26.py"))]
|
||||
#[test_case(Rule::MultipleSpacesAfterComma, Path::new("E24.py"))]
|
||||
#[test_case(Rule::MultipleSpacesAfterKeyword, Path::new("E27.py"))]
|
||||
#[test_case(Rule::MultipleSpacesAfterOperator, Path::new("E22.py"))]
|
||||
#[test_case(Rule::MultipleSpacesBeforeKeyword, Path::new("E27.py"))]
|
||||
@@ -81,7 +80,6 @@ mod tests {
|
||||
#[test_case(Rule::NoSpaceAfterBlockComment, Path::new("E26.py"))]
|
||||
#[test_case(Rule::NoSpaceAfterInlineComment, Path::new("E26.py"))]
|
||||
#[test_case(Rule::OverIndented, Path::new("E11.py"))]
|
||||
#[test_case(Rule::TabAfterComma, Path::new("E24.py"))]
|
||||
#[test_case(Rule::TabAfterKeyword, Path::new("E27.py"))]
|
||||
#[test_case(Rule::TabAfterOperator, Path::new("E22.py"))]
|
||||
#[test_case(Rule::TabBeforeKeyword, Path::new("E27.py"))]
|
||||
|
||||
@@ -5,26 +5,6 @@ use ruff_python_parser::TokenKind;
|
||||
use crate::checkers::logical_lines::LogicalLinesContext;
|
||||
use crate::rules::pycodestyle::rules::logical_lines::LogicalLine;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for missing whitespace around all operators.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// According to [PEP 8], there should be one space before and after all
|
||||
/// operators.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// if number==42:
|
||||
/// print('you have found the meaning of life')
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// if number == 42:
|
||||
/// print('you have found the meaning of life')
|
||||
/// ```
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/#pet-peeves
|
||||
// E225
|
||||
#[violation]
|
||||
pub struct MissingWhitespaceAroundOperator;
|
||||
@@ -36,24 +16,6 @@ impl Violation for MissingWhitespaceAroundOperator {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for missing whitespace arithmetic operators.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// According to [PEP 8], there should be one space before and after an
|
||||
/// arithmetic operator (+, -, /, and *).
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// number = 40+2
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// number = 40 + 2
|
||||
/// ```
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/#pet-peeves
|
||||
// E226
|
||||
#[violation]
|
||||
pub struct MissingWhitespaceAroundArithmeticOperator;
|
||||
@@ -65,24 +27,6 @@ impl Violation for MissingWhitespaceAroundArithmeticOperator {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for missing whitespace around bitwise and shift operators.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// According to [PEP 8], there should be one space before and after bitwise and
|
||||
/// shift operators (<<, >>, &, |, ^).
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// x = 128<<1
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// x = 128 << 1
|
||||
/// ```
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/#pet-peeves
|
||||
// E227
|
||||
#[violation]
|
||||
pub struct MissingWhitespaceAroundBitwiseOrShiftOperator;
|
||||
@@ -94,24 +38,6 @@ impl Violation for MissingWhitespaceAroundBitwiseOrShiftOperator {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for missing whitespace around the modulo operator.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// According to [PEP 8], the modulo operator (%) should have whitespace on
|
||||
/// either side of it.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// remainder = 10%2
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// remainder = 10 % 2
|
||||
/// ```
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/#other-recommendations
|
||||
// E228
|
||||
#[violation]
|
||||
pub struct MissingWhitespaceAroundModuloOperator;
|
||||
|
||||
@@ -120,57 +120,6 @@ impl Violation for MultipleSpacesAfterOperator {
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for extraneous tabs after a comma.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Commas should be followed by one space, never tabs.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// a = 4,\t5
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// a = 4, 3
|
||||
/// ```
|
||||
///
|
||||
#[violation]
|
||||
pub struct TabAfterComma;
|
||||
|
||||
impl Violation for TabAfterComma {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Tab after comma")
|
||||
}
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for extraneous whitespace after a comma.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// According to the `black` code style, commas should be followed by a single space.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// a = 4, 5
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// a = 4, 5
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct MultipleSpacesAfterComma;
|
||||
|
||||
impl Violation for MultipleSpacesAfterComma {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Multiple spaces after comma")
|
||||
}
|
||||
}
|
||||
|
||||
/// E221, E222, E223, E224
|
||||
pub(crate) fn space_around_operator(line: &LogicalLine, context: &mut LogicalLinesContext) {
|
||||
let mut after_operator = false;
|
||||
@@ -212,23 +161,6 @@ pub(crate) fn space_around_operator(line: &LogicalLine, context: &mut LogicalLin
|
||||
}
|
||||
}
|
||||
|
||||
/// E241, E242
|
||||
pub(crate) fn space_after_comma(line: &LogicalLine, context: &mut LogicalLinesContext) {
|
||||
for token in line.tokens() {
|
||||
if matches!(token.kind(), TokenKind::Comma) {
|
||||
match line.trailing_whitespace(token) {
|
||||
(Whitespace::Tab, len) => {
|
||||
context.push(TabAfterComma, TextRange::at(token.end(), len));
|
||||
}
|
||||
(Whitespace::Many, len) => {
|
||||
context.push(MultipleSpacesAfterComma, TextRange::at(token.end(), len));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fn is_operator_token(token: TokenKind) -> bool {
|
||||
matches!(
|
||||
token,
|
||||
|
||||
@@ -8,11 +8,11 @@ use crate::checkers::logical_lines::LogicalLinesContext;
|
||||
use crate::rules::pycodestyle::rules::logical_lines::LogicalLine;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for extraneous whitespace immediately preceding an open parenthesis
|
||||
/// Checks for extraneous whitespace immediately after an open parenthesis
|
||||
/// or bracket.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// According to [PEP 8], open parentheses and brackets should not be preceded
|
||||
/// According to [PEP 8], open parentheses and brackets should not be followed
|
||||
/// by any trailing whitespace.
|
||||
///
|
||||
/// ## Example
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E24.py:2:8: E241 Multiple spaces after comma
|
||||
|
|
||||
1 | #: E241
|
||||
2 | a = (1, 2)
|
||||
| ^^ E241
|
||||
3 | #: Okay
|
||||
4 | b = (1, 20)
|
||||
|
|
||||
|
||||
E24.py:11:18: E241 Multiple spaces after comma
|
||||
|
|
||||
9 | #: E241 E241 E241
|
||||
10 | # issue 135
|
||||
11 | more_spaces = [a, b,
|
||||
| ^^^^ E241
|
||||
12 | ef, +h,
|
||||
13 | c, -d]
|
||||
|
|
||||
|
||||
E24.py:12:19: E241 Multiple spaces after comma
|
||||
|
|
||||
10 | # issue 135
|
||||
11 | more_spaces = [a, b,
|
||||
12 | ef, +h,
|
||||
| ^^ E241
|
||||
13 | c, -d]
|
||||
|
|
||||
|
||||
E24.py:13:18: E241 Multiple spaces after comma
|
||||
|
|
||||
11 | more_spaces = [a, b,
|
||||
12 | ef, +h,
|
||||
13 | c, -d]
|
||||
| ^^^ E241
|
||||
|
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
E24.py:6:8: E242 Tab after comma
|
||||
|
|
||||
4 | b = (1, 20)
|
||||
5 | #: E242
|
||||
6 | a = (1, 2) # tab before 2
|
||||
| ^ E242
|
||||
7 | #: Okay
|
||||
8 | b = (1, 20) # space before 20
|
||||
|
|
||||
|
||||
|
||||
@@ -128,7 +128,6 @@ mod tests {
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_14.py"))]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_15.py"))]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_16.py"))]
|
||||
#[test_case(Rule::UndefinedName, Path::new("F821_17.py"))]
|
||||
#[test_case(Rule::UndefinedExport, Path::new("F822_0.py"))]
|
||||
#[test_case(Rule::UndefinedExport, Path::new("F822_1.py"))]
|
||||
#[test_case(Rule::UndefinedExport, Path::new("F822_2.py"))]
|
||||
@@ -2415,45 +2414,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aliased_submodule_import() {
|
||||
flakes(
|
||||
r#"
|
||||
import fu.bar as baz
|
||||
import fu.bar as baz
|
||||
baz
|
||||
"#,
|
||||
&[Rule::RedefinedWhileUnused],
|
||||
);
|
||||
|
||||
flakes(
|
||||
r#"
|
||||
import fu.bar as baz
|
||||
import baz
|
||||
baz
|
||||
"#,
|
||||
&[Rule::RedefinedWhileUnused],
|
||||
);
|
||||
|
||||
flakes(
|
||||
r#"
|
||||
import fu.bar as baz
|
||||
import fu.bar as bop
|
||||
baz, bop
|
||||
"#,
|
||||
&[],
|
||||
);
|
||||
|
||||
flakes(
|
||||
r#"
|
||||
import foo.baz
|
||||
import foo.baz as foo
|
||||
foo
|
||||
"#,
|
||||
&[Rule::RedefinedWhileUnused],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn used_package_with_submodule_import() {
|
||||
// Usage of package marks submodule imports as used.
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
F821_17.py:16:12: F821 Undefined name `DoesNotExist`
|
||||
|
|
||||
14 | # Types used in aliased assignment must exist
|
||||
15 |
|
||||
16 | type Foo = DoesNotExist # F821: Undefined name `DoesNotExist`
|
||||
| ^^^^^^^^^^^^ F821
|
||||
17 | type Foo = list[DoesNotExist] # F821: Undefined name `DoesNotExist`
|
||||
|
|
||||
|
||||
F821_17.py:17:17: F821 Undefined name `DoesNotExist`
|
||||
|
|
||||
16 | type Foo = DoesNotExist # F821: Undefined name `DoesNotExist`
|
||||
17 | type Foo = list[DoesNotExist] # F821: Undefined name `DoesNotExist`
|
||||
| ^^^^^^^^^^^^ F821
|
||||
18 |
|
||||
19 | # Type parameters do not escape alias scopes
|
||||
|
|
||||
|
||||
F821_17.py:22:1: F821 Undefined name `T`
|
||||
|
|
||||
21 | type Foo[T] = T
|
||||
22 | T # F821: Undefined name `T` - not accessible afterward alias scope
|
||||
| ^ F821
|
||||
23 |
|
||||
24 | # Type parameters in functions
|
||||
|
|
||||
|
||||
F821_17.py:39:17: F821 Undefined name `T`
|
||||
|
|
||||
37 | from some_library import some_decorator
|
||||
38 |
|
||||
39 | @some_decorator(T) # F821: Undefined name `T` - not accessible in decorators
|
||||
| ^ F821
|
||||
40 |
|
||||
41 | def foo[T](t: T) -> None: ...
|
||||
|
|
||||
|
||||
F821_17.py:42:1: F821 Undefined name `T`
|
||||
|
|
||||
41 | def foo[T](t: T) -> None: ...
|
||||
42 | T # F821: Undefined name `T` - not accessible afterward function scope
|
||||
| ^ F821
|
||||
|
|
||||
|
||||
F821_17.py:64:17: F821 Undefined name `T`
|
||||
|
|
||||
63 | from some_library import some_decorator
|
||||
64 | @some_decorator(T) # F821: Undefined name `T` - not accessible in decorators
|
||||
| ^ F821
|
||||
65 |
|
||||
66 | class Foo[T](list[T]): ...
|
||||
|
|
||||
|
||||
F821_17.py:67:1: F821 Undefined name `T`
|
||||
|
|
||||
66 | class Foo[T](list[T]): ...
|
||||
67 | T # F821: Undefined name `T` - not accessible after class scope
|
||||
| ^ F821
|
||||
68 |
|
||||
69 | # Types specified in bounds should exist
|
||||
|
|
||||
|
||||
F821_17.py:71:13: F821 Undefined name `DoesNotExist`
|
||||
|
|
||||
69 | # Types specified in bounds should exist
|
||||
70 |
|
||||
71 | type Foo[T: DoesNotExist] = T # F821: Undefined name `DoesNotExist`
|
||||
| ^^^^^^^^^^^^ F821
|
||||
72 | def foo[T: DoesNotExist](t: T) -> T: return t # F821: Undefined name `DoesNotExist`
|
||||
73 | class Foo[T: DoesNotExist](list[T]): ... # F821: Undefined name `DoesNotExist`
|
||||
|
|
||||
|
||||
F821_17.py:72:12: F821 Undefined name `DoesNotExist`
|
||||
|
|
||||
71 | type Foo[T: DoesNotExist] = T # F821: Undefined name `DoesNotExist`
|
||||
72 | def foo[T: DoesNotExist](t: T) -> T: return t # F821: Undefined name `DoesNotExist`
|
||||
| ^^^^^^^^^^^^ F821
|
||||
73 | class Foo[T: DoesNotExist](list[T]): ... # F821: Undefined name `DoesNotExist`
|
||||
|
|
||||
|
||||
F821_17.py:73:14: F821 Undefined name `DoesNotExist`
|
||||
|
|
||||
71 | type Foo[T: DoesNotExist] = T # F821: Undefined name `DoesNotExist`
|
||||
72 | def foo[T: DoesNotExist](t: T) -> T: return t # F821: Undefined name `DoesNotExist`
|
||||
73 | class Foo[T: DoesNotExist](list[T]): ... # F821: Undefined name `DoesNotExist`
|
||||
| ^^^^^^^^^^^^ F821
|
||||
74 |
|
||||
75 | type Foo[T: (DoesNotExist1, DoesNotExist2)] = T # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
|
|
||||
|
||||
F821_17.py:75:14: F821 Undefined name `DoesNotExist1`
|
||||
|
|
||||
73 | class Foo[T: DoesNotExist](list[T]): ... # F821: Undefined name `DoesNotExist`
|
||||
74 |
|
||||
75 | type Foo[T: (DoesNotExist1, DoesNotExist2)] = T # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
| ^^^^^^^^^^^^^ F821
|
||||
76 | def foo[T: (DoesNotExist1, DoesNotExist2)](t: T) -> T: return t # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
77 | class Foo[T: (DoesNotExist1, DoesNotExist2)](list[T]): ... # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
|
|
||||
|
||||
F821_17.py:75:29: F821 Undefined name `DoesNotExist2`
|
||||
|
|
||||
73 | class Foo[T: DoesNotExist](list[T]): ... # F821: Undefined name `DoesNotExist`
|
||||
74 |
|
||||
75 | type Foo[T: (DoesNotExist1, DoesNotExist2)] = T # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
| ^^^^^^^^^^^^^ F821
|
||||
76 | def foo[T: (DoesNotExist1, DoesNotExist2)](t: T) -> T: return t # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
77 | class Foo[T: (DoesNotExist1, DoesNotExist2)](list[T]): ... # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
|
|
||||
|
||||
F821_17.py:76:13: F821 Undefined name `DoesNotExist1`
|
||||
|
|
||||
75 | type Foo[T: (DoesNotExist1, DoesNotExist2)] = T # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
76 | def foo[T: (DoesNotExist1, DoesNotExist2)](t: T) -> T: return t # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
| ^^^^^^^^^^^^^ F821
|
||||
77 | class Foo[T: (DoesNotExist1, DoesNotExist2)](list[T]): ... # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
|
|
||||
|
||||
F821_17.py:76:28: F821 Undefined name `DoesNotExist2`
|
||||
|
|
||||
75 | type Foo[T: (DoesNotExist1, DoesNotExist2)] = T # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
76 | def foo[T: (DoesNotExist1, DoesNotExist2)](t: T) -> T: return t # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
| ^^^^^^^^^^^^^ F821
|
||||
77 | class Foo[T: (DoesNotExist1, DoesNotExist2)](list[T]): ... # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
|
|
||||
|
||||
F821_17.py:77:15: F821 Undefined name `DoesNotExist1`
|
||||
|
|
||||
75 | type Foo[T: (DoesNotExist1, DoesNotExist2)] = T # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
76 | def foo[T: (DoesNotExist1, DoesNotExist2)](t: T) -> T: return t # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
77 | class Foo[T: (DoesNotExist1, DoesNotExist2)](list[T]): ... # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
| ^^^^^^^^^^^^^ F821
|
||||
78 |
|
||||
79 | # Type parameters in nested classes
|
||||
|
|
||||
|
||||
F821_17.py:77:30: F821 Undefined name `DoesNotExist2`
|
||||
|
|
||||
75 | type Foo[T: (DoesNotExist1, DoesNotExist2)] = T # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
76 | def foo[T: (DoesNotExist1, DoesNotExist2)](t: T) -> T: return t # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
77 | class Foo[T: (DoesNotExist1, DoesNotExist2)](list[T]): ... # F821: Undefined name `DoesNotExist1`, Undefined name `DoesNotExist2`
|
||||
| ^^^^^^^^^^^^^ F821
|
||||
78 |
|
||||
79 | # Type parameters in nested classes
|
||||
|
|
||||
|
||||
F821_17.py:92:52: F821 Undefined name `t`
|
||||
|
|
||||
90 | return x
|
||||
91 |
|
||||
92 | def cannot_access_parent_variable(self, x: t) -> t: # F821: Undefined name `T`
|
||||
| ^ F821
|
||||
93 | t # F821: Undefined name `t`
|
||||
94 | return x
|
||||
|
|
||||
|
||||
F821_17.py:92:58: F821 Undefined name `t`
|
||||
|
|
||||
90 | return x
|
||||
91 |
|
||||
92 | def cannot_access_parent_variable(self, x: t) -> t: # F821: Undefined name `T`
|
||||
| ^ F821
|
||||
93 | t # F821: Undefined name `t`
|
||||
94 | return x
|
||||
|
|
||||
|
||||
F821_17.py:93:17: F821 Undefined name `t`
|
||||
|
|
||||
92 | def cannot_access_parent_variable(self, x: t) -> t: # F821: Undefined name `T`
|
||||
93 | t # F821: Undefined name `t`
|
||||
| ^ F821
|
||||
94 | return x
|
||||
|
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ pub(super) fn in_dunder_init(semantic: &SemanticModel, settings: &Settings) -> b
|
||||
if name != "__init__" {
|
||||
return false;
|
||||
}
|
||||
let Some(parent) = semantic.first_non_type_parent_scope(scope) else {
|
||||
let Some(parent) = scope.parent.map(|scope_id| &semantic.scopes[scope_id]) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ mod tests {
|
||||
Path::new("repeated_isinstance_calls.py")
|
||||
)]
|
||||
#[test_case(Rule::ComparisonWithItself, Path::new("comparison_with_itself.py"))]
|
||||
#[test_case(Rule::EqWithoutHash, Path::new("eq_without_hash.py"))]
|
||||
#[test_case(Rule::ManualFromImport, Path::new("import_aliasing.py"))]
|
||||
#[test_case(Rule::SingleStringSlots, Path::new("single_string_slots.py"))]
|
||||
#[test_case(Rule::SysExitAlias, Path::new("sys_exit_alias_0.py"))]
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
use ruff_python_ast::{self as ast, Ranged, Stmt};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for classes that implement `__eq__` but not `__hash__`.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// A class that implements `__eq__` but not `__hash__` will have its hash
|
||||
/// method implicitly set to `None`. This will cause the class to be
|
||||
/// unhashable, will in turn cause issues when using the class as a key in a
|
||||
/// dictionary or a member of a set.
|
||||
///
|
||||
/// ## Known problems
|
||||
/// Does not check for `__hash__` implementations in superclasses.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Person:
|
||||
/// def __init__(self):
|
||||
/// self.name = "monty"
|
||||
///
|
||||
/// def __eq__(self, other):
|
||||
/// return isinstance(other, Person) and other.name == self.name
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// class Person:
|
||||
/// def __init__(self):
|
||||
/// self.name = "monty"
|
||||
///
|
||||
/// def __eq__(self, other):
|
||||
/// return isinstance(other, Person) and other.name == self.name
|
||||
///
|
||||
/// def __hash__(self):
|
||||
/// return hash(self.name)
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct EqWithoutHash;
|
||||
|
||||
impl Violation for EqWithoutHash {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Object does not implement `__hash__` method")
|
||||
}
|
||||
}
|
||||
|
||||
/// W1641
|
||||
pub(crate) fn object_without_hash_method(
|
||||
checker: &mut Checker,
|
||||
ast::StmtClassDef { name, body, .. }: &ast::StmtClassDef,
|
||||
) {
|
||||
if has_eq_without_hash(body) {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(EqWithoutHash, name.range()));
|
||||
}
|
||||
}
|
||||
|
||||
fn has_eq_without_hash(body: &[Stmt]) -> bool {
|
||||
let mut has_hash = false;
|
||||
let mut has_eq = false;
|
||||
for statement in body {
|
||||
let Stmt::FunctionDef(ast::StmtFunctionDef { name, .. }) = statement else {
|
||||
continue;
|
||||
};
|
||||
match name.as_str() {
|
||||
"__hash__" => has_hash = true,
|
||||
"__eq__" => has_eq = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
has_eq && !has_hash
|
||||
}
|
||||
@@ -10,7 +10,6 @@ pub(crate) use comparison_of_constant::*;
|
||||
pub(crate) use comparison_with_itself::*;
|
||||
pub(crate) use continue_in_finally::*;
|
||||
pub(crate) use duplicate_bases::*;
|
||||
pub(crate) use eq_without_hash::*;
|
||||
pub(crate) use global_statement::*;
|
||||
pub(crate) use global_variable_not_assigned::*;
|
||||
pub(crate) use import_self::*;
|
||||
@@ -64,7 +63,6 @@ mod comparison_of_constant;
|
||||
mod comparison_with_itself;
|
||||
mod continue_in_finally;
|
||||
mod duplicate_bases;
|
||||
mod eq_without_hash;
|
||||
mod global_statement;
|
||||
mod global_variable_not_assigned;
|
||||
mod import_self;
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pylint/mod.rs
|
||||
---
|
||||
eq_without_hash.py:1:7: PLW1641 Object does not implement `__hash__` method
|
||||
|
|
||||
1 | class Person:
|
||||
| ^^^^^^ PLW1641
|
||||
2 | def __init__(self):
|
||||
3 | self.name = "monty"
|
||||
|
|
||||
|
||||
|
||||
@@ -167,9 +167,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ruff_noqa_all() -> Result<()> {
|
||||
fn ruff_noqa() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("ruff/ruff_noqa_all.py"),
|
||||
Path::new("ruff/ruff_noqa.py"),
|
||||
&settings::Settings::for_rules(vec![Rule::UnusedImport, Rule::UnusedVariable]),
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
@@ -177,19 +177,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ruff_noqa_codes() -> Result<()> {
|
||||
fn ruff_targeted_noqa() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("ruff/ruff_noqa_codes.py"),
|
||||
&settings::Settings::for_rules(vec![Rule::UnusedImport, Rule::UnusedVariable]),
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ruff_noqa_invalid() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("ruff/ruff_noqa_invalid.py"),
|
||||
Path::new("ruff/ruff_targeted_noqa.py"),
|
||||
&settings::Settings::for_rules(vec![Rule::UnusedImport, Rule::UnusedVariable]),
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use num_traits::Zero;
|
||||
use num_bigint::BigInt;
|
||||
use num_traits::{One, Zero};
|
||||
use ruff_python_ast::{self as ast, Comprehension, Constant, Expr, Ranged};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Comprehension, Constant, Expr, Ranged};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
@@ -48,20 +49,37 @@ use crate::registry::AsRule;
|
||||
#[violation]
|
||||
pub(crate) struct UnnecessaryIterableAllocationForFirstElement {
|
||||
iterable: String,
|
||||
subscript_kind: HeadSubscriptKind,
|
||||
}
|
||||
|
||||
impl AlwaysAutofixableViolation for UnnecessaryIterableAllocationForFirstElement {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let UnnecessaryIterableAllocationForFirstElement { iterable } = self;
|
||||
let UnnecessaryIterableAllocationForFirstElement {
|
||||
iterable,
|
||||
subscript_kind,
|
||||
} = self;
|
||||
let iterable = Self::truncate(iterable);
|
||||
format!("Prefer `next({iterable})` over single element slice")
|
||||
match subscript_kind {
|
||||
HeadSubscriptKind::Index => {
|
||||
format!("Prefer `next({iterable})` over single element slice")
|
||||
}
|
||||
HeadSubscriptKind::Slice => {
|
||||
format!("Prefer `[next({iterable})]` over single element slice")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn autofix_title(&self) -> String {
|
||||
let UnnecessaryIterableAllocationForFirstElement { iterable } = self;
|
||||
let UnnecessaryIterableAllocationForFirstElement {
|
||||
iterable,
|
||||
subscript_kind,
|
||||
} = self;
|
||||
let iterable = Self::truncate(iterable);
|
||||
format!("Replace with `next({iterable})`")
|
||||
match subscript_kind {
|
||||
HeadSubscriptKind::Index => format!("Replace with `next({iterable})`"),
|
||||
HeadSubscriptKind::Slice => format!("Replace with `[next({iterable})]"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,9 +106,9 @@ pub(crate) fn unnecessary_iterable_allocation_for_first_element(
|
||||
..
|
||||
} = subscript;
|
||||
|
||||
if !is_head_slice(slice) {
|
||||
let Some(subscript_kind) = classify_subscript(slice) else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let Some(target) = match_iteration_target(value, checker.semantic()) else {
|
||||
return;
|
||||
@@ -105,31 +123,72 @@ pub(crate) fn unnecessary_iterable_allocation_for_first_element(
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UnnecessaryIterableAllocationForFirstElement {
|
||||
iterable: iterable.to_string(),
|
||||
subscript_kind,
|
||||
},
|
||||
*range,
|
||||
);
|
||||
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(
|
||||
format!("next({iterable})"),
|
||||
*range,
|
||||
)));
|
||||
let replacement = match subscript_kind {
|
||||
HeadSubscriptKind::Index => format!("next({iterable})"),
|
||||
HeadSubscriptKind::Slice => format!("[next({iterable})]"),
|
||||
};
|
||||
diagnostic.set_fix(Fix::suggested(Edit::range_replacement(replacement, *range)));
|
||||
}
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
/// Check that the slice [`Expr`] is a slice of the first element (e.g., `x[0]`).
|
||||
fn is_head_slice(expr: &Expr) -> bool {
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(value),
|
||||
..
|
||||
}) = expr
|
||||
{
|
||||
value.is_zero()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
/// A subscript slice that represents the first element of a list.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum HeadSubscriptKind {
|
||||
/// The subscript is an index (e.g., `[0]`).
|
||||
Index,
|
||||
/// The subscript is a slice (e.g., `[:1]`).
|
||||
Slice,
|
||||
}
|
||||
|
||||
/// Check that the slice [`Expr`] is functionally equivalent to slicing into the first element. The
|
||||
/// first `bool` checks that the element is in fact first, the second checks if it's a slice or an
|
||||
/// index.
|
||||
fn classify_subscript(expr: &Expr) -> Option<HeadSubscriptKind> {
|
||||
let result = match expr {
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(value),
|
||||
..
|
||||
}) if value.is_zero() => HeadSubscriptKind::Index,
|
||||
Expr::Slice(ast::ExprSlice {
|
||||
step, lower, upper, ..
|
||||
}) => {
|
||||
// Avoid, e.g., `list(...)[:2]`
|
||||
let upper = upper.as_ref()?;
|
||||
let upper = as_int(upper)?;
|
||||
if !upper.is_one() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Avoid, e.g., `list(...)[2:]`.
|
||||
if let Some(lower) = lower.as_ref() {
|
||||
let lower = as_int(lower)?;
|
||||
if !lower.is_zero() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid, e.g., `list(...)[::-1]`
|
||||
if let Some(step) = step.as_ref() {
|
||||
let step = as_int(step)?;
|
||||
if step < upper {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
HeadSubscriptKind::Slice
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(result)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -251,3 +310,17 @@ fn match_simple_comprehension(elt: &Expr, generators: &[Comprehension]) -> Optio
|
||||
|
||||
Some(generator.iter.range())
|
||||
}
|
||||
|
||||
/// If an expression is a constant integer, returns the value of that integer; otherwise,
|
||||
/// returns `None`.
|
||||
fn as_int(expr: &Expr) -> Option<&BigInt> {
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(value),
|
||||
..
|
||||
}) = expr
|
||||
{
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ RUF015.py:4:1: RUF015 [*] Prefer `next(iter(x))` over single element slice
|
||||
3 | # RUF015
|
||||
4 | list(x)[0]
|
||||
| ^^^^^^^^^^ RUF015
|
||||
5 | tuple(x)[0]
|
||||
6 | list(i for i in x)[0]
|
||||
5 | list(x)[:1]
|
||||
6 | list(x)[:1:1]
|
||||
|
|
||||
= help: Replace with `next(iter(x))`
|
||||
|
||||
@@ -17,238 +17,490 @@ RUF015.py:4:1: RUF015 [*] Prefer `next(iter(x))` over single element slice
|
||||
3 3 | # RUF015
|
||||
4 |-list(x)[0]
|
||||
4 |+next(iter(x))
|
||||
5 5 | tuple(x)[0]
|
||||
6 6 | list(i for i in x)[0]
|
||||
7 7 | [i for i in x][0]
|
||||
5 5 | list(x)[:1]
|
||||
6 6 | list(x)[:1:1]
|
||||
7 7 | list(x)[:1:2]
|
||||
|
||||
RUF015.py:5:1: RUF015 [*] Prefer `next(iter(x))` over single element slice
|
||||
RUF015.py:5:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice
|
||||
|
|
||||
3 | # RUF015
|
||||
4 | list(x)[0]
|
||||
5 | tuple(x)[0]
|
||||
5 | list(x)[:1]
|
||||
| ^^^^^^^^^^^ RUF015
|
||||
6 | list(i for i in x)[0]
|
||||
7 | [i for i in x][0]
|
||||
6 | list(x)[:1:1]
|
||||
7 | list(x)[:1:2]
|
||||
|
|
||||
= help: Replace with `next(iter(x))`
|
||||
= help: Replace with `[next(iter(x))]
|
||||
|
||||
ℹ Suggested fix
|
||||
2 2 |
|
||||
3 3 | # RUF015
|
||||
4 4 | list(x)[0]
|
||||
5 |-tuple(x)[0]
|
||||
5 |+next(iter(x))
|
||||
6 6 | list(i for i in x)[0]
|
||||
7 7 | [i for i in x][0]
|
||||
8 8 |
|
||||
5 |-list(x)[:1]
|
||||
5 |+[next(iter(x))]
|
||||
6 6 | list(x)[:1:1]
|
||||
7 7 | list(x)[:1:2]
|
||||
8 8 | tuple(x)[0]
|
||||
|
||||
RUF015.py:6:1: RUF015 [*] Prefer `next(iter(x))` over single element slice
|
||||
RUF015.py:6:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice
|
||||
|
|
||||
4 | list(x)[0]
|
||||
5 | tuple(x)[0]
|
||||
6 | list(i for i in x)[0]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ RUF015
|
||||
7 | [i for i in x][0]
|
||||
5 | list(x)[:1]
|
||||
6 | list(x)[:1:1]
|
||||
| ^^^^^^^^^^^^^ RUF015
|
||||
7 | list(x)[:1:2]
|
||||
8 | tuple(x)[0]
|
||||
|
|
||||
= help: Replace with `next(iter(x))`
|
||||
= help: Replace with `[next(iter(x))]
|
||||
|
||||
ℹ Suggested fix
|
||||
3 3 | # RUF015
|
||||
4 4 | list(x)[0]
|
||||
5 5 | tuple(x)[0]
|
||||
6 |-list(i for i in x)[0]
|
||||
6 |+next(iter(x))
|
||||
7 7 | [i for i in x][0]
|
||||
8 8 |
|
||||
9 9 | # OK (not indexing (solely) the first element)
|
||||
5 5 | list(x)[:1]
|
||||
6 |-list(x)[:1:1]
|
||||
6 |+[next(iter(x))]
|
||||
7 7 | list(x)[:1:2]
|
||||
8 8 | tuple(x)[0]
|
||||
9 9 | tuple(x)[:1]
|
||||
|
||||
RUF015.py:7:1: RUF015 [*] Prefer `next(iter(x))` over single element slice
|
||||
RUF015.py:7:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice
|
||||
|
|
||||
5 | tuple(x)[0]
|
||||
6 | list(i for i in x)[0]
|
||||
7 | [i for i in x][0]
|
||||
| ^^^^^^^^^^^^^^^^^ RUF015
|
||||
8 |
|
||||
9 | # OK (not indexing (solely) the first element)
|
||||
5 | list(x)[:1]
|
||||
6 | list(x)[:1:1]
|
||||
7 | list(x)[:1:2]
|
||||
| ^^^^^^^^^^^^^ RUF015
|
||||
8 | tuple(x)[0]
|
||||
9 | tuple(x)[:1]
|
||||
|
|
||||
= help: Replace with `next(iter(x))`
|
||||
= help: Replace with `[next(iter(x))]
|
||||
|
||||
ℹ Suggested fix
|
||||
4 4 | list(x)[0]
|
||||
5 5 | tuple(x)[0]
|
||||
6 6 | list(i for i in x)[0]
|
||||
7 |-[i for i in x][0]
|
||||
7 |+next(iter(x))
|
||||
8 8 |
|
||||
9 9 | # OK (not indexing (solely) the first element)
|
||||
10 10 | list(x)
|
||||
5 5 | list(x)[:1]
|
||||
6 6 | list(x)[:1:1]
|
||||
7 |-list(x)[:1:2]
|
||||
7 |+[next(iter(x))]
|
||||
8 8 | tuple(x)[0]
|
||||
9 9 | tuple(x)[:1]
|
||||
10 10 | tuple(x)[:1:1]
|
||||
|
||||
RUF015.py:29:1: RUF015 [*] Prefer `next(i + 1 for i in x)` over single element slice
|
||||
RUF015.py:8:1: RUF015 [*] Prefer `next(iter(x))` over single element slice
|
||||
|
|
||||
28 | # RUF015 (doesn't mirror the underlying list)
|
||||
29 | [i + 1 for i in x][0]
|
||||
6 | list(x)[:1:1]
|
||||
7 | list(x)[:1:2]
|
||||
8 | tuple(x)[0]
|
||||
| ^^^^^^^^^^^ RUF015
|
||||
9 | tuple(x)[:1]
|
||||
10 | tuple(x)[:1:1]
|
||||
|
|
||||
= help: Replace with `next(iter(x))`
|
||||
|
||||
ℹ Suggested fix
|
||||
5 5 | list(x)[:1]
|
||||
6 6 | list(x)[:1:1]
|
||||
7 7 | list(x)[:1:2]
|
||||
8 |-tuple(x)[0]
|
||||
8 |+next(iter(x))
|
||||
9 9 | tuple(x)[:1]
|
||||
10 10 | tuple(x)[:1:1]
|
||||
11 11 | tuple(x)[:1:2]
|
||||
|
||||
RUF015.py:9:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice
|
||||
|
|
||||
7 | list(x)[:1:2]
|
||||
8 | tuple(x)[0]
|
||||
9 | tuple(x)[:1]
|
||||
| ^^^^^^^^^^^^ RUF015
|
||||
10 | tuple(x)[:1:1]
|
||||
11 | tuple(x)[:1:2]
|
||||
|
|
||||
= help: Replace with `[next(iter(x))]
|
||||
|
||||
ℹ Suggested fix
|
||||
6 6 | list(x)[:1:1]
|
||||
7 7 | list(x)[:1:2]
|
||||
8 8 | tuple(x)[0]
|
||||
9 |-tuple(x)[:1]
|
||||
9 |+[next(iter(x))]
|
||||
10 10 | tuple(x)[:1:1]
|
||||
11 11 | tuple(x)[:1:2]
|
||||
12 12 | list(i for i in x)[0]
|
||||
|
||||
RUF015.py:10:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice
|
||||
|
|
||||
8 | tuple(x)[0]
|
||||
9 | tuple(x)[:1]
|
||||
10 | tuple(x)[:1:1]
|
||||
| ^^^^^^^^^^^^^^ RUF015
|
||||
11 | tuple(x)[:1:2]
|
||||
12 | list(i for i in x)[0]
|
||||
|
|
||||
= help: Replace with `[next(iter(x))]
|
||||
|
||||
ℹ Suggested fix
|
||||
7 7 | list(x)[:1:2]
|
||||
8 8 | tuple(x)[0]
|
||||
9 9 | tuple(x)[:1]
|
||||
10 |-tuple(x)[:1:1]
|
||||
10 |+[next(iter(x))]
|
||||
11 11 | tuple(x)[:1:2]
|
||||
12 12 | list(i for i in x)[0]
|
||||
13 13 | list(i for i in x)[:1]
|
||||
|
||||
RUF015.py:11:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice
|
||||
|
|
||||
9 | tuple(x)[:1]
|
||||
10 | tuple(x)[:1:1]
|
||||
11 | tuple(x)[:1:2]
|
||||
| ^^^^^^^^^^^^^^ RUF015
|
||||
12 | list(i for i in x)[0]
|
||||
13 | list(i for i in x)[:1]
|
||||
|
|
||||
= help: Replace with `[next(iter(x))]
|
||||
|
||||
ℹ Suggested fix
|
||||
8 8 | tuple(x)[0]
|
||||
9 9 | tuple(x)[:1]
|
||||
10 10 | tuple(x)[:1:1]
|
||||
11 |-tuple(x)[:1:2]
|
||||
11 |+[next(iter(x))]
|
||||
12 12 | list(i for i in x)[0]
|
||||
13 13 | list(i for i in x)[:1]
|
||||
14 14 | list(i for i in x)[:1:1]
|
||||
|
||||
RUF015.py:12:1: RUF015 [*] Prefer `next(iter(x))` over single element slice
|
||||
|
|
||||
10 | tuple(x)[:1:1]
|
||||
11 | tuple(x)[:1:2]
|
||||
12 | list(i for i in x)[0]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ RUF015
|
||||
30 | [i for i in x if i > 5][0]
|
||||
31 | [(i, i + 1) for i in x][0]
|
||||
13 | list(i for i in x)[:1]
|
||||
14 | list(i for i in x)[:1:1]
|
||||
|
|
||||
= help: Replace with `next(iter(x))`
|
||||
|
||||
ℹ Suggested fix
|
||||
9 9 | tuple(x)[:1]
|
||||
10 10 | tuple(x)[:1:1]
|
||||
11 11 | tuple(x)[:1:2]
|
||||
12 |-list(i for i in x)[0]
|
||||
12 |+next(iter(x))
|
||||
13 13 | list(i for i in x)[:1]
|
||||
14 14 | list(i for i in x)[:1:1]
|
||||
15 15 | list(i for i in x)[:1:2]
|
||||
|
||||
RUF015.py:13:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice
|
||||
|
|
||||
11 | tuple(x)[:1:2]
|
||||
12 | list(i for i in x)[0]
|
||||
13 | list(i for i in x)[:1]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ RUF015
|
||||
14 | list(i for i in x)[:1:1]
|
||||
15 | list(i for i in x)[:1:2]
|
||||
|
|
||||
= help: Replace with `[next(iter(x))]
|
||||
|
||||
ℹ Suggested fix
|
||||
10 10 | tuple(x)[:1:1]
|
||||
11 11 | tuple(x)[:1:2]
|
||||
12 12 | list(i for i in x)[0]
|
||||
13 |-list(i for i in x)[:1]
|
||||
13 |+[next(iter(x))]
|
||||
14 14 | list(i for i in x)[:1:1]
|
||||
15 15 | list(i for i in x)[:1:2]
|
||||
16 16 | [i for i in x][0]
|
||||
|
||||
RUF015.py:14:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice
|
||||
|
|
||||
12 | list(i for i in x)[0]
|
||||
13 | list(i for i in x)[:1]
|
||||
14 | list(i for i in x)[:1:1]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ RUF015
|
||||
15 | list(i for i in x)[:1:2]
|
||||
16 | [i for i in x][0]
|
||||
|
|
||||
= help: Replace with `[next(iter(x))]
|
||||
|
||||
ℹ Suggested fix
|
||||
11 11 | tuple(x)[:1:2]
|
||||
12 12 | list(i for i in x)[0]
|
||||
13 13 | list(i for i in x)[:1]
|
||||
14 |-list(i for i in x)[:1:1]
|
||||
14 |+[next(iter(x))]
|
||||
15 15 | list(i for i in x)[:1:2]
|
||||
16 16 | [i for i in x][0]
|
||||
17 17 | [i for i in x][:1]
|
||||
|
||||
RUF015.py:15:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice
|
||||
|
|
||||
13 | list(i for i in x)[:1]
|
||||
14 | list(i for i in x)[:1:1]
|
||||
15 | list(i for i in x)[:1:2]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ RUF015
|
||||
16 | [i for i in x][0]
|
||||
17 | [i for i in x][:1]
|
||||
|
|
||||
= help: Replace with `[next(iter(x))]
|
||||
|
||||
ℹ Suggested fix
|
||||
12 12 | list(i for i in x)[0]
|
||||
13 13 | list(i for i in x)[:1]
|
||||
14 14 | list(i for i in x)[:1:1]
|
||||
15 |-list(i for i in x)[:1:2]
|
||||
15 |+[next(iter(x))]
|
||||
16 16 | [i for i in x][0]
|
||||
17 17 | [i for i in x][:1]
|
||||
18 18 | [i for i in x][:1:1]
|
||||
|
||||
RUF015.py:16:1: RUF015 [*] Prefer `next(iter(x))` over single element slice
|
||||
|
|
||||
14 | list(i for i in x)[:1:1]
|
||||
15 | list(i for i in x)[:1:2]
|
||||
16 | [i for i in x][0]
|
||||
| ^^^^^^^^^^^^^^^^^ RUF015
|
||||
17 | [i for i in x][:1]
|
||||
18 | [i for i in x][:1:1]
|
||||
|
|
||||
= help: Replace with `next(iter(x))`
|
||||
|
||||
ℹ Suggested fix
|
||||
13 13 | list(i for i in x)[:1]
|
||||
14 14 | list(i for i in x)[:1:1]
|
||||
15 15 | list(i for i in x)[:1:2]
|
||||
16 |-[i for i in x][0]
|
||||
16 |+next(iter(x))
|
||||
17 17 | [i for i in x][:1]
|
||||
18 18 | [i for i in x][:1:1]
|
||||
19 19 | [i for i in x][:1:2]
|
||||
|
||||
RUF015.py:17:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice
|
||||
|
|
||||
15 | list(i for i in x)[:1:2]
|
||||
16 | [i for i in x][0]
|
||||
17 | [i for i in x][:1]
|
||||
| ^^^^^^^^^^^^^^^^^^ RUF015
|
||||
18 | [i for i in x][:1:1]
|
||||
19 | [i for i in x][:1:2]
|
||||
|
|
||||
= help: Replace with `[next(iter(x))]
|
||||
|
||||
ℹ Suggested fix
|
||||
14 14 | list(i for i in x)[:1:1]
|
||||
15 15 | list(i for i in x)[:1:2]
|
||||
16 16 | [i for i in x][0]
|
||||
17 |-[i for i in x][:1]
|
||||
17 |+[next(iter(x))]
|
||||
18 18 | [i for i in x][:1:1]
|
||||
19 19 | [i for i in x][:1:2]
|
||||
20 20 |
|
||||
|
||||
RUF015.py:18:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice
|
||||
|
|
||||
16 | [i for i in x][0]
|
||||
17 | [i for i in x][:1]
|
||||
18 | [i for i in x][:1:1]
|
||||
| ^^^^^^^^^^^^^^^^^^^^ RUF015
|
||||
19 | [i for i in x][:1:2]
|
||||
|
|
||||
= help: Replace with `[next(iter(x))]
|
||||
|
||||
ℹ Suggested fix
|
||||
15 15 | list(i for i in x)[:1:2]
|
||||
16 16 | [i for i in x][0]
|
||||
17 17 | [i for i in x][:1]
|
||||
18 |-[i for i in x][:1:1]
|
||||
18 |+[next(iter(x))]
|
||||
19 19 | [i for i in x][:1:2]
|
||||
20 20 |
|
||||
21 21 | # OK (not indexing (solely) the first element)
|
||||
|
||||
RUF015.py:19:1: RUF015 [*] Prefer `[next(iter(x))]` over single element slice
|
||||
|
|
||||
17 | [i for i in x][:1]
|
||||
18 | [i for i in x][:1:1]
|
||||
19 | [i for i in x][:1:2]
|
||||
| ^^^^^^^^^^^^^^^^^^^^ RUF015
|
||||
20 |
|
||||
21 | # OK (not indexing (solely) the first element)
|
||||
|
|
||||
= help: Replace with `[next(iter(x))]
|
||||
|
||||
ℹ Suggested fix
|
||||
16 16 | [i for i in x][0]
|
||||
17 17 | [i for i in x][:1]
|
||||
18 18 | [i for i in x][:1:1]
|
||||
19 |-[i for i in x][:1:2]
|
||||
19 |+[next(iter(x))]
|
||||
20 20 |
|
||||
21 21 | # OK (not indexing (solely) the first element)
|
||||
22 22 | list(x)
|
||||
|
||||
RUF015.py:38:1: RUF015 [*] Prefer `next(i + 1 for i in x)` over single element slice
|
||||
|
|
||||
37 | # RUF015 (doesn't mirror the underlying list)
|
||||
38 | [i + 1 for i in x][0]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ RUF015
|
||||
39 | [i for i in x if i > 5][0]
|
||||
40 | [(i, i + 1) for i in x][0]
|
||||
|
|
||||
= help: Replace with `next(i + 1 for i in x)`
|
||||
|
||||
ℹ Suggested fix
|
||||
26 26 | [i for i in x][::]
|
||||
27 27 |
|
||||
28 28 | # RUF015 (doesn't mirror the underlying list)
|
||||
29 |-[i + 1 for i in x][0]
|
||||
29 |+next(i + 1 for i in x)
|
||||
30 30 | [i for i in x if i > 5][0]
|
||||
31 31 | [(i, i + 1) for i in x][0]
|
||||
32 32 |
|
||||
35 35 | [i for i in x][::]
|
||||
36 36 |
|
||||
37 37 | # RUF015 (doesn't mirror the underlying list)
|
||||
38 |-[i + 1 for i in x][0]
|
||||
38 |+next(i + 1 for i in x)
|
||||
39 39 | [i for i in x if i > 5][0]
|
||||
40 40 | [(i, i + 1) for i in x][0]
|
||||
41 41 |
|
||||
|
||||
RUF015.py:30:1: RUF015 [*] Prefer `next(i for i in x if i > 5)` over single element slice
|
||||
RUF015.py:39:1: RUF015 [*] Prefer `next(i for i in x if i > 5)` over single element slice
|
||||
|
|
||||
28 | # RUF015 (doesn't mirror the underlying list)
|
||||
29 | [i + 1 for i in x][0]
|
||||
30 | [i for i in x if i > 5][0]
|
||||
37 | # RUF015 (doesn't mirror the underlying list)
|
||||
38 | [i + 1 for i in x][0]
|
||||
39 | [i for i in x if i > 5][0]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF015
|
||||
31 | [(i, i + 1) for i in x][0]
|
||||
40 | [(i, i + 1) for i in x][0]
|
||||
|
|
||||
= help: Replace with `next(i for i in x if i > 5)`
|
||||
|
||||
ℹ Suggested fix
|
||||
27 27 |
|
||||
28 28 | # RUF015 (doesn't mirror the underlying list)
|
||||
29 29 | [i + 1 for i in x][0]
|
||||
30 |-[i for i in x if i > 5][0]
|
||||
30 |+next(i for i in x if i > 5)
|
||||
31 31 | [(i, i + 1) for i in x][0]
|
||||
32 32 |
|
||||
33 33 | # RUF015 (multiple generators)
|
||||
36 36 |
|
||||
37 37 | # RUF015 (doesn't mirror the underlying list)
|
||||
38 38 | [i + 1 for i in x][0]
|
||||
39 |-[i for i in x if i > 5][0]
|
||||
39 |+next(i for i in x if i > 5)
|
||||
40 40 | [(i, i + 1) for i in x][0]
|
||||
41 41 |
|
||||
42 42 | # RUF015 (multiple generators)
|
||||
|
||||
RUF015.py:31:1: RUF015 [*] Prefer `next((i, i + 1) for i in x)` over single element slice
|
||||
RUF015.py:40:1: RUF015 [*] Prefer `next((i, i + 1) for i in x)` over single element slice
|
||||
|
|
||||
29 | [i + 1 for i in x][0]
|
||||
30 | [i for i in x if i > 5][0]
|
||||
31 | [(i, i + 1) for i in x][0]
|
||||
38 | [i + 1 for i in x][0]
|
||||
39 | [i for i in x if i > 5][0]
|
||||
40 | [(i, i + 1) for i in x][0]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF015
|
||||
32 |
|
||||
33 | # RUF015 (multiple generators)
|
||||
41 |
|
||||
42 | # RUF015 (multiple generators)
|
||||
|
|
||||
= help: Replace with `next((i, i + 1) for i in x)`
|
||||
|
||||
ℹ Suggested fix
|
||||
28 28 | # RUF015 (doesn't mirror the underlying list)
|
||||
29 29 | [i + 1 for i in x][0]
|
||||
30 30 | [i for i in x if i > 5][0]
|
||||
31 |-[(i, i + 1) for i in x][0]
|
||||
31 |+next((i, i + 1) for i in x)
|
||||
32 32 |
|
||||
33 33 | # RUF015 (multiple generators)
|
||||
34 34 | y = range(10)
|
||||
37 37 | # RUF015 (doesn't mirror the underlying list)
|
||||
38 38 | [i + 1 for i in x][0]
|
||||
39 39 | [i for i in x if i > 5][0]
|
||||
40 |-[(i, i + 1) for i in x][0]
|
||||
40 |+next((i, i + 1) for i in x)
|
||||
41 41 |
|
||||
42 42 | # RUF015 (multiple generators)
|
||||
43 43 | y = range(10)
|
||||
|
||||
RUF015.py:35:1: RUF015 [*] Prefer `next(i + j for i in x for j in y)` over single element slice
|
||||
RUF015.py:44:1: RUF015 [*] Prefer `next(i + j for i in x for j in y)` over single element slice
|
||||
|
|
||||
33 | # RUF015 (multiple generators)
|
||||
34 | y = range(10)
|
||||
35 | [i + j for i in x for j in y][0]
|
||||
42 | # RUF015 (multiple generators)
|
||||
43 | y = range(10)
|
||||
44 | [i + j for i in x for j in y][0]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF015
|
||||
36 |
|
||||
37 | # RUF015
|
||||
45 |
|
||||
46 | # RUF015
|
||||
|
|
||||
= help: Replace with `next(i + j for i in x for j in y)`
|
||||
|
||||
ℹ Suggested fix
|
||||
32 32 |
|
||||
33 33 | # RUF015 (multiple generators)
|
||||
34 34 | y = range(10)
|
||||
35 |-[i + j for i in x for j in y][0]
|
||||
35 |+next(i + j for i in x for j in y)
|
||||
36 36 |
|
||||
37 37 | # RUF015
|
||||
38 38 | list(range(10))[0]
|
||||
41 41 |
|
||||
42 42 | # RUF015 (multiple generators)
|
||||
43 43 | y = range(10)
|
||||
44 |-[i + j for i in x for j in y][0]
|
||||
44 |+next(i + j for i in x for j in y)
|
||||
45 45 |
|
||||
46 46 | # RUF015
|
||||
47 47 | list(range(10))[0]
|
||||
|
||||
RUF015.py:38:1: RUF015 [*] Prefer `next(iter(range(10)))` over single element slice
|
||||
RUF015.py:47:1: RUF015 [*] Prefer `next(iter(range(10)))` over single element slice
|
||||
|
|
||||
37 | # RUF015
|
||||
38 | list(range(10))[0]
|
||||
46 | # RUF015
|
||||
47 | list(range(10))[0]
|
||||
| ^^^^^^^^^^^^^^^^^^ RUF015
|
||||
39 | list(x.y)[0]
|
||||
40 | list(x["y"])[0]
|
||||
48 | list(x.y)[0]
|
||||
49 | list(x["y"])[0]
|
||||
|
|
||||
= help: Replace with `next(iter(range(10)))`
|
||||
|
||||
ℹ Suggested fix
|
||||
35 35 | [i + j for i in x for j in y][0]
|
||||
36 36 |
|
||||
37 37 | # RUF015
|
||||
38 |-list(range(10))[0]
|
||||
38 |+next(iter(range(10)))
|
||||
39 39 | list(x.y)[0]
|
||||
40 40 | list(x["y"])[0]
|
||||
41 41 |
|
||||
44 44 | [i + j for i in x for j in y][0]
|
||||
45 45 |
|
||||
46 46 | # RUF015
|
||||
47 |-list(range(10))[0]
|
||||
47 |+next(iter(range(10)))
|
||||
48 48 | list(x.y)[0]
|
||||
49 49 | list(x["y"])[0]
|
||||
50 50 |
|
||||
|
||||
RUF015.py:39:1: RUF015 [*] Prefer `next(iter(x.y))` over single element slice
|
||||
RUF015.py:48:1: RUF015 [*] Prefer `next(iter(x.y))` over single element slice
|
||||
|
|
||||
37 | # RUF015
|
||||
38 | list(range(10))[0]
|
||||
39 | list(x.y)[0]
|
||||
46 | # RUF015
|
||||
47 | list(range(10))[0]
|
||||
48 | list(x.y)[0]
|
||||
| ^^^^^^^^^^^^ RUF015
|
||||
40 | list(x["y"])[0]
|
||||
49 | list(x["y"])[0]
|
||||
|
|
||||
= help: Replace with `next(iter(x.y))`
|
||||
|
||||
ℹ Suggested fix
|
||||
36 36 |
|
||||
37 37 | # RUF015
|
||||
38 38 | list(range(10))[0]
|
||||
39 |-list(x.y)[0]
|
||||
39 |+next(iter(x.y))
|
||||
40 40 | list(x["y"])[0]
|
||||
41 41 |
|
||||
42 42 | # RUF015 (multi-line)
|
||||
45 45 |
|
||||
46 46 | # RUF015
|
||||
47 47 | list(range(10))[0]
|
||||
48 |-list(x.y)[0]
|
||||
48 |+next(iter(x.y))
|
||||
49 49 | list(x["y"])[0]
|
||||
50 50 |
|
||||
51 51 | # RUF015 (multi-line)
|
||||
|
||||
RUF015.py:40:1: RUF015 [*] Prefer `next(iter(x["y"]))` over single element slice
|
||||
RUF015.py:49:1: RUF015 [*] Prefer `next(iter(x["y"]))` over single element slice
|
||||
|
|
||||
38 | list(range(10))[0]
|
||||
39 | list(x.y)[0]
|
||||
40 | list(x["y"])[0]
|
||||
47 | list(range(10))[0]
|
||||
48 | list(x.y)[0]
|
||||
49 | list(x["y"])[0]
|
||||
| ^^^^^^^^^^^^^^^ RUF015
|
||||
41 |
|
||||
42 | # RUF015 (multi-line)
|
||||
50 |
|
||||
51 | # RUF015 (multi-line)
|
||||
|
|
||||
= help: Replace with `next(iter(x["y"]))`
|
||||
|
||||
ℹ Suggested fix
|
||||
37 37 | # RUF015
|
||||
38 38 | list(range(10))[0]
|
||||
39 39 | list(x.y)[0]
|
||||
40 |-list(x["y"])[0]
|
||||
40 |+next(iter(x["y"]))
|
||||
41 41 |
|
||||
42 42 | # RUF015 (multi-line)
|
||||
43 43 | revision_heads_map_ast = [
|
||||
46 46 | # RUF015
|
||||
47 47 | list(range(10))[0]
|
||||
48 48 | list(x.y)[0]
|
||||
49 |-list(x["y"])[0]
|
||||
49 |+next(iter(x["y"]))
|
||||
50 50 |
|
||||
51 51 | # RUF015 (multi-line)
|
||||
52 52 | revision_heads_map_ast = [
|
||||
|
||||
RUF015.py:43:26: RUF015 [*] Prefer `next(...)` over single element slice
|
||||
RUF015.py:52:26: RUF015 [*] Prefer `next(...)` over single element slice
|
||||
|
|
||||
42 | # RUF015 (multi-line)
|
||||
43 | revision_heads_map_ast = [
|
||||
51 | # RUF015 (multi-line)
|
||||
52 | revision_heads_map_ast = [
|
||||
| __________________________^
|
||||
44 | | a
|
||||
45 | | for a in revision_heads_map_ast_obj.body
|
||||
46 | | if isinstance(a, ast.Assign) and a.targets[0].id == "REVISION_HEADS_MAP"
|
||||
47 | | ][0]
|
||||
53 | | a
|
||||
54 | | for a in revision_heads_map_ast_obj.body
|
||||
55 | | if isinstance(a, ast.Assign) and a.targets[0].id == "REVISION_HEADS_MAP"
|
||||
56 | | ][0]
|
||||
| |____^ RUF015
|
||||
|
|
||||
= help: Replace with `next(...)`
|
||||
|
||||
ℹ Suggested fix
|
||||
40 40 | list(x["y"])[0]
|
||||
41 41 |
|
||||
42 42 | # RUF015 (multi-line)
|
||||
43 |-revision_heads_map_ast = [
|
||||
43 |+revision_heads_map_ast = next(
|
||||
44 44 | a
|
||||
45 45 | for a in revision_heads_map_ast_obj.body
|
||||
46 46 | if isinstance(a, ast.Assign) and a.targets[0].id == "REVISION_HEADS_MAP"
|
||||
47 |-][0]
|
||||
47 |+)
|
||||
49 49 | list(x["y"])[0]
|
||||
50 50 |
|
||||
51 51 | # RUF015 (multi-line)
|
||||
52 |-revision_heads_map_ast = [
|
||||
52 |+revision_heads_map_ast = next(
|
||||
53 53 | a
|
||||
54 54 | for a in revision_heads_map_ast_obj.body
|
||||
55 55 | if isinstance(a, ast.Assign) and a.targets[0].id == "REVISION_HEADS_MAP"
|
||||
56 |-][0]
|
||||
56 |+)
|
||||
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/ruff/mod.rs
|
||||
---
|
||||
ruff_noqa_invalid.py:1:8: F401 [*] `os` imported but unused
|
||||
|
|
||||
1 | import os # ruff: noqa: F401
|
||||
| ^^ F401
|
||||
|
|
||||
= help: Remove unused import: `os`
|
||||
|
||||
ℹ Fix
|
||||
1 |-import os # ruff: noqa: F401
|
||||
2 1 |
|
||||
3 2 |
|
||||
4 3 | def f():
|
||||
|
||||
ruff_noqa_invalid.py:5:5: F841 [*] Local variable `x` is assigned to but never used
|
||||
|
|
||||
4 | def f():
|
||||
5 | x = 1
|
||||
| ^ F841
|
||||
|
|
||||
= help: Remove assignment to unused variable `x`
|
||||
|
||||
ℹ Suggested fix
|
||||
2 2 |
|
||||
3 3 |
|
||||
4 4 | def f():
|
||||
5 |- x = 1
|
||||
5 |+ pass
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/ruff/mod.rs
|
||||
---
|
||||
ruff_noqa_codes.py:8:5: F841 [*] Local variable `x` is assigned to but never used
|
||||
ruff_targeted_noqa.py:8:5: F841 [*] Local variable `x` is assigned to but never used
|
||||
|
|
||||
7 | def f():
|
||||
8 | x = 1
|
||||
@@ -85,7 +85,7 @@ pub fn test_snippet(contents: &str, settings: &Settings) -> Vec<Message> {
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static MAX_ITERATIONS: std::cell::Cell<usize> = std::cell::Cell::new(8);
|
||||
static MAX_ITERATIONS: std::cell::Cell<usize> = std::cell::Cell::new(30);
|
||||
}
|
||||
|
||||
pub fn set_max_iterations(max: usize) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.281"
|
||||
version = "0.0.280"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -17,7 +17,7 @@ use similar::TextDiff;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
use crate::cache::Cache;
|
||||
use ruff::jupyter::{Cell, Notebook};
|
||||
use ruff::jupyter::Notebook;
|
||||
use ruff::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult};
|
||||
use ruff::logging::DisplayParseError;
|
||||
use ruff::message::Message;
|
||||
@@ -261,66 +261,13 @@ pub(crate) fn lint_path(
|
||||
}
|
||||
},
|
||||
flags::FixMode::Diff => {
|
||||
match &source_kind {
|
||||
SourceKind::Python(_) => {
|
||||
let mut stdout = io::stdout().lock();
|
||||
TextDiff::from_lines(contents.as_str(), &transformed)
|
||||
.unified_diff()
|
||||
.header(&fs::relativize_path(path), &fs::relativize_path(path))
|
||||
.to_writer(&mut stdout)?;
|
||||
stdout.write_all(b"\n")?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
SourceKind::Jupyter(dest_notebook) => {
|
||||
// We need to load the notebook again, since we might've
|
||||
// mutated it.
|
||||
let src_notebook = match load_jupyter_notebook(path) {
|
||||
Ok(notebook) => notebook,
|
||||
Err(diagnostic) => return Ok(*diagnostic),
|
||||
};
|
||||
let mut stdout = io::stdout().lock();
|
||||
for ((idx, src_cell), dest_cell) in src_notebook
|
||||
.cells()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.zip(dest_notebook.cells().iter())
|
||||
{
|
||||
let (Cell::Code(src_code_cell), Cell::Code(dest_code_cell)) =
|
||||
(src_cell, dest_cell)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
TextDiff::from_lines(
|
||||
&src_code_cell.source.to_string(),
|
||||
&dest_code_cell.source.to_string(),
|
||||
)
|
||||
.unified_diff()
|
||||
// Jupyter notebook cells don't necessarily have a newline
|
||||
// at the end. For example,
|
||||
//
|
||||
// ```python
|
||||
// print("hello")
|
||||
// ```
|
||||
//
|
||||
// For a cell containing the above code, there'll only be one line,
|
||||
// and it won't have a newline at the end. If it did, there'd be
|
||||
// two lines, and the second line would be empty:
|
||||
//
|
||||
// ```python
|
||||
// print("hello")
|
||||
//
|
||||
// ```
|
||||
.missing_newline_hint(false)
|
||||
.header(
|
||||
&format!("{}:cell {}", &fs::relativize_path(path), idx),
|
||||
&format!("{}:cell {}", &fs::relativize_path(path), idx),
|
||||
)
|
||||
.to_writer(&mut stdout)?;
|
||||
}
|
||||
stdout.write_all(b"\n")?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
}
|
||||
let mut stdout = io::stdout().lock();
|
||||
TextDiff::from_lines(contents.as_str(), &transformed)
|
||||
.unified_diff()
|
||||
.header(&fs::relativize_path(path), &fs::relativize_path(path))
|
||||
.to_writer(&mut stdout)?;
|
||||
stdout.write_all(b"\n")?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
flags::FixMode::Generate => {}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
/// A text edit to be applied to a source file. Inserts, deletes, or replaces
|
||||
/// content at a given location.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use ruff_text_size::TextSize;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use ruff_text_size::TextSize;
|
||||
|
||||
use crate::edit::Edit;
|
||||
|
||||
/// Indicates confidence in the correctness of a suggested fix.
|
||||
@@ -43,11 +42,8 @@ pub enum IsolationLevel {
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct Fix {
|
||||
/// The [`Edit`] elements to be applied, sorted by [`Edit::start`] in ascending order.
|
||||
edits: Vec<Edit>,
|
||||
/// The [`Applicability`] of the fix.
|
||||
applicability: Applicability,
|
||||
/// The [`IsolationLevel`] of the fix.
|
||||
isolation_level: IsolationLevel,
|
||||
}
|
||||
|
||||
@@ -87,10 +83,8 @@ impl Fix {
|
||||
|
||||
/// Create a new [`Fix`] with [automatic applicability](Applicability::Automatic) from multiple [`Edit`] elements.
|
||||
pub fn automatic_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
|
||||
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
|
||||
edits.sort_by_key(Edit::start);
|
||||
Self {
|
||||
edits,
|
||||
edits: std::iter::once(edit).chain(rest).collect(),
|
||||
applicability: Applicability::Automatic,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
@@ -107,10 +101,8 @@ impl Fix {
|
||||
|
||||
/// Create a new [`Fix`] with [suggested applicability](Applicability::Suggested) from multiple [`Edit`] elements.
|
||||
pub fn suggested_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
|
||||
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
|
||||
edits.sort_by_key(Edit::start);
|
||||
Self {
|
||||
edits,
|
||||
edits: std::iter::once(edit).chain(rest).collect(),
|
||||
applicability: Applicability::Suggested,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
@@ -127,10 +119,8 @@ impl Fix {
|
||||
|
||||
/// Create a new [`Fix`] with [manual applicability](Applicability::Manual) from multiple [`Edit`] elements.
|
||||
pub fn manual_edits(edit: Edit, rest: impl IntoIterator<Item = Edit>) -> Self {
|
||||
let mut edits: Vec<Edit> = std::iter::once(edit).chain(rest).collect();
|
||||
edits.sort_by_key(Edit::start);
|
||||
Self {
|
||||
edits,
|
||||
edits: std::iter::once(edit).chain(rest).collect(),
|
||||
applicability: Applicability::Manual,
|
||||
isolation_level: IsolationLevel::default(),
|
||||
}
|
||||
@@ -138,14 +128,18 @@ impl Fix {
|
||||
|
||||
/// Return the [`TextSize`] of the first [`Edit`] in the [`Fix`].
|
||||
pub fn min_start(&self) -> Option<TextSize> {
|
||||
self.edits.first().map(Edit::start)
|
||||
self.edits.iter().map(Edit::start).min()
|
||||
}
|
||||
|
||||
/// Return a slice of the [`Edit`] elements in the [`Fix`], sorted by [`Edit::start`] in ascending order.
|
||||
/// Return a slice of the [`Edit`] elements in the [`Fix`].
|
||||
pub fn edits(&self) -> &[Edit] {
|
||||
&self.edits
|
||||
}
|
||||
|
||||
pub fn into_edits(self) -> Vec<Edit> {
|
||||
self.edits
|
||||
}
|
||||
|
||||
/// Return the [`Applicability`] of the [`Fix`].
|
||||
pub fn applicability(&self) -> Applicability {
|
||||
self.applicability
|
||||
|
||||
@@ -12,7 +12,7 @@ pub fn indentation<'a, T>(locator: &'a Locator, located: &T) -> Option<&'a str>
|
||||
where
|
||||
T: Ranged,
|
||||
{
|
||||
indentation_at_offset(located.start(), locator)
|
||||
indentation_at_offset(locator, located.start())
|
||||
}
|
||||
|
||||
/// Return the end offset at which the empty lines following a statement.
|
||||
|
||||
@@ -34,7 +34,6 @@ mod precedence {
|
||||
pub(crate) const COMMA: u8 = 21;
|
||||
pub(crate) const NAMED_EXPR: u8 = 23;
|
||||
pub(crate) const ASSERT: u8 = 23;
|
||||
pub(crate) const COMPREHENSION_ELEMENT: u8 = 27;
|
||||
pub(crate) const LAMBDA: u8 = 27;
|
||||
pub(crate) const IF_EXP: u8 = 27;
|
||||
pub(crate) const COMPREHENSION: u8 = 29;
|
||||
@@ -1053,7 +1052,7 @@ impl<'a> Generator<'a> {
|
||||
range: _range,
|
||||
}) => {
|
||||
self.p("[");
|
||||
self.unparse_expr(elt, precedence::COMPREHENSION_ELEMENT);
|
||||
self.unparse_expr(elt, precedence::MAX);
|
||||
self.unparse_comp(generators);
|
||||
self.p("]");
|
||||
}
|
||||
@@ -1063,7 +1062,7 @@ impl<'a> Generator<'a> {
|
||||
range: _range,
|
||||
}) => {
|
||||
self.p("{");
|
||||
self.unparse_expr(elt, precedence::COMPREHENSION_ELEMENT);
|
||||
self.unparse_expr(elt, precedence::MAX);
|
||||
self.unparse_comp(generators);
|
||||
self.p("}");
|
||||
}
|
||||
@@ -1074,9 +1073,9 @@ impl<'a> Generator<'a> {
|
||||
range: _range,
|
||||
}) => {
|
||||
self.p("{");
|
||||
self.unparse_expr(key, precedence::COMPREHENSION_ELEMENT);
|
||||
self.unparse_expr(key, precedence::MAX);
|
||||
self.p(": ");
|
||||
self.unparse_expr(value, precedence::COMPREHENSION_ELEMENT);
|
||||
self.unparse_expr(value, precedence::MAX);
|
||||
self.unparse_comp(generators);
|
||||
self.p("}");
|
||||
}
|
||||
@@ -1086,7 +1085,7 @@ impl<'a> Generator<'a> {
|
||||
range: _range,
|
||||
}) => {
|
||||
self.p("(");
|
||||
self.unparse_expr(elt, precedence::COMPREHENSION_ELEMENT);
|
||||
self.unparse_expr(elt, precedence::COMMA);
|
||||
self.unparse_comp(generators);
|
||||
self.p(")");
|
||||
}
|
||||
@@ -1571,8 +1570,6 @@ mod tests {
|
||||
assert_round_trip!("foo(1)");
|
||||
assert_round_trip!("foo(1, 2)");
|
||||
assert_round_trip!("foo(x for x in y)");
|
||||
assert_round_trip!("foo([x for x in y])");
|
||||
assert_round_trip!("foo([(x := 2) for x in y])");
|
||||
assert_round_trip!("x = yield 1");
|
||||
assert_round_trip!("return (yield 1)");
|
||||
assert_round_trip!("lambda: (1, 2, 3)");
|
||||
@@ -1625,8 +1622,8 @@ mod tests {
|
||||
r#"def f() -> (int, str):
|
||||
pass"#
|
||||
);
|
||||
assert_round_trip!("[await x async for x in y]");
|
||||
assert_round_trip!("[await i for i in b if await c]");
|
||||
assert_round_trip!("[(await x) async for x in y]");
|
||||
assert_round_trip!("[(await i) for i in b if await c]");
|
||||
assert_round_trip!("(await x async for x in y)");
|
||||
assert_round_trip!(
|
||||
r#"async def read_data(db):
|
||||
@@ -1722,18 +1719,6 @@ class Foo:
|
||||
pass"#
|
||||
);
|
||||
|
||||
assert_round_trip!(r#"[lambda n: n for n in range(10)]"#);
|
||||
assert_round_trip!(r#"[n[0:2] for n in range(10)]"#);
|
||||
assert_round_trip!(r#"[n[0] for n in range(10)]"#);
|
||||
assert_round_trip!(r#"[(n, n * 2) for n in range(10)]"#);
|
||||
assert_round_trip!(r#"[1 if n % 2 == 0 else 0 for n in range(10)]"#);
|
||||
assert_round_trip!(r#"[n % 2 == 0 or 0 for n in range(10)]"#);
|
||||
assert_round_trip!(r#"[(n := 2) for n in range(10)]"#);
|
||||
assert_round_trip!(r#"((n := 2) for n in range(10))"#);
|
||||
assert_round_trip!(r#"[n * 2 for n in range(10)]"#);
|
||||
assert_round_trip!(r#"{n * 2 for n in range(10)}"#);
|
||||
assert_round_trip!(r#"{i: n * 2 for i, n in enumerate(range(10))}"#);
|
||||
|
||||
// Type aliases
|
||||
assert_round_trip!(r#"type Foo = int | str"#);
|
||||
assert_round_trip!(r#"type Foo[T] = list[T]"#);
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
[
|
||||
{
|
||||
"quote_style": "double"
|
||||
},
|
||||
{
|
||||
"quote_style": "single"
|
||||
}
|
||||
]
|
||||
@@ -1,120 +0,0 @@
|
||||
b"' test"
|
||||
b'" test'
|
||||
|
||||
b"\" test"
|
||||
b'\' test'
|
||||
|
||||
# Prefer single quotes for string with more double quotes
|
||||
b"' \" \" '' \" \" '"
|
||||
|
||||
# Prefer double quotes for string with more single quotes
|
||||
b'\' " " \'\' " " \''
|
||||
|
||||
# Prefer double quotes for string with equal amount of single and double quotes
|
||||
b'" \' " " \'\''
|
||||
b"' \" '' \" \""
|
||||
|
||||
b"\\' \"\""
|
||||
b'\\\' ""'
|
||||
|
||||
|
||||
b"Test"
|
||||
B"Test"
|
||||
|
||||
rb"Test"
|
||||
Rb"Test"
|
||||
|
||||
b'This string will not include \
|
||||
backslashes or newline characters.'
|
||||
|
||||
if True:
|
||||
b'This string will not include \
|
||||
backslashes or newline characters.'
|
||||
|
||||
b"""Multiline
|
||||
String \"
|
||||
"""
|
||||
|
||||
b'''Multiline
|
||||
String \'
|
||||
'''
|
||||
|
||||
b'''Multiline
|
||||
String ""
|
||||
'''
|
||||
|
||||
b'''Multiline
|
||||
String """
|
||||
'''
|
||||
|
||||
b'''Multiline
|
||||
String "'''
|
||||
|
||||
b"""Multiline
|
||||
String '''
|
||||
"""
|
||||
|
||||
b"""Multiline
|
||||
String '"""
|
||||
|
||||
b'''Multiline
|
||||
String \"\"\"
|
||||
'''
|
||||
|
||||
# String continuation
|
||||
|
||||
b"Let's" b"start" b"with" b"a" b"simple" b"example"
|
||||
|
||||
b"Let's" b"start" b"with" b"a" b"simple" b"example" b"now repeat after me:" b"I am confident" b"I am confident" b"I am confident" b"I am confident" b"I am confident"
|
||||
|
||||
(
|
||||
b"Let's" b"start" b"with" b"a" b"simple" b"example" b"now repeat after me:" b"I am confident" b"I am confident" b"I am confident" b"I am confident" b"I am confident"
|
||||
)
|
||||
|
||||
if (
|
||||
a + b"Let's"
|
||||
b"start"
|
||||
b"with"
|
||||
b"a"
|
||||
b"simple"
|
||||
b"example"
|
||||
b"now repeat after me:"
|
||||
b"I am confident"
|
||||
b"I am confident"
|
||||
b"I am confident"
|
||||
b"I am confident"
|
||||
b"I am confident"
|
||||
):
|
||||
pass
|
||||
|
||||
if b"Let's" b"start" b"with" b"a" b"simple" b"example" b"now repeat after me:" b"I am confident" b"I am confident" b"I am confident" b"I am confident" b"I am confident":
|
||||
pass
|
||||
|
||||
(
|
||||
# leading
|
||||
b"a" # trailing part comment
|
||||
|
||||
# leading part comment
|
||||
|
||||
b"b" # trailing second part comment
|
||||
# trailing
|
||||
)
|
||||
|
||||
test_particular = [
|
||||
# squares
|
||||
b'1.00000000100000000025',
|
||||
b'1.0000000000000000000000000100000000000000000000000' #...
|
||||
b'00025',
|
||||
b'1.0000000000000000000000000000000000000000000010000' #...
|
||||
b'0000000000000000000000000000000000000000025',
|
||||
]
|
||||
|
||||
# Parenthesized string continuation with messed up indentation
|
||||
{
|
||||
"key": (
|
||||
[],
|
||||
b'a'
|
||||
b'b'
|
||||
b'c'
|
||||
)
|
||||
}
|
||||
@@ -11,28 +11,3 @@ if (
|
||||
y0 = (y1 := f(x))
|
||||
|
||||
f(x:=y, z=True)
|
||||
|
||||
assert (x := 1)
|
||||
|
||||
|
||||
def f():
|
||||
return (x := 1)
|
||||
|
||||
|
||||
for x in (y := [1, 2, 3]):
|
||||
pass
|
||||
|
||||
async for x in (y := [1, 2, 3]):
|
||||
pass
|
||||
|
||||
del (x := 1)
|
||||
|
||||
try:
|
||||
pass
|
||||
except (e := Exception):
|
||||
if x := 1:
|
||||
pass
|
||||
|
||||
(x := 1)
|
||||
|
||||
(x := 1) + (y := 2)
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
def f():
|
||||
global x, y, z
|
||||
|
||||
|
||||
def f():
|
||||
# leading comment
|
||||
global x, y, z # end-of-line comment
|
||||
# trailing comment
|
||||
|
||||
|
||||
def f():
|
||||
global analyze_featuremap_layer, analyze_featuremapcompression_layer, analyze_latencies_post, analyze_motions_layer, analyze_size_model
|
||||
@@ -1,12 +0,0 @@
|
||||
def f():
|
||||
nonlocal x, y, z
|
||||
|
||||
|
||||
def f():
|
||||
# leading comment
|
||||
nonlocal x, y, z # end-of-line comment
|
||||
# trailing comment
|
||||
|
||||
|
||||
def f():
|
||||
nonlocal analyze_featuremap_layer, analyze_featuremapcompression_layer, analyze_latencies_post, analyze_motions_layer, analyze_size_model
|
||||
@@ -202,45 +202,14 @@ impl<'fmt, 'ast, 'buf> JoinNodesBuilder<'fmt, 'ast, 'buf> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum Entries {
|
||||
/// No previous entry
|
||||
None,
|
||||
/// One previous ending at the given position.
|
||||
One(TextSize),
|
||||
/// More than one entry, the last one ending at the specific position.
|
||||
MoreThanOne(TextSize),
|
||||
}
|
||||
|
||||
impl Entries {
|
||||
fn position(self) -> Option<TextSize> {
|
||||
match self {
|
||||
Entries::None => None,
|
||||
Entries::One(position) | Entries::MoreThanOne(position) => Some(position),
|
||||
}
|
||||
}
|
||||
|
||||
const fn is_one_or_more(self) -> bool {
|
||||
!matches!(self, Entries::None)
|
||||
}
|
||||
|
||||
const fn is_more_than_one(self) -> bool {
|
||||
matches!(self, Entries::MoreThanOne(_))
|
||||
}
|
||||
|
||||
const fn next(self, end_position: TextSize) -> Self {
|
||||
match self {
|
||||
Entries::None => Entries::One(end_position),
|
||||
Entries::One(_) | Entries::MoreThanOne(_) => Entries::MoreThanOne(end_position),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
|
||||
result: FormatResult<()>,
|
||||
fmt: &'fmt mut PyFormatter<'ast, 'buf>,
|
||||
entries: Entries,
|
||||
end_of_last_entry: Option<TextSize>,
|
||||
sequence_end: TextSize,
|
||||
/// We need to track whether we have more than one entry since a sole entry doesn't get a
|
||||
/// magic trailing comma even when expanded
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
|
||||
@@ -248,7 +217,8 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
|
||||
Self {
|
||||
fmt: f,
|
||||
result: Ok(()),
|
||||
entries: Entries::None,
|
||||
end_of_last_entry: None,
|
||||
len: 0,
|
||||
sequence_end,
|
||||
}
|
||||
}
|
||||
@@ -275,11 +245,12 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
|
||||
Separator: Format<PyFormatContext<'ast>>,
|
||||
{
|
||||
self.result = self.result.and_then(|_| {
|
||||
if self.entries.is_one_or_more() {
|
||||
if self.end_of_last_entry.is_some() {
|
||||
write!(self.fmt, [text(","), separator])?;
|
||||
}
|
||||
|
||||
self.entries = self.entries.next(node.end());
|
||||
self.end_of_last_entry = Some(node.end());
|
||||
self.len += 1;
|
||||
|
||||
content.fmt(self.fmt)
|
||||
});
|
||||
@@ -315,7 +286,7 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
|
||||
|
||||
pub(crate) fn finish(&mut self) -> FormatResult<()> {
|
||||
self.result.and_then(|_| {
|
||||
if let Some(last_end) = self.entries.position() {
|
||||
if let Some(last_end) = self.end_of_last_entry.take() {
|
||||
let magic_trailing_comma = match self.fmt.options().magic_trailing_comma() {
|
||||
MagicTrailingComma::Respect => {
|
||||
let first_token = SimpleTokenizer::new(
|
||||
@@ -339,7 +310,7 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
|
||||
|
||||
// If there is a single entry, only keep the magic trailing comma, don't add it if
|
||||
// it wasn't there. If there is more than one entry, always add it.
|
||||
if magic_trailing_comma || self.entries.is_more_than_one() {
|
||||
if magic_trailing_comma || self.len > 1 {
|
||||
if_group_breaks(&text(",")).fmt(self.fmt)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -297,7 +297,7 @@ fn handle_own_line_comment_between_branches<'a>(
|
||||
|
||||
// It depends on the indentation level of the comment if it is a leading comment for the
|
||||
// following branch or if it a trailing comment of the previous body's last statement.
|
||||
let comment_indentation = indentation_at_offset(comment.slice().range().start(), locator)
|
||||
let comment_indentation = indentation_at_offset(locator, comment.slice().range().start())
|
||||
.unwrap_or_default()
|
||||
.len();
|
||||
|
||||
@@ -402,7 +402,7 @@ fn handle_match_comment<'a>(
|
||||
|
||||
let next_case = match_stmt.cases.get(current_case_index + 1);
|
||||
|
||||
let comment_indentation = indentation_at_offset(comment.slice().range().start(), locator)
|
||||
let comment_indentation = indentation_at_offset(locator, comment.slice().range().start())
|
||||
.unwrap_or_default()
|
||||
.len();
|
||||
let match_case_indentation = indentation(locator, match_case).unwrap().len();
|
||||
@@ -480,7 +480,7 @@ fn handle_own_line_comment_after_branch<'a>(
|
||||
|
||||
// We only care about the length because indentations with mixed spaces and tabs are only valid if
|
||||
// the indent-level doesn't depend on the tab width (the indent level must be the same if the tab width is 1 or 8).
|
||||
let comment_indentation = indentation_at_offset(comment.slice().range().start(), locator)
|
||||
let comment_indentation = indentation_at_offset(locator, comment.slice().range().start())
|
||||
.unwrap_or_default()
|
||||
.len();
|
||||
|
||||
@@ -493,7 +493,7 @@ fn handle_own_line_comment_after_branch<'a>(
|
||||
// # Trailing if comment
|
||||
// ```
|
||||
// Here we keep the comment a trailing comment of the `if`
|
||||
let preceding_indentation = indentation_at_offset(preceding_node.start(), locator)
|
||||
let preceding_indentation = indentation_at_offset(locator, preceding_node.start())
|
||||
.unwrap_or_default()
|
||||
.len();
|
||||
if comment_indentation == preceding_indentation {
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::expression::number::{FormatComplex, FormatFloat, FormatInt};
|
||||
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
|
||||
use crate::expression::string::{FormatString, StringLayout, StringPrefix, StringQuotes};
|
||||
use crate::prelude::*;
|
||||
use crate::FormatNodeRule;
|
||||
use crate::{not_yet_implemented_custom_text, FormatNodeRule};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatExprConstant {
|
||||
@@ -51,13 +51,16 @@ impl FormatNodeRule<ExprConstant> for FormatExprConstant {
|
||||
Constant::Int(_) => FormatInt::new(item).fmt(f),
|
||||
Constant::Float(_) => FormatFloat::new(item).fmt(f),
|
||||
Constant::Complex { .. } => FormatComplex::new(item).fmt(f),
|
||||
Constant::Str(_) | Constant::Bytes(_) => {
|
||||
Constant::Str(_) => {
|
||||
let string_layout = match self.layout {
|
||||
ExprConstantLayout::Default => StringLayout::Default,
|
||||
ExprConstantLayout::String(layout) => layout,
|
||||
};
|
||||
FormatString::new(item).with_layout(string_layout).fmt(f)
|
||||
}
|
||||
Constant::Bytes(_) => {
|
||||
not_yet_implemented_custom_text(r#"b"NOT_YET_IMPLEMENTED_BYTE_STRING""#).fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +79,7 @@ impl NeedsParentheses for ExprConstant {
|
||||
_parent: AnyNodeRef,
|
||||
context: &PyFormatContext,
|
||||
) -> OptionalParentheses {
|
||||
if self.value.is_str() || self.value.is_bytes() {
|
||||
if self.value.is_str() {
|
||||
let contents = context.locator().slice(self.range());
|
||||
// Don't wrap triple quoted strings
|
||||
if is_multiline_string(self, context.source()) || !is_implicit_concatenation(contents) {
|
||||
@@ -91,7 +94,7 @@ impl NeedsParentheses for ExprConstant {
|
||||
}
|
||||
|
||||
pub(super) fn is_multiline_string(constant: &ExprConstant, source: &str) -> bool {
|
||||
if constant.value.is_str() || constant.value.is_bytes() {
|
||||
if constant.value.is_str() {
|
||||
let contents = &source[constant.range()];
|
||||
let prefix = StringPrefix::parse(contents);
|
||||
let quotes =
|
||||
|
||||
@@ -32,25 +32,11 @@ impl FormatNodeRule<ExprNamedExpr> for FormatExprNamedExpr {
|
||||
impl NeedsParentheses for ExprNamedExpr {
|
||||
fn needs_parentheses(
|
||||
&self,
|
||||
parent: AnyNodeRef,
|
||||
_parent: AnyNodeRef,
|
||||
_context: &PyFormatContext,
|
||||
) -> OptionalParentheses {
|
||||
// Unlike tuples, named expression parentheses are not part of the range even when
|
||||
// mandatory. See [PEP 572](https://peps.python.org/pep-0572/) for details.
|
||||
if parent.is_stmt_ann_assign()
|
||||
|| parent.is_stmt_assign()
|
||||
|| parent.is_stmt_aug_assign()
|
||||
|| parent.is_stmt_assert()
|
||||
|| parent.is_stmt_return()
|
||||
|| parent.is_except_handler_except_handler()
|
||||
|| parent.is_with_item()
|
||||
|| parent.is_stmt_delete()
|
||||
|| parent.is_stmt_for()
|
||||
|| parent.is_stmt_async_for()
|
||||
{
|
||||
OptionalParentheses::Always
|
||||
} else {
|
||||
OptionalParentheses::Never
|
||||
}
|
||||
OptionalParentheses::Always
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ impl FormatNodeRule<ExprSubscript> for FormatExprSubscript {
|
||||
let dangling_comments = comments.dangling_comments(item.as_any_node_ref());
|
||||
debug_assert!(
|
||||
dangling_comments.len() <= 1,
|
||||
"A subscript expression can only have a single dangling comment, the one after the bracket"
|
||||
"The subscript expression must have at most a single comment, the one after the bracket"
|
||||
);
|
||||
|
||||
if let NodeLevel::Expression(Some(group_id)) = f.context().node_level() {
|
||||
|
||||
@@ -1,41 +1,36 @@
|
||||
use crate::context::PyFormatContext;
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parenthesize};
|
||||
use crate::prelude::*;
|
||||
use crate::{FormatNodeRule, PyFormatter};
|
||||
use ruff_formatter::write;
|
||||
use ruff_formatter::prelude::{space, text};
|
||||
use ruff_formatter::{write, Buffer, FormatResult};
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::{Expr, ExprYield, ExprYieldFrom, Ranged};
|
||||
use ruff_text_size::TextRange;
|
||||
use ruff_python_ast::ExprYield;
|
||||
|
||||
pub(super) enum AnyExpressionYield<'a> {
|
||||
Yield(&'a ExprYield),
|
||||
YieldFrom(&'a ExprYieldFrom),
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct FormatExprYield;
|
||||
|
||||
impl<'a> AnyExpressionYield<'a> {
|
||||
const fn is_yield_from(&self) -> bool {
|
||||
matches!(self, AnyExpressionYield::YieldFrom(_))
|
||||
}
|
||||
impl FormatNodeRule<ExprYield> for FormatExprYield {
|
||||
fn fmt_fields(&self, item: &ExprYield, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let ExprYield { range: _, value } = item;
|
||||
|
||||
fn value(&self) -> Option<&Expr> {
|
||||
match self {
|
||||
AnyExpressionYield::Yield(yld) => yld.value.as_deref(),
|
||||
AnyExpressionYield::YieldFrom(yld) => Some(&yld.value),
|
||||
if let Some(val) = value {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
text("yield"),
|
||||
space(),
|
||||
maybe_parenthesize_expression(val, item, Parenthesize::IfRequired)
|
||||
]
|
||||
)?;
|
||||
} else {
|
||||
write!(f, [&text("yield")])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for AnyExpressionYield<'_> {
|
||||
fn range(&self) -> TextRange {
|
||||
match self {
|
||||
AnyExpressionYield::Yield(yld) => yld.range(),
|
||||
AnyExpressionYield::YieldFrom(yld) => yld.range(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NeedsParentheses for AnyExpressionYield<'_> {
|
||||
impl NeedsParentheses for ExprYield {
|
||||
fn needs_parentheses(
|
||||
&self,
|
||||
parent: AnyNodeRef,
|
||||
@@ -54,68 +49,3 @@ impl NeedsParentheses for AnyExpressionYield<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ExprYield> for AnyExpressionYield<'a> {
|
||||
fn from(value: &'a ExprYield) -> Self {
|
||||
AnyExpressionYield::Yield(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ExprYieldFrom> for AnyExpressionYield<'a> {
|
||||
fn from(value: &'a ExprYieldFrom) -> Self {
|
||||
AnyExpressionYield::YieldFrom(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&AnyExpressionYield<'a>> for AnyNodeRef<'a> {
|
||||
fn from(value: &AnyExpressionYield<'a>) -> Self {
|
||||
match value {
|
||||
AnyExpressionYield::Yield(yld) => AnyNodeRef::ExprYield(yld),
|
||||
AnyExpressionYield::YieldFrom(yld) => AnyNodeRef::ExprYieldFrom(yld),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Format<PyFormatContext<'_>> for AnyExpressionYield<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
|
||||
let keyword = if self.is_yield_from() {
|
||||
"yield from"
|
||||
} else {
|
||||
"yield"
|
||||
};
|
||||
|
||||
if let Some(val) = self.value() {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
text(keyword),
|
||||
space(),
|
||||
maybe_parenthesize_expression(val, self, Parenthesize::IfRequired)
|
||||
]
|
||||
)?;
|
||||
} else {
|
||||
// ExprYieldFrom always has Some(value) so we should never get a bare `yield from`
|
||||
write!(f, [&text(keyword)])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatExprYield;
|
||||
|
||||
impl FormatNodeRule<ExprYield> for FormatExprYield {
|
||||
fn fmt_fields(&self, item: &ExprYield, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
AnyExpressionYield::from(item).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl NeedsParentheses for ExprYield {
|
||||
fn needs_parentheses(
|
||||
&self,
|
||||
parent: AnyNodeRef,
|
||||
context: &PyFormatContext,
|
||||
) -> OptionalParentheses {
|
||||
AnyExpressionYield::from(self).needs_parentheses(parent, context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::expression::expr_yield::AnyExpressionYield;
|
||||
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
|
||||
use crate::prelude::PyFormatContext;
|
||||
use crate::context::PyFormatContext;
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parenthesize};
|
||||
use crate::{FormatNodeRule, PyFormatter};
|
||||
use ruff_formatter::{Format, FormatResult};
|
||||
use ruff_formatter::prelude::{space, text};
|
||||
use ruff_formatter::{write, Buffer, FormatResult};
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::ExprYieldFrom;
|
||||
|
||||
@@ -11,7 +12,18 @@ pub struct FormatExprYieldFrom;
|
||||
|
||||
impl FormatNodeRule<ExprYieldFrom> for FormatExprYieldFrom {
|
||||
fn fmt_fields(&self, item: &ExprYieldFrom, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
AnyExpressionYield::from(item).fmt(f)
|
||||
let ExprYieldFrom { range: _, value } = item;
|
||||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
text("yield from"),
|
||||
space(),
|
||||
maybe_parenthesize_expression(value, item, Parenthesize::IfRequired)
|
||||
]
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +31,18 @@ impl NeedsParentheses for ExprYieldFrom {
|
||||
fn needs_parentheses(
|
||||
&self,
|
||||
parent: AnyNodeRef,
|
||||
context: &PyFormatContext,
|
||||
_context: &PyFormatContext,
|
||||
) -> OptionalParentheses {
|
||||
AnyExpressionYield::from(self).needs_parentheses(parent, context)
|
||||
// According to https://docs.python.org/3/reference/grammar.html There are two situations
|
||||
// where we do not want to always parenthesize a yield expression:
|
||||
// 1. Right hand side of an assignment, e.g. `x = yield y`
|
||||
// 2. Yield statement, e.g. `def foo(): yield y`
|
||||
// We catch situation 1 below. Situation 2 does not need to be handled here as
|
||||
// FormatStmtExpr, does not add parenthesis
|
||||
if parent.is_stmt_assign() || parent.is_stmt_ann_assign() || parent.is_stmt_aug_assign() {
|
||||
OptionalParentheses::Multiline
|
||||
} else {
|
||||
OptionalParentheses::Always
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ pub enum StringLayout {
|
||||
|
||||
impl<'a> FormatString<'a> {
|
||||
pub(super) fn new(constant: &'a ExprConstant) -> Self {
|
||||
debug_assert!(constant.value.is_str() || constant.value.is_bytes());
|
||||
debug_assert!(constant.value.is_str());
|
||||
Self {
|
||||
constant,
|
||||
layout: StringLayout::Default,
|
||||
@@ -70,7 +70,7 @@ struct FormatStringContinuation<'a> {
|
||||
|
||||
impl<'a> FormatStringContinuation<'a> {
|
||||
fn new(constant: &'a ExprConstant) -> Self {
|
||||
debug_assert!(constant.value.is_str() || constant.value.is_bytes());
|
||||
debug_assert!(constant.value.is_str());
|
||||
Self { constant }
|
||||
}
|
||||
}
|
||||
@@ -203,17 +203,11 @@ impl Format<PyFormatContext<'_>> for FormatStringPart {
|
||||
let raw_content_range = relative_raw_content_range + self.part_range.start();
|
||||
|
||||
let raw_content = &string_content[relative_raw_content_range];
|
||||
let is_raw_string = prefix.is_raw_string();
|
||||
let preferred_quotes = if is_raw_string {
|
||||
preferred_quotes_raw(raw_content, quotes, f.options().quote_style())
|
||||
} else {
|
||||
preferred_quotes(raw_content, quotes, f.options().quote_style())
|
||||
};
|
||||
let preferred_quotes = preferred_quotes(raw_content, quotes, f.options().quote_style());
|
||||
|
||||
write!(f, [prefix, preferred_quotes])?;
|
||||
|
||||
let (normalized, contains_newlines) =
|
||||
normalize_string(raw_content, preferred_quotes, is_raw_string);
|
||||
let (normalized, contains_newlines) = normalize_string(raw_content, preferred_quotes);
|
||||
|
||||
match normalized {
|
||||
Cow::Borrowed(_) => {
|
||||
@@ -229,7 +223,7 @@ impl Format<PyFormatContext<'_>> for FormatStringPart {
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(super) struct StringPrefix: u8 {
|
||||
const UNICODE = 0b0000_0001;
|
||||
/// `r"test"`
|
||||
@@ -270,10 +264,6 @@ impl StringPrefix {
|
||||
pub(super) const fn text_len(self) -> TextSize {
|
||||
TextSize::new(self.bits().count_ones())
|
||||
}
|
||||
|
||||
pub(super) const fn is_raw_string(self) -> bool {
|
||||
matches!(self, StringPrefix::RAW | StringPrefix::RAW_UPPER)
|
||||
}
|
||||
}
|
||||
|
||||
impl Format<PyFormatContext<'_>> for StringPrefix {
|
||||
@@ -300,54 +290,6 @@ impl Format<PyFormatContext<'_>> for StringPrefix {
|
||||
}
|
||||
}
|
||||
|
||||
/// Detects the preferred quotes for raw string `input`.
|
||||
/// The configured quote style is preferred unless `input` contains unescaped quotes of the
|
||||
/// configured style. For example, `r"foo"` is preferred over `r'foo'` if the configured
|
||||
/// quote style is double quotes.
|
||||
fn preferred_quotes_raw(
|
||||
input: &str,
|
||||
quotes: StringQuotes,
|
||||
configured_style: QuoteStyle,
|
||||
) -> StringQuotes {
|
||||
let configured_quote_char = configured_style.as_char();
|
||||
let mut chars = input.chars().peekable();
|
||||
let contains_unescaped_configured_quotes = loop {
|
||||
match chars.next() {
|
||||
Some('\\') => {
|
||||
// Ignore escaped characters
|
||||
chars.next();
|
||||
}
|
||||
// `"` or `'`
|
||||
Some(c) if c == configured_quote_char => {
|
||||
if !quotes.triple {
|
||||
break true;
|
||||
}
|
||||
|
||||
if chars.peek() == Some(&configured_quote_char) {
|
||||
// `""` or `''`
|
||||
chars.next();
|
||||
|
||||
if chars.peek() == Some(&configured_quote_char) {
|
||||
// `"""` or `'''`
|
||||
break true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(_) => continue,
|
||||
None => break false,
|
||||
}
|
||||
};
|
||||
|
||||
StringQuotes {
|
||||
triple: quotes.triple,
|
||||
style: if contains_unescaped_configured_quotes {
|
||||
quotes.style
|
||||
} else {
|
||||
configured_style
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Detects the preferred quotes for `input`.
|
||||
/// * single quoted strings: The preferred quote style is the one that requires less escape sequences.
|
||||
/// * triple quoted strings: Use double quotes except the string contains a sequence of `"""`.
|
||||
@@ -492,11 +434,7 @@ impl Format<PyFormatContext<'_>> for StringQuotes {
|
||||
/// with the provided `style`.
|
||||
///
|
||||
/// Returns the normalized string and whether it contains new lines.
|
||||
fn normalize_string(
|
||||
input: &str,
|
||||
quotes: StringQuotes,
|
||||
is_raw: bool,
|
||||
) -> (Cow<str>, ContainsNewlines) {
|
||||
fn normalize_string(input: &str, quotes: StringQuotes) -> (Cow<str>, ContainsNewlines) {
|
||||
// The normalized string if `input` is not yet normalized.
|
||||
// `output` must remain empty if `input` is already normalized.
|
||||
let mut output = String::new();
|
||||
@@ -529,7 +467,7 @@ fn normalize_string(
|
||||
newlines = ContainsNewlines::Yes;
|
||||
} else if c == '\n' {
|
||||
newlines = ContainsNewlines::Yes;
|
||||
} else if !quotes.triple && !is_raw {
|
||||
} else if !quotes.triple {
|
||||
if c == '\\' {
|
||||
if let Some(next) = input.as_bytes().get(index + 1).copied().map(char::from) {
|
||||
#[allow(clippy::if_same_then_else)]
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
use ruff_formatter::{format_args, write};
|
||||
use crate::{not_yet_implemented, FormatNodeRule, PyFormatter};
|
||||
use ruff_formatter::{write, Buffer, FormatResult};
|
||||
use ruff_python_ast::StmtGlobal;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::FormatNodeRule;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtGlobal;
|
||||
|
||||
impl FormatNodeRule<StmtGlobal> for FormatStmtGlobal {
|
||||
fn fmt_fields(&self, item: &StmtGlobal, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
write!(f, [text("global"), space()])?;
|
||||
|
||||
f.join_with(format_args![text(","), space()])
|
||||
.entries(item.names.iter().formatted())
|
||||
.finish()
|
||||
write!(f, [not_yet_implemented(item)])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
use ruff_formatter::{format_args, write};
|
||||
use crate::{not_yet_implemented, FormatNodeRule, PyFormatter};
|
||||
use ruff_formatter::{write, Buffer, FormatResult};
|
||||
use ruff_python_ast::StmtNonlocal;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::FormatNodeRule;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtNonlocal;
|
||||
|
||||
impl FormatNodeRule<StmtNonlocal> for FormatStmtNonlocal {
|
||||
fn fmt_fields(&self, item: &StmtNonlocal, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
write!(f, [text("nonlocal"), space()])?;
|
||||
|
||||
f.join_with(format_args![text(","), space()])
|
||||
.entries(item.names.iter().formatted())
|
||||
.finish()
|
||||
write!(f, [not_yet_implemented(item)])
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user