-
Notifications
You must be signed in to change notification settings - Fork 50
/
ruff.py
125 lines (93 loc) · 2.8 KB
/
ruff.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
from __future__ import annotations
from typing import Any, ClassVar, Protocol
from .._compat.importlib.resources.abc import Traversable
from . import mk_url
## R0xx: Ruff general
## R1xx: Ruff checks
class Ruff:
family = "ruff"
url = mk_url("style")
class RF001(Ruff):
"Has Ruff config"
requires = {"PY001"}
@staticmethod
def check(pyproject: dict[str, Any]) -> bool:
"""
Must have `tool.ruff` section in `pyproject.toml`. Other forms of
configuration are not supported by this check.
"""
match pyproject:
case {"tool": {"ruff": object()}}:
return True
case _:
return False
class RF002(Ruff):
"Target version must be set"
requires = {"RF001"}
@staticmethod
def check(pyproject: dict[str, Any]) -> bool:
"""
Must select a minimum version to target. Affects pyupgrade,
isort, and others.
"""
match pyproject:
case {"tool": {"ruff": {"target-version": str()}}}:
return True
case _:
return False
class RF003(Ruff):
"src directory specified if used"
requires = {"RF001"}
@staticmethod
def check(pyproject: dict[str, Any], package: Traversable) -> bool | None:
"""
Must specify `src` directory if it exists.
```toml
src = ["src"]
```
"""
if not package.joinpath("src").is_dir():
return None
match pyproject:
case {"tool": {"ruff": {"src": list(x)}}}:
return "src" in x
case _:
return False
class RuffMixin(Protocol):
code: ClassVar[str]
name: ClassVar[str]
class RF1xx(Ruff):
family = "ruff"
requires = {"RF001"}
@classmethod
def check(cls: type[RuffMixin], pyproject: dict[str, Any]) -> bool:
"""
Must select the {cls.name} `{cls.code}` checks. Recommended:
```toml
select = ["{cls.code}"] # {cls.name}
```
"""
match pyproject:
case {"tool": {"ruff": {"select": list(x)}}} | {
"tool": {"ruff": {"extend-select": list(x)}}
}:
return cls.code in x
case _:
return False
class RF101(RF1xx):
"Bugbear must be selected"
code = "B"
name = "flake8-bugbear"
class RF102(RF1xx):
"isort must be selected"
code = "I"
name = "isort"
class RF103(RF1xx):
"pyupgrade must be selected"
code = "UP"
name = "pyupgrade"
def repo_review_checks() -> dict[str, Ruff]:
base_classes = set(Ruff.__subclasses__()) - {RF1xx}
rf1xx_classes = set(RF1xx.__subclasses__())
repo_review_checks = base_classes | rf1xx_classes
return {p.__name__: p() for p in repo_review_checks}