flake8_simplify 0.20.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flake8_simplify/__init__.py +177 -0
- flake8_simplify/constants.py +7 -0
- flake8_simplify/rules/__init__.py +0 -0
- flake8_simplify/rules/ast_assign.py +116 -0
- flake8_simplify/rules/ast_bool_op.py +219 -0
- flake8_simplify/rules/ast_call.py +235 -0
- flake8_simplify/rules/ast_classdef.py +24 -0
- flake8_simplify/rules/ast_compare.py +90 -0
- flake8_simplify/rules/ast_expr.py +103 -0
- flake8_simplify/rules/ast_for.py +207 -0
- flake8_simplify/rules/ast_if.py +515 -0
- flake8_simplify/rules/ast_ifexp.py +68 -0
- flake8_simplify/rules/ast_subscript.py +59 -0
- flake8_simplify/rules/ast_try.py +96 -0
- flake8_simplify/rules/ast_unary_op.py +106 -0
- flake8_simplify/rules/ast_with.py +62 -0
- flake8_simplify/utils.py +247 -0
- flake8_simplify-0.20.0.dist-info/LICENSE +21 -0
- flake8_simplify-0.20.0.dist-info/METADATA +656 -0
- flake8_simplify-0.20.0.dist-info/RECORD +22 -0
- flake8_simplify-0.20.0.dist-info/WHEEL +4 -0
- flake8_simplify-0.20.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Core Library
|
|
2
|
+
import ast
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from typing import List, Tuple
|
|
6
|
+
|
|
7
|
+
# First party
|
|
8
|
+
from flake8_simplify.constants import BOOL_CONST_TYPES
|
|
9
|
+
from flake8_simplify.utils import Call, to_source
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_sim115(node: Call) -> List[Tuple[int, int, str]]:
|
|
15
|
+
"""
|
|
16
|
+
Find places where open() is called without a context handler.
|
|
17
|
+
|
|
18
|
+
Example AST
|
|
19
|
+
-----------
|
|
20
|
+
Assign(
|
|
21
|
+
targets=[Name(id='f', ctx=Store())],
|
|
22
|
+
value=Call(
|
|
23
|
+
func=Name(id='open', ctx=Load()),
|
|
24
|
+
args=[Constant(value=Ellipsis, kind=None)],
|
|
25
|
+
keywords=[],
|
|
26
|
+
),
|
|
27
|
+
type_comment=None,
|
|
28
|
+
)
|
|
29
|
+
...
|
|
30
|
+
Expr(
|
|
31
|
+
value=Call(
|
|
32
|
+
func=Attribute(
|
|
33
|
+
value=Name(id='f', ctx=Load()),
|
|
34
|
+
attr='close',
|
|
35
|
+
ctx=Load(),
|
|
36
|
+
),
|
|
37
|
+
args=[],
|
|
38
|
+
keywords=[],
|
|
39
|
+
),
|
|
40
|
+
),
|
|
41
|
+
"""
|
|
42
|
+
RULE = "SIM115 Use context handler for opening files"
|
|
43
|
+
errors: List[Tuple[int, int, str]] = []
|
|
44
|
+
if not (
|
|
45
|
+
isinstance(node.func, ast.Name)
|
|
46
|
+
and node.func.id == "open"
|
|
47
|
+
and not isinstance(node.parent, ast.withitem)
|
|
48
|
+
):
|
|
49
|
+
return errors
|
|
50
|
+
errors.append((node.lineno, node.col_offset, RULE))
|
|
51
|
+
return errors
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# Experimental rules
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_sim901(node: ast.Call) -> List[Tuple[int, int, str]]:
|
|
58
|
+
"""
|
|
59
|
+
Get a list of all calls of the type "bool(comparison)".
|
|
60
|
+
|
|
61
|
+
Call(
|
|
62
|
+
func=Name(id='bool', ctx=Load()),
|
|
63
|
+
args=[
|
|
64
|
+
Compare(
|
|
65
|
+
left=Name(id='a', ctx=Load()),
|
|
66
|
+
ops=[Eq()],
|
|
67
|
+
comparators=[Name(id='b', ctx=Load())],
|
|
68
|
+
),
|
|
69
|
+
],
|
|
70
|
+
keywords=[],
|
|
71
|
+
)
|
|
72
|
+
"""
|
|
73
|
+
RULE = "SIM901 Use '{expected}' instead of '{actual}'"
|
|
74
|
+
errors: List[Tuple[int, int, str]] = []
|
|
75
|
+
if not (
|
|
76
|
+
isinstance(node.func, ast.Name)
|
|
77
|
+
and node.func.id == "bool"
|
|
78
|
+
and len(node.args) == 1
|
|
79
|
+
and isinstance(node.args[0], ast.Compare)
|
|
80
|
+
):
|
|
81
|
+
return errors
|
|
82
|
+
|
|
83
|
+
actual = to_source(node)
|
|
84
|
+
expected = to_source(node.args[0])
|
|
85
|
+
|
|
86
|
+
errors.append(
|
|
87
|
+
(
|
|
88
|
+
node.lineno,
|
|
89
|
+
node.col_offset,
|
|
90
|
+
RULE.format(actual=actual, expected=expected),
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
return errors
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def get_sim905(node: ast.Call) -> List[Tuple[int, int, str]]:
|
|
97
|
+
RULE = "SIM905 Use '{expected}' instead of '{actual}'"
|
|
98
|
+
errors: List[Tuple[int, int, str]] = []
|
|
99
|
+
if not (
|
|
100
|
+
isinstance(node.func, ast.Attribute)
|
|
101
|
+
and node.func.attr == "split"
|
|
102
|
+
and isinstance(node.func.value, (ast.Str, ast.Constant))
|
|
103
|
+
):
|
|
104
|
+
return errors
|
|
105
|
+
|
|
106
|
+
if isinstance(node.func.value, ast.Constant):
|
|
107
|
+
value = node.func.value.value
|
|
108
|
+
else:
|
|
109
|
+
value = node.func.value.s
|
|
110
|
+
|
|
111
|
+
expected = json.dumps(value.split())
|
|
112
|
+
actual = to_source(node.func.value) + ".split()"
|
|
113
|
+
errors.append(
|
|
114
|
+
(
|
|
115
|
+
node.lineno,
|
|
116
|
+
node.col_offset,
|
|
117
|
+
RULE.format(expected=expected, actual=actual),
|
|
118
|
+
)
|
|
119
|
+
)
|
|
120
|
+
return errors
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def get_sim906(node: ast.Call) -> List[Tuple[int, int, str]]:
|
|
124
|
+
RULE = "SIM906 Use '{expected}' instead of '{actual}'"
|
|
125
|
+
errors: List[Tuple[int, int, str]] = []
|
|
126
|
+
if not (
|
|
127
|
+
isinstance(node.func, ast.Attribute)
|
|
128
|
+
and isinstance(node.func.value, ast.Attribute)
|
|
129
|
+
and isinstance(node.func.value.value, ast.Name)
|
|
130
|
+
and node.func.value.value.id == "os"
|
|
131
|
+
and node.func.value.attr == "path"
|
|
132
|
+
and node.func.attr == "join"
|
|
133
|
+
and len(node.args) == 2
|
|
134
|
+
and any(
|
|
135
|
+
(
|
|
136
|
+
isinstance(arg, ast.Call)
|
|
137
|
+
and isinstance(arg.func, ast.Attribute)
|
|
138
|
+
and isinstance(arg.func.value, ast.Attribute)
|
|
139
|
+
and isinstance(arg.func.value.value, ast.Name)
|
|
140
|
+
and arg.func.value.value.id == "os"
|
|
141
|
+
and arg.func.value.attr == "path"
|
|
142
|
+
and arg.func.attr == "join"
|
|
143
|
+
)
|
|
144
|
+
for arg in node.args
|
|
145
|
+
)
|
|
146
|
+
):
|
|
147
|
+
return errors
|
|
148
|
+
|
|
149
|
+
def get_os_path_join_args(node: ast.Call) -> List[str]:
|
|
150
|
+
names: List[str] = []
|
|
151
|
+
for arg in node.args:
|
|
152
|
+
if (
|
|
153
|
+
isinstance(arg, ast.Call)
|
|
154
|
+
and isinstance(arg.func, ast.Attribute)
|
|
155
|
+
and isinstance(arg.func.value, ast.Attribute)
|
|
156
|
+
and isinstance(arg.func.value.value, ast.Name)
|
|
157
|
+
and arg.func.value.value.id == "os"
|
|
158
|
+
and arg.func.value.attr == "path"
|
|
159
|
+
and arg.func.attr == "join"
|
|
160
|
+
):
|
|
161
|
+
names = names + get_os_path_join_args(arg)
|
|
162
|
+
elif isinstance(arg, ast.Name):
|
|
163
|
+
names.append(arg.id)
|
|
164
|
+
elif isinstance(arg, ast.Str):
|
|
165
|
+
names.append(f"'{arg.s}'")
|
|
166
|
+
else:
|
|
167
|
+
logger.debug(
|
|
168
|
+
f"Unexpected os.path.join arg: {arg} -- {to_source(arg)}"
|
|
169
|
+
)
|
|
170
|
+
return names
|
|
171
|
+
|
|
172
|
+
names = get_os_path_join_args(node)
|
|
173
|
+
|
|
174
|
+
actual = to_source(node)
|
|
175
|
+
expected = f"os.path.join({', '.join(names)})"
|
|
176
|
+
errors.append(
|
|
177
|
+
(
|
|
178
|
+
node.lineno,
|
|
179
|
+
node.col_offset,
|
|
180
|
+
RULE.format(actual=actual, expected=expected),
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
return errors
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def get_sim910(node: Call) -> List[Tuple[int, int, str]]:
|
|
187
|
+
"""
|
|
188
|
+
Get a list of all usages of "dict.get(key, None)"
|
|
189
|
+
|
|
190
|
+
Example AST
|
|
191
|
+
-----------
|
|
192
|
+
Expr(
|
|
193
|
+
value=Call(
|
|
194
|
+
func=Attribute(
|
|
195
|
+
value=Name(id='a_dict', ctx=Load()),
|
|
196
|
+
attr='get',
|
|
197
|
+
ctx=Load()
|
|
198
|
+
),
|
|
199
|
+
args=[
|
|
200
|
+
Name(id='key', ctx=Load()),
|
|
201
|
+
Constant(value=None)
|
|
202
|
+
],
|
|
203
|
+
keywords=[]
|
|
204
|
+
),
|
|
205
|
+
),
|
|
206
|
+
"""
|
|
207
|
+
RULE = "SIM910 Use '{expected}' instead of '{actual}'"
|
|
208
|
+
errors: List[Tuple[int, int, str]] = []
|
|
209
|
+
if not (
|
|
210
|
+
isinstance(node.func, ast.Attribute)
|
|
211
|
+
and node.func.attr == "get"
|
|
212
|
+
and isinstance(node.func.ctx, ast.Load)
|
|
213
|
+
):
|
|
214
|
+
return errors
|
|
215
|
+
|
|
216
|
+
# check the argument value
|
|
217
|
+
if not (
|
|
218
|
+
len(node.args) == 2
|
|
219
|
+
and isinstance(node.args[1], BOOL_CONST_TYPES)
|
|
220
|
+
and node.args[1].value is None
|
|
221
|
+
):
|
|
222
|
+
return errors
|
|
223
|
+
|
|
224
|
+
actual = to_source(node)
|
|
225
|
+
func = to_source(node.func)
|
|
226
|
+
key = to_source(node.args[0])
|
|
227
|
+
expected = f"{func}({key})"
|
|
228
|
+
errors.append(
|
|
229
|
+
(
|
|
230
|
+
node.lineno,
|
|
231
|
+
node.col_offset,
|
|
232
|
+
RULE.format(actual=actual, expected=expected),
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
return errors
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Core Library
|
|
2
|
+
import ast
|
|
3
|
+
from typing import List, Tuple
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_sim120(node: ast.ClassDef) -> List[Tuple[int, int, str]]:
|
|
7
|
+
"""
|
|
8
|
+
Get a list of all classes that inherit from object.
|
|
9
|
+
"""
|
|
10
|
+
RULE = (
|
|
11
|
+
"SIM120 Use 'class {classname}:' "
|
|
12
|
+
"instead of 'class {classname}(object):'"
|
|
13
|
+
)
|
|
14
|
+
errors: List[Tuple[int, int, str]] = []
|
|
15
|
+
if not (
|
|
16
|
+
len(node.bases) == 1
|
|
17
|
+
and isinstance(node.bases[0], ast.Name)
|
|
18
|
+
and node.bases[0].id == "object"
|
|
19
|
+
):
|
|
20
|
+
return errors
|
|
21
|
+
errors.append(
|
|
22
|
+
(node.lineno, node.col_offset, RULE.format(classname=node.name))
|
|
23
|
+
)
|
|
24
|
+
return errors
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Core Library
|
|
2
|
+
import ast
|
|
3
|
+
from typing import List, Tuple
|
|
4
|
+
|
|
5
|
+
# First party
|
|
6
|
+
from flake8_simplify.constants import AST_CONST_TYPES
|
|
7
|
+
from flake8_simplify.utils import to_source
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_sim118(node: ast.Compare) -> List[Tuple[int, int, str]]:
|
|
11
|
+
"""
|
|
12
|
+
Get a list of all usages of "key in dict.keys()"
|
|
13
|
+
|
|
14
|
+
Compare(left=Name(id='key', ctx=Load()),
|
|
15
|
+
ops=[In()],
|
|
16
|
+
comparators=[
|
|
17
|
+
Call(
|
|
18
|
+
func=Attribute(
|
|
19
|
+
value=Name(id='dict', ctx=Load()),
|
|
20
|
+
attr='keys',
|
|
21
|
+
ctx=Load(),
|
|
22
|
+
),
|
|
23
|
+
args=[],
|
|
24
|
+
keywords=[],
|
|
25
|
+
),
|
|
26
|
+
],
|
|
27
|
+
)
|
|
28
|
+
"""
|
|
29
|
+
RULE = "SIM118 Use '{el} in {dict}' instead of '{el} in {dict}.keys()'"
|
|
30
|
+
errors: List[Tuple[int, int, str]] = []
|
|
31
|
+
if not (
|
|
32
|
+
len(node.ops) == 1
|
|
33
|
+
and isinstance(node.ops[0], ast.In)
|
|
34
|
+
and len(node.comparators) == 1
|
|
35
|
+
):
|
|
36
|
+
return errors
|
|
37
|
+
call_node = node.comparators[0]
|
|
38
|
+
if not isinstance(call_node, ast.Call):
|
|
39
|
+
return errors
|
|
40
|
+
|
|
41
|
+
attr_node = call_node.func
|
|
42
|
+
if not (
|
|
43
|
+
isinstance(call_node.func, ast.Attribute)
|
|
44
|
+
and call_node.func.attr == "keys"
|
|
45
|
+
and isinstance(call_node.func.ctx, ast.Load)
|
|
46
|
+
):
|
|
47
|
+
return errors
|
|
48
|
+
assert isinstance(attr_node, ast.Attribute), "hint for mypy" # noqa
|
|
49
|
+
|
|
50
|
+
key_str = to_source(node.left)
|
|
51
|
+
dict_str = to_source(attr_node.value)
|
|
52
|
+
errors.append(
|
|
53
|
+
(
|
|
54
|
+
node.lineno,
|
|
55
|
+
node.col_offset,
|
|
56
|
+
RULE.format(el=key_str, dict=dict_str),
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
return errors
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_sim300(node: ast.Compare) -> List[Tuple[int, int, str]]:
|
|
63
|
+
"""
|
|
64
|
+
Get a list of all Yoda conditions.
|
|
65
|
+
|
|
66
|
+
Compare(
|
|
67
|
+
left=Constant(value='Yoda', kind=None),
|
|
68
|
+
ops=[Eq()],
|
|
69
|
+
comparators=[Name(id='i_am', ctx=Load())],
|
|
70
|
+
)
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
RULE = (
|
|
74
|
+
"SIM300 Use '{right} == {left}' instead of "
|
|
75
|
+
"'{left} == {right}' (Yoda-conditions)"
|
|
76
|
+
)
|
|
77
|
+
errors: List[Tuple[int, int, str]] = []
|
|
78
|
+
if not (
|
|
79
|
+
isinstance(node.left, AST_CONST_TYPES)
|
|
80
|
+
and len(node.ops) == 1
|
|
81
|
+
and isinstance(node.ops[0], ast.Eq)
|
|
82
|
+
):
|
|
83
|
+
return errors
|
|
84
|
+
|
|
85
|
+
left = to_source(node.left)
|
|
86
|
+
right = to_source(node.comparators[0])
|
|
87
|
+
errors.append(
|
|
88
|
+
(node.lineno, node.col_offset, RULE.format(left=left, right=right))
|
|
89
|
+
)
|
|
90
|
+
return errors
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Core Library
|
|
2
|
+
import ast
|
|
3
|
+
from typing import List, Tuple
|
|
4
|
+
|
|
5
|
+
# First party
|
|
6
|
+
from flake8_simplify.constants import STR_TYPES
|
|
7
|
+
from flake8_simplify.utils import to_source
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_sim112(node: ast.Expr) -> List[Tuple[int, int, str]]:
|
|
11
|
+
"""
|
|
12
|
+
Find non-capitalized calls to environment variables.
|
|
13
|
+
|
|
14
|
+
os.environ["foo"]
|
|
15
|
+
Expr(
|
|
16
|
+
value=Subscript(
|
|
17
|
+
value=Attribute(
|
|
18
|
+
value=Name(id='os', ctx=Load()),
|
|
19
|
+
attr='environ',
|
|
20
|
+
ctx=Load(),
|
|
21
|
+
),
|
|
22
|
+
slice=Index(
|
|
23
|
+
value=Constant(value='foo', kind=None),
|
|
24
|
+
),
|
|
25
|
+
ctx=Load(),
|
|
26
|
+
),
|
|
27
|
+
),
|
|
28
|
+
"""
|
|
29
|
+
RULE = "SIM112 Use '{expected}' instead of '{original}'"
|
|
30
|
+
errors: List[Tuple[int, int, str]] = []
|
|
31
|
+
|
|
32
|
+
is_index_call = (
|
|
33
|
+
isinstance(node.value, ast.Subscript)
|
|
34
|
+
and isinstance(node.value.value, ast.Attribute)
|
|
35
|
+
and isinstance(node.value.value.value, ast.Name)
|
|
36
|
+
and node.value.value.value.id == "os"
|
|
37
|
+
and node.value.value.attr == "environ"
|
|
38
|
+
and (
|
|
39
|
+
(
|
|
40
|
+
isinstance(node.value.slice, ast.Index)
|
|
41
|
+
and isinstance(node.value.slice.value, STR_TYPES) # type: ignore # noqa
|
|
42
|
+
)
|
|
43
|
+
or isinstance(node.value.slice, ast.Constant)
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
if is_index_call:
|
|
47
|
+
subscript = node.value
|
|
48
|
+
assert isinstance(subscript, ast.Subscript), "hint for mypy" # noqa
|
|
49
|
+
slice_ = subscript.slice
|
|
50
|
+
if isinstance(slice_, ast.Index):
|
|
51
|
+
# Python < 3.9
|
|
52
|
+
string_part = slice_.value # type: ignore
|
|
53
|
+
assert isinstance(string_part, STR_TYPES), "hint for mypy" # noqa
|
|
54
|
+
env_name = to_source(string_part)
|
|
55
|
+
elif isinstance(slice_, ast.Constant):
|
|
56
|
+
# Python 3.9
|
|
57
|
+
env_name = to_source(slice_)
|
|
58
|
+
|
|
59
|
+
# Check if this has a change
|
|
60
|
+
has_change = env_name != env_name.upper()
|
|
61
|
+
|
|
62
|
+
is_get_call = (
|
|
63
|
+
isinstance(node.value, ast.Call)
|
|
64
|
+
and isinstance(node.value.func, ast.Attribute)
|
|
65
|
+
and isinstance(node.value.func.value, ast.Attribute)
|
|
66
|
+
and isinstance(node.value.func.value.value, ast.Name)
|
|
67
|
+
and node.value.func.value.value.id == "os"
|
|
68
|
+
and node.value.func.value.attr == "environ"
|
|
69
|
+
and node.value.func.attr == "get"
|
|
70
|
+
and len(node.value.args) in [1, 2]
|
|
71
|
+
and isinstance(node.value.args[0], STR_TYPES)
|
|
72
|
+
)
|
|
73
|
+
if is_get_call:
|
|
74
|
+
call = node.value
|
|
75
|
+
assert isinstance(call, ast.Call), "hint for mypy" # noqa
|
|
76
|
+
string_part = call.args[0]
|
|
77
|
+
assert isinstance(string_part, STR_TYPES), "hint for mypy" # noqa
|
|
78
|
+
env_name = to_source(string_part)
|
|
79
|
+
# Check if this has a change
|
|
80
|
+
has_change = env_name != env_name.upper()
|
|
81
|
+
if not (is_index_call or is_get_call) or not has_change:
|
|
82
|
+
return errors
|
|
83
|
+
if is_index_call:
|
|
84
|
+
original = to_source(node)
|
|
85
|
+
expected = f"os.environ[{env_name.upper()}]"
|
|
86
|
+
elif is_get_call:
|
|
87
|
+
original = to_source(node)
|
|
88
|
+
if len(node.value.args) == 1: # type: ignore
|
|
89
|
+
expected = f"os.environ.get({env_name.upper()})"
|
|
90
|
+
else:
|
|
91
|
+
assert isinstance(node.value, ast.Call), "hint for mypy" # noqa
|
|
92
|
+
default_value = to_source(node.value.args[1])
|
|
93
|
+
expected = f"os.environ.get({env_name.upper()}, {default_value})"
|
|
94
|
+
else:
|
|
95
|
+
return errors
|
|
96
|
+
errors.append(
|
|
97
|
+
(
|
|
98
|
+
node.lineno,
|
|
99
|
+
node.col_offset,
|
|
100
|
+
RULE.format(original=original, expected=expected),
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
return errors
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Core Library
|
|
2
|
+
import ast
|
|
3
|
+
from typing import List, Tuple
|
|
4
|
+
|
|
5
|
+
# First party
|
|
6
|
+
from flake8_simplify.constants import BOOL_CONST_TYPES
|
|
7
|
+
from flake8_simplify.utils import (
|
|
8
|
+
For,
|
|
9
|
+
body_contains_continue,
|
|
10
|
+
is_constant_increase,
|
|
11
|
+
to_source,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_sim104(node: ast.For) -> List[Tuple[int, int, str]]:
|
|
16
|
+
"""
|
|
17
|
+
Get a list of all "iterate and yield" patterns.
|
|
18
|
+
|
|
19
|
+
for item in iterable:
|
|
20
|
+
yield item
|
|
21
|
+
|
|
22
|
+
which is
|
|
23
|
+
|
|
24
|
+
For(
|
|
25
|
+
target=Name(id='item', ctx=Store()),
|
|
26
|
+
iter=Name(id='iterable', ctx=Load()),
|
|
27
|
+
body=[
|
|
28
|
+
Expr(
|
|
29
|
+
value=Yield(
|
|
30
|
+
value=Name(id='item', ctx=Load()),
|
|
31
|
+
),
|
|
32
|
+
),
|
|
33
|
+
],
|
|
34
|
+
orelse=[],
|
|
35
|
+
type_comment=None,
|
|
36
|
+
),
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
RULE = "SIM104 Use 'yield from {iterable}'"
|
|
40
|
+
errors: List[Tuple[int, int, str]] = []
|
|
41
|
+
if (
|
|
42
|
+
len(node.body) != 1
|
|
43
|
+
or not isinstance(node.body[0], ast.Expr)
|
|
44
|
+
or not isinstance(node.body[0].value, ast.Yield)
|
|
45
|
+
or not isinstance(node.target, ast.Name)
|
|
46
|
+
or not isinstance(node.body[0].value.value, ast.Name)
|
|
47
|
+
or node.target.id != node.body[0].value.value.id
|
|
48
|
+
or node.orelse != []
|
|
49
|
+
):
|
|
50
|
+
return errors
|
|
51
|
+
|
|
52
|
+
parent = getattr(node, "parent", None)
|
|
53
|
+
while (parent
|
|
54
|
+
and hasattr(parent, "parent")
|
|
55
|
+
and parent.parent is not parent
|
|
56
|
+
and not isinstance(parent, ast.AsyncFunctionDef)):
|
|
57
|
+
parent = getattr(parent, "parent", None)
|
|
58
|
+
|
|
59
|
+
if isinstance(parent, ast.AsyncFunctionDef): # type: ignore
|
|
60
|
+
return errors
|
|
61
|
+
iterable = to_source(node.iter)
|
|
62
|
+
errors.append(
|
|
63
|
+
(node.lineno, node.col_offset, RULE.format(iterable=iterable))
|
|
64
|
+
)
|
|
65
|
+
return errors
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_sim110_sim111(node: ast.For) -> List[Tuple[int, int, str]]:
|
|
69
|
+
"""
|
|
70
|
+
Check if any / all could be used.
|
|
71
|
+
|
|
72
|
+
For(
|
|
73
|
+
target=Name(id='x', ctx=Store()),
|
|
74
|
+
iter=Name(id='iterable', ctx=Load()),
|
|
75
|
+
body=[
|
|
76
|
+
If(
|
|
77
|
+
test=Call(
|
|
78
|
+
func=Name(id='check', ctx=Load()),
|
|
79
|
+
args=[Name(id='x', ctx=Load())],
|
|
80
|
+
keywords=[],
|
|
81
|
+
),
|
|
82
|
+
body=[
|
|
83
|
+
Return(
|
|
84
|
+
value=Constant(value=True, kind=None),
|
|
85
|
+
),
|
|
86
|
+
],
|
|
87
|
+
orelse=[],
|
|
88
|
+
),
|
|
89
|
+
],
|
|
90
|
+
orelse=[],
|
|
91
|
+
type_comment=None,
|
|
92
|
+
),
|
|
93
|
+
Return(value=Constant(value=False, kind=None))
|
|
94
|
+
"""
|
|
95
|
+
SIM110 = "SIM110 Use 'return any({check} for {target} in {iterable})'"
|
|
96
|
+
SIM111 = "SIM111 Use 'return all({check} for {target} in {iterable})'"
|
|
97
|
+
errors: List[Tuple[int, int, str]] = []
|
|
98
|
+
if not (
|
|
99
|
+
len(node.body) == 1
|
|
100
|
+
and isinstance(node.body[0], ast.If)
|
|
101
|
+
and len(node.body[0].body) == 1
|
|
102
|
+
and isinstance(node.body[0].body[0], ast.Return)
|
|
103
|
+
and isinstance(node.body[0].body[0].value, BOOL_CONST_TYPES)
|
|
104
|
+
):
|
|
105
|
+
return errors
|
|
106
|
+
if not hasattr(node.body[0].body[0].value, "value"):
|
|
107
|
+
return errors
|
|
108
|
+
if not isinstance(node.next_sibling, ast.Return): # type: ignore
|
|
109
|
+
return errors
|
|
110
|
+
check = to_source(node.body[0].test)
|
|
111
|
+
target = to_source(node.target)
|
|
112
|
+
iterable = to_source(node.iter)
|
|
113
|
+
if node.body[0].body[0].value.value is True:
|
|
114
|
+
errors.append(
|
|
115
|
+
(
|
|
116
|
+
node.lineno,
|
|
117
|
+
node.col_offset,
|
|
118
|
+
SIM110.format(check=check, target=target, iterable=iterable),
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
elif node.body[0].body[0].value.value is False:
|
|
122
|
+
is_compound_expression = " and " in check or " or " in check
|
|
123
|
+
|
|
124
|
+
if is_compound_expression:
|
|
125
|
+
check = f"not ({check})"
|
|
126
|
+
else:
|
|
127
|
+
if check.startswith("not "):
|
|
128
|
+
check = check[len("not ") :]
|
|
129
|
+
else:
|
|
130
|
+
check = f"not {check}"
|
|
131
|
+
errors.append(
|
|
132
|
+
(
|
|
133
|
+
node.lineno,
|
|
134
|
+
node.col_offset,
|
|
135
|
+
SIM111.format(check=check, target=target, iterable=iterable),
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
return errors
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def get_sim113(node: For) -> List[Tuple[int, int, str]]:
|
|
142
|
+
"""
|
|
143
|
+
Find loops in which "enumerate" should be used.
|
|
144
|
+
|
|
145
|
+
For(
|
|
146
|
+
target=Name(id='el', ctx=Store()),
|
|
147
|
+
iter=Name(id='iterable', ctx=Load()),
|
|
148
|
+
body=[
|
|
149
|
+
Expr(
|
|
150
|
+
value=Constant(value=Ellipsis, kind=None),
|
|
151
|
+
),
|
|
152
|
+
AugAssign( -- argument and assign, aka "+= 1"
|
|
153
|
+
target=Name(id='idx', ctx=Store()),
|
|
154
|
+
op=Add(),
|
|
155
|
+
value=Constant(value=1, kind=None),
|
|
156
|
+
),
|
|
157
|
+
],
|
|
158
|
+
orelse=[],
|
|
159
|
+
type_comment=None,
|
|
160
|
+
),
|
|
161
|
+
"""
|
|
162
|
+
errors: List[Tuple[int, int, str]] = []
|
|
163
|
+
variable_candidates = []
|
|
164
|
+
if body_contains_continue(node.body):
|
|
165
|
+
return errors
|
|
166
|
+
|
|
167
|
+
# Find variables that might just count the iteration of the current loop
|
|
168
|
+
for expression in node.body:
|
|
169
|
+
if (
|
|
170
|
+
isinstance(expression, ast.AugAssign)
|
|
171
|
+
and is_constant_increase(expression)
|
|
172
|
+
and isinstance(expression.target, ast.Name)
|
|
173
|
+
):
|
|
174
|
+
variable_candidates.append(expression.target)
|
|
175
|
+
str_candidates = [to_source(x) for x in variable_candidates]
|
|
176
|
+
|
|
177
|
+
older_siblings = []
|
|
178
|
+
for older_sibling in node.parent.body: # type: ignore
|
|
179
|
+
if older_sibling is node:
|
|
180
|
+
break
|
|
181
|
+
older_siblings.append(older_sibling)
|
|
182
|
+
|
|
183
|
+
matches = [
|
|
184
|
+
n.targets[0]
|
|
185
|
+
for n in older_siblings
|
|
186
|
+
if isinstance(n, ast.Assign)
|
|
187
|
+
and len(n.targets) == 1
|
|
188
|
+
and isinstance(n.targets[0], ast.Name)
|
|
189
|
+
and to_source(n.targets[0]) in str_candidates
|
|
190
|
+
]
|
|
191
|
+
if len(matches) == 0:
|
|
192
|
+
return errors
|
|
193
|
+
|
|
194
|
+
sibling = node.previous_sibling
|
|
195
|
+
while sibling is not None:
|
|
196
|
+
sibling = sibling.previous_sibling
|
|
197
|
+
|
|
198
|
+
for match in matches:
|
|
199
|
+
variable = to_source(match)
|
|
200
|
+
errors.append(
|
|
201
|
+
(
|
|
202
|
+
match.lineno,
|
|
203
|
+
match.col_offset,
|
|
204
|
+
f"SIM113 Use enumerate for '{variable}'",
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
return errors
|