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,515 @@
|
|
|
1
|
+
# Core Library
|
|
2
|
+
import ast
|
|
3
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
4
|
+
|
|
5
|
+
# First party
|
|
6
|
+
from flake8_simplify.constants import AST_CONST_TYPES, BOOL_CONST_TYPES
|
|
7
|
+
from flake8_simplify.utils import (
|
|
8
|
+
If,
|
|
9
|
+
get_if_body_pairs,
|
|
10
|
+
is_body_same,
|
|
11
|
+
to_source,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_sim102(node: ast.If) -> List[Tuple[int, int, str]]:
|
|
16
|
+
"""Get a list of all nested if-statements without else-blocks."""
|
|
17
|
+
RULE = "SIM102 Use a single if-statement instead of nested if-statements"
|
|
18
|
+
errors: List[Tuple[int, int, str]] = []
|
|
19
|
+
|
|
20
|
+
# ## Pattern 1
|
|
21
|
+
# if a: <---
|
|
22
|
+
# if b: <---
|
|
23
|
+
# c
|
|
24
|
+
is_pattern_1 = (
|
|
25
|
+
node.orelse == []
|
|
26
|
+
and len(node.body) == 1
|
|
27
|
+
and isinstance(node.body[0], ast.If)
|
|
28
|
+
and node.body[0].orelse == []
|
|
29
|
+
)
|
|
30
|
+
# ## Pattern 2
|
|
31
|
+
# if a: < irrelevant for here
|
|
32
|
+
# pass
|
|
33
|
+
# elif b: <--- this is treated like a nested block
|
|
34
|
+
# if c: <---
|
|
35
|
+
# d
|
|
36
|
+
|
|
37
|
+
if not is_pattern_1:
|
|
38
|
+
return errors
|
|
39
|
+
is_main_check = (
|
|
40
|
+
isinstance(node.test, ast.Compare)
|
|
41
|
+
and isinstance(node.test.left, ast.Name)
|
|
42
|
+
and node.test.left.id == "__name__"
|
|
43
|
+
and len(node.test.comparators) == 1
|
|
44
|
+
and isinstance(node.test.comparators[0], ast.Str)
|
|
45
|
+
and node.test.comparators[0].s == "__main__"
|
|
46
|
+
)
|
|
47
|
+
if is_main_check:
|
|
48
|
+
return errors
|
|
49
|
+
errors.append((node.lineno, node.col_offset, RULE))
|
|
50
|
+
return errors
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_sim103(node: ast.If) -> List[Tuple[int, int, str]]:
|
|
54
|
+
"""
|
|
55
|
+
Get a list of all calls that wrap a condition to return a bool.
|
|
56
|
+
|
|
57
|
+
if cond:
|
|
58
|
+
return True
|
|
59
|
+
else:
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
which is
|
|
63
|
+
|
|
64
|
+
If(
|
|
65
|
+
test=Name(id='cond', ctx=Load()),
|
|
66
|
+
body=[
|
|
67
|
+
Return(
|
|
68
|
+
value=Constant(value=True, kind=None),
|
|
69
|
+
),
|
|
70
|
+
],
|
|
71
|
+
orelse=[
|
|
72
|
+
Return(
|
|
73
|
+
value=Constant(value=False, kind=None),
|
|
74
|
+
),
|
|
75
|
+
],
|
|
76
|
+
),
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
SIM103 = "SIM103 Return the condition {cond} directly"
|
|
80
|
+
errors: List[Tuple[int, int, str]] = []
|
|
81
|
+
if (
|
|
82
|
+
len(node.body) != 1
|
|
83
|
+
or not isinstance(node.body[0], ast.Return)
|
|
84
|
+
or not isinstance(node.body[0].value, BOOL_CONST_TYPES)
|
|
85
|
+
or not (
|
|
86
|
+
node.body[0].value.value is True
|
|
87
|
+
or node.body[0].value.value is False
|
|
88
|
+
)
|
|
89
|
+
or len(node.orelse) != 1
|
|
90
|
+
or not isinstance(node.orelse[0], ast.Return)
|
|
91
|
+
or not isinstance(node.orelse[0].value, BOOL_CONST_TYPES)
|
|
92
|
+
or not (
|
|
93
|
+
node.orelse[0].value.value is True
|
|
94
|
+
or node.orelse[0].value.value is False
|
|
95
|
+
)
|
|
96
|
+
):
|
|
97
|
+
return errors
|
|
98
|
+
cond = to_source(node.test)
|
|
99
|
+
errors.append((node.lineno, node.col_offset, SIM103.format(cond=cond)))
|
|
100
|
+
return errors
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def get_sim108(node: If) -> List[Tuple[int, int, str]]:
|
|
104
|
+
"""
|
|
105
|
+
Get a list of all if-elses which could be a ternary operator assignment.
|
|
106
|
+
|
|
107
|
+
If(
|
|
108
|
+
test=Name(id='a', ctx=Load()),
|
|
109
|
+
body=[
|
|
110
|
+
Assign(
|
|
111
|
+
targets=[Name(id='b', ctx=Store())],
|
|
112
|
+
value=Name(id='c', ctx=Load()),
|
|
113
|
+
type_comment=None,
|
|
114
|
+
),
|
|
115
|
+
],
|
|
116
|
+
orelse=[
|
|
117
|
+
Assign(
|
|
118
|
+
targets=[Name(id='b', ctx=Store())],
|
|
119
|
+
value=Name(id='d', ctx=Load()),
|
|
120
|
+
type_comment=None,
|
|
121
|
+
),
|
|
122
|
+
],
|
|
123
|
+
),
|
|
124
|
+
"""
|
|
125
|
+
RULE = (
|
|
126
|
+
"SIM108 Use ternary operator "
|
|
127
|
+
"'{assign} = {body} if {cond} else {orelse}' "
|
|
128
|
+
"instead of if-else-block"
|
|
129
|
+
)
|
|
130
|
+
errors: List[Tuple[int, int, str]] = []
|
|
131
|
+
if not (
|
|
132
|
+
len(node.body) == 1
|
|
133
|
+
and isinstance(node.body[0], ast.Assign)
|
|
134
|
+
and len(node.orelse) == 1
|
|
135
|
+
and isinstance(node.orelse[0], ast.Assign)
|
|
136
|
+
and len(node.body[0].targets) == 1
|
|
137
|
+
and len(node.orelse[0].targets) == 1
|
|
138
|
+
and isinstance(node.body[0].targets[0], ast.Name)
|
|
139
|
+
and isinstance(node.orelse[0].targets[0], ast.Name)
|
|
140
|
+
and node.body[0].targets[0].id == node.orelse[0].targets[0].id
|
|
141
|
+
):
|
|
142
|
+
return errors
|
|
143
|
+
|
|
144
|
+
target_var = node.body[0].targets[0]
|
|
145
|
+
assign = to_source(target_var)
|
|
146
|
+
|
|
147
|
+
# It's part of a bigger if-elseif block:
|
|
148
|
+
# https://github.com/MartinThoma/flake8-simplify/issues/115
|
|
149
|
+
if isinstance(node.parent, ast.If):
|
|
150
|
+
for n in node.parent.body:
|
|
151
|
+
if (
|
|
152
|
+
isinstance(n, ast.Assign)
|
|
153
|
+
and isinstance(n.targets[0], ast.Name)
|
|
154
|
+
and n.targets[0].id == target_var.id
|
|
155
|
+
):
|
|
156
|
+
return errors
|
|
157
|
+
|
|
158
|
+
body = to_source(node.body[0].value)
|
|
159
|
+
cond = to_source(node.test)
|
|
160
|
+
orelse = to_source(node.orelse[0].value)
|
|
161
|
+
new_code = RULE.format(assign=assign, body=body, cond=cond, orelse=orelse)
|
|
162
|
+
if len(new_code) > 79:
|
|
163
|
+
return errors
|
|
164
|
+
errors.append((node.lineno, node.col_offset, new_code))
|
|
165
|
+
return errors
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def get_sim114(node: ast.If) -> List[Tuple[int, int, str]]:
|
|
169
|
+
"""
|
|
170
|
+
Find same bodys.
|
|
171
|
+
|
|
172
|
+
Examples
|
|
173
|
+
--------
|
|
174
|
+
If(
|
|
175
|
+
test=Name(id='a', ctx=Load()),
|
|
176
|
+
body=[
|
|
177
|
+
Expr(
|
|
178
|
+
value=Name(id='b', ctx=Load()),
|
|
179
|
+
),
|
|
180
|
+
],
|
|
181
|
+
orelse=[
|
|
182
|
+
If(
|
|
183
|
+
test=Name(id='c', ctx=Load()),
|
|
184
|
+
body=[
|
|
185
|
+
Expr(
|
|
186
|
+
value=Name(id='b', ctx=Load()),
|
|
187
|
+
),
|
|
188
|
+
],
|
|
189
|
+
orelse=[],
|
|
190
|
+
),
|
|
191
|
+
],
|
|
192
|
+
),
|
|
193
|
+
"""
|
|
194
|
+
SIM114 = "SIM114 Use logical or (({cond1}) or ({cond2})) and a single body"
|
|
195
|
+
errors: List[Tuple[int, int, str]] = []
|
|
196
|
+
if_body_pairs = get_if_body_pairs(node)
|
|
197
|
+
error_pairs = []
|
|
198
|
+
for i in range(len(if_body_pairs) - 1):
|
|
199
|
+
# It's not all combinations because of this:
|
|
200
|
+
# https://github.com/MartinThoma/flake8-simplify/issues/70
|
|
201
|
+
# #issuecomment-924074984
|
|
202
|
+
ifbody1 = if_body_pairs[i]
|
|
203
|
+
ifbody2 = if_body_pairs[i + 1]
|
|
204
|
+
if is_body_same(ifbody1[1], ifbody2[1]):
|
|
205
|
+
error_pairs.append((ifbody1, ifbody2))
|
|
206
|
+
for ifbody1, ifbody2 in error_pairs:
|
|
207
|
+
errors.append(
|
|
208
|
+
(
|
|
209
|
+
ifbody1[0].lineno,
|
|
210
|
+
ifbody1[0].col_offset,
|
|
211
|
+
SIM114.format(
|
|
212
|
+
cond1=to_source(ifbody1[0]), cond2=to_source(ifbody2[0])
|
|
213
|
+
),
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
return errors
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def get_sim116(node: ast.If) -> List[Tuple[int, int, str]]:
|
|
220
|
+
"""
|
|
221
|
+
Find places where 3 or more consecutive if-statements with direct returns.
|
|
222
|
+
|
|
223
|
+
* Each if-statement must be a check for equality with the
|
|
224
|
+
same variable
|
|
225
|
+
* Each if-statement must just have a "return"
|
|
226
|
+
* Else must also just have a return
|
|
227
|
+
"""
|
|
228
|
+
SIM116 = (
|
|
229
|
+
"SIM116 Use a dictionary lookup instead of 3+ if/elif-statements: "
|
|
230
|
+
"return {ret}"
|
|
231
|
+
)
|
|
232
|
+
errors: List[Tuple[int, int, str]] = []
|
|
233
|
+
if not (
|
|
234
|
+
isinstance(node.test, ast.Compare)
|
|
235
|
+
and isinstance(node.test.left, ast.Name)
|
|
236
|
+
and len(node.test.ops) == 1
|
|
237
|
+
and isinstance(node.test.ops[0], ast.Eq)
|
|
238
|
+
and len(node.test.comparators) == 1
|
|
239
|
+
and isinstance(node.test.comparators[0], AST_CONST_TYPES)
|
|
240
|
+
and len(node.body) == 1
|
|
241
|
+
and isinstance(node.body[0], ast.Return)
|
|
242
|
+
and len(node.orelse) == 1
|
|
243
|
+
and isinstance(node.orelse[0], ast.If)
|
|
244
|
+
):
|
|
245
|
+
return errors
|
|
246
|
+
variable = node.test.left
|
|
247
|
+
child: Optional[ast.If] = node.orelse[0]
|
|
248
|
+
assert isinstance(child, ast.If), "hint for mypy"
|
|
249
|
+
else_value: Optional[str] = None
|
|
250
|
+
key_value_pairs: Dict[Any, Any]
|
|
251
|
+
if isinstance(node.test.comparators[0], ast.Str):
|
|
252
|
+
value = to_source(node.body[0].value)
|
|
253
|
+
if value[0] == '"' and value[-1] == '"':
|
|
254
|
+
value = value[1:-1]
|
|
255
|
+
key_value_pairs = {node.test.comparators[0].s: value}
|
|
256
|
+
elif isinstance(node.test.comparators[0], ast.Num):
|
|
257
|
+
key_value_pairs = {
|
|
258
|
+
node.test.comparators[0].n: to_source(node.body[0].value)
|
|
259
|
+
}
|
|
260
|
+
else:
|
|
261
|
+
key_value_pairs = {
|
|
262
|
+
node.test.comparators[0].value: to_source(node.body[0].value)
|
|
263
|
+
}
|
|
264
|
+
while child:
|
|
265
|
+
if not (
|
|
266
|
+
isinstance(child.test, ast.Compare)
|
|
267
|
+
and isinstance(child.test.left, ast.Name)
|
|
268
|
+
and child.test.left.id == variable.id
|
|
269
|
+
and len(child.test.ops) == 1
|
|
270
|
+
and isinstance(child.test.ops[0], ast.Eq)
|
|
271
|
+
and len(child.test.comparators) == 1
|
|
272
|
+
and isinstance(child.test.comparators[0], AST_CONST_TYPES)
|
|
273
|
+
and len(child.body) == 1
|
|
274
|
+
and isinstance(child.body[0], ast.Return)
|
|
275
|
+
and len(child.orelse) <= 1
|
|
276
|
+
):
|
|
277
|
+
return errors
|
|
278
|
+
return_call = child.body[0]
|
|
279
|
+
assert isinstance(return_call, ast.Return), "hint for mypy"
|
|
280
|
+
if isinstance(return_call.value, ast.Call):
|
|
281
|
+
# See https://github.com/MartinThoma/flake8-simplify/issues/113
|
|
282
|
+
return errors
|
|
283
|
+
key: Any
|
|
284
|
+
if isinstance(child.test.comparators[0], ast.Str):
|
|
285
|
+
key = child.test.comparators[0].s
|
|
286
|
+
elif isinstance(child.test.comparators[0], ast.Num):
|
|
287
|
+
key = child.test.comparators[0].n
|
|
288
|
+
else:
|
|
289
|
+
key = child.test.comparators[0].value
|
|
290
|
+
|
|
291
|
+
value = to_source(child.body[0].value)
|
|
292
|
+
if value[0] == '"' and value[-1] == '"':
|
|
293
|
+
value = value[1:-1]
|
|
294
|
+
key_value_pairs[key] = value
|
|
295
|
+
|
|
296
|
+
if len(child.orelse) == 1:
|
|
297
|
+
if isinstance(child.orelse[0], ast.If):
|
|
298
|
+
child = child.orelse[0]
|
|
299
|
+
elif isinstance(child.orelse[0], ast.Return):
|
|
300
|
+
else_value = to_source(child.orelse[0].value)
|
|
301
|
+
child = None
|
|
302
|
+
else:
|
|
303
|
+
return errors
|
|
304
|
+
else:
|
|
305
|
+
child = None
|
|
306
|
+
if len(key_value_pairs) < 3:
|
|
307
|
+
return errors
|
|
308
|
+
if else_value:
|
|
309
|
+
ret = f"{key_value_pairs}.get({variable.id}, {else_value})"
|
|
310
|
+
else:
|
|
311
|
+
ret = f"{key_value_pairs}.get({variable.id})"
|
|
312
|
+
errors.append((node.lineno, node.col_offset, SIM116.format(ret=ret)))
|
|
313
|
+
return errors
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def get_sim908(node: ast.If) -> List[Tuple[int, int, str]]:
|
|
317
|
+
"""
|
|
318
|
+
Get all if-blocks which only check if a key is in a dictionary.
|
|
319
|
+
"""
|
|
320
|
+
RULE = (
|
|
321
|
+
"SIM908 Use '{dictname}.get({key})' instead of "
|
|
322
|
+
"'if {key} in {dictname}: {dictname}[{key}]'"
|
|
323
|
+
)
|
|
324
|
+
errors: List[Tuple[int, int, str]] = []
|
|
325
|
+
if not (
|
|
326
|
+
isinstance(node.test, ast.Compare)
|
|
327
|
+
and len(node.test.ops) == 1
|
|
328
|
+
and isinstance(node.test.ops[0], ast.In)
|
|
329
|
+
and len(node.body) == 1
|
|
330
|
+
and len(node.orelse) == 0
|
|
331
|
+
):
|
|
332
|
+
return errors
|
|
333
|
+
|
|
334
|
+
# We might still be left with a check if a value is in a list or in
|
|
335
|
+
# the body the developer might remove the element from the list
|
|
336
|
+
# We need to have a look at the body
|
|
337
|
+
if not (
|
|
338
|
+
isinstance(node.body[0], ast.Assign)
|
|
339
|
+
and isinstance(node.body[0].value, ast.Subscript)
|
|
340
|
+
and len(node.body[0].targets) == 1
|
|
341
|
+
and isinstance(node.body[0].targets[0], ast.Name)
|
|
342
|
+
):
|
|
343
|
+
return errors
|
|
344
|
+
|
|
345
|
+
test_var = node.test.left
|
|
346
|
+
slice_var = node.body[0].value.slice
|
|
347
|
+
if to_source(slice_var) != to_source(test_var):
|
|
348
|
+
return errors
|
|
349
|
+
|
|
350
|
+
key = to_source(node.test.left)
|
|
351
|
+
dictname = to_source(node.test.comparators[0])
|
|
352
|
+
errors.append(
|
|
353
|
+
(
|
|
354
|
+
node.lineno,
|
|
355
|
+
node.col_offset,
|
|
356
|
+
RULE.format(key=key, dictname=dictname),
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
return errors
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def get_sim401(node: ast.If) -> List[Tuple[int, int, str]]:
|
|
363
|
+
"""
|
|
364
|
+
Get all calls that should use default values for dictionary access.
|
|
365
|
+
|
|
366
|
+
Pattern 1
|
|
367
|
+
---------
|
|
368
|
+
if key in a_dict:
|
|
369
|
+
value = a_dict[key]
|
|
370
|
+
else:
|
|
371
|
+
value = "default"
|
|
372
|
+
|
|
373
|
+
which is
|
|
374
|
+
|
|
375
|
+
If(
|
|
376
|
+
test=Compare(
|
|
377
|
+
left=Name(id='key', ctx=Load()),
|
|
378
|
+
ops=[In()],
|
|
379
|
+
comparators=[Name(id='a_dict', ctx=Load())],
|
|
380
|
+
),
|
|
381
|
+
body=[
|
|
382
|
+
Assign(
|
|
383
|
+
targets=[Name(id='value', ctx=Store())],
|
|
384
|
+
value=Subscript(
|
|
385
|
+
value=Name(id='a_dict', ctx=Load()),
|
|
386
|
+
slice=Name(id='key', ctx=Load()),
|
|
387
|
+
ctx=Load(),
|
|
388
|
+
),
|
|
389
|
+
type_comment=None,
|
|
390
|
+
),
|
|
391
|
+
],
|
|
392
|
+
orelse=[
|
|
393
|
+
Assign(
|
|
394
|
+
targets=[Name(id='value', ctx=Store())],
|
|
395
|
+
value=Constant(value='default', kind=None),
|
|
396
|
+
type_comment=None,
|
|
397
|
+
),
|
|
398
|
+
],
|
|
399
|
+
),
|
|
400
|
+
|
|
401
|
+
Pattern 2
|
|
402
|
+
---------
|
|
403
|
+
|
|
404
|
+
if key not in a_dict:
|
|
405
|
+
value = 'default'
|
|
406
|
+
else:
|
|
407
|
+
value = a_dict[key]
|
|
408
|
+
|
|
409
|
+
which is
|
|
410
|
+
|
|
411
|
+
If(
|
|
412
|
+
test=Compare(
|
|
413
|
+
left=Name(id='key', ctx=Load()),
|
|
414
|
+
ops=[NotIn()],
|
|
415
|
+
comparators=[Name(id='a_dict', ctx=Load())],
|
|
416
|
+
),
|
|
417
|
+
body=[
|
|
418
|
+
Assign(
|
|
419
|
+
targets=[Name(id='value', ctx=Store())],
|
|
420
|
+
value=Constant(value='default', kind=None),
|
|
421
|
+
type_comment=None,
|
|
422
|
+
),
|
|
423
|
+
],
|
|
424
|
+
orelse=[
|
|
425
|
+
Assign(
|
|
426
|
+
targets=[Name(id='value', ctx=Store())],
|
|
427
|
+
value=Subscript(
|
|
428
|
+
value=Name(id='a_dict', ctx=Load()),
|
|
429
|
+
slice=Name(id='key', ctx=Load()),
|
|
430
|
+
ctx=Load(),
|
|
431
|
+
),
|
|
432
|
+
type_comment=None,
|
|
433
|
+
),
|
|
434
|
+
],
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
"""
|
|
438
|
+
SIM401 = (
|
|
439
|
+
"SIM401 Use '{value} = {dict}.get({key}, {default_value})' "
|
|
440
|
+
"instead of an if-block"
|
|
441
|
+
)
|
|
442
|
+
errors: List[Tuple[int, int, str]] = []
|
|
443
|
+
is_pattern_1 = (
|
|
444
|
+
len(node.body) == 1
|
|
445
|
+
and isinstance(node.body[0], ast.Assign)
|
|
446
|
+
and len(node.body[0].targets) == 1
|
|
447
|
+
and isinstance(node.body[0].value, ast.Subscript)
|
|
448
|
+
and len(node.orelse) == 1
|
|
449
|
+
and isinstance(node.orelse[0], ast.Assign)
|
|
450
|
+
and len(node.orelse[0].targets) == 1
|
|
451
|
+
and isinstance(node.test, ast.Compare)
|
|
452
|
+
and len(node.test.ops) == 1
|
|
453
|
+
and isinstance(node.test.ops[0], ast.In)
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
# just like pattern_1, but using NotIn and reversing if/else
|
|
457
|
+
is_pattern_2 = (
|
|
458
|
+
len(node.body) == 1
|
|
459
|
+
and isinstance(node.body[0], ast.Assign)
|
|
460
|
+
and len(node.orelse) == 1
|
|
461
|
+
and isinstance(node.orelse[0], ast.Assign)
|
|
462
|
+
and isinstance(node.orelse[0].value, ast.Subscript)
|
|
463
|
+
and isinstance(node.test, ast.Compare)
|
|
464
|
+
and len(node.test.ops) == 1
|
|
465
|
+
and isinstance(node.test.ops[0], ast.NotIn)
|
|
466
|
+
)
|
|
467
|
+
if is_pattern_1:
|
|
468
|
+
assert isinstance(node.test, ast.Compare)
|
|
469
|
+
assert isinstance(node.body[0], ast.Assign)
|
|
470
|
+
assert isinstance(node.body[0].value, ast.Subscript)
|
|
471
|
+
assert isinstance(node.orelse[0], ast.Assign)
|
|
472
|
+
key = node.test.left
|
|
473
|
+
if to_source(key) != to_source(node.body[0].value.slice):
|
|
474
|
+
return errors # second part of pattern 1
|
|
475
|
+
assign_to_if_body = node.body[0].targets[0]
|
|
476
|
+
assign_to_else = node.orelse[0].targets[0]
|
|
477
|
+
if to_source(assign_to_if_body) != to_source(assign_to_else):
|
|
478
|
+
return errors
|
|
479
|
+
dict_name = node.test.comparators[0]
|
|
480
|
+
default_value = node.orelse[0].value
|
|
481
|
+
value_node = node.body[0].targets[0]
|
|
482
|
+
key_str = to_source(key)
|
|
483
|
+
dict_str = to_source(dict_name)
|
|
484
|
+
default_str = to_source(default_value)
|
|
485
|
+
value_str = to_source(value_node)
|
|
486
|
+
elif is_pattern_2:
|
|
487
|
+
assert isinstance(node.test, ast.Compare)
|
|
488
|
+
assert isinstance(node.body[0], ast.Assign)
|
|
489
|
+
assert isinstance(node.orelse[0], ast.Assign)
|
|
490
|
+
assert isinstance(node.orelse[0].value, ast.Subscript)
|
|
491
|
+
key = node.test.left
|
|
492
|
+
if to_source(key) != to_source(node.orelse[0].value.slice):
|
|
493
|
+
return errors # second part of pattern 1
|
|
494
|
+
dict_name = node.test.comparators[0]
|
|
495
|
+
default_value = node.body[0].value
|
|
496
|
+
value_node = node.body[0].targets[0]
|
|
497
|
+
key_str = to_source(key)
|
|
498
|
+
dict_str = to_source(dict_name)
|
|
499
|
+
default_str = to_source(default_value)
|
|
500
|
+
value_str = to_source(value_node)
|
|
501
|
+
else:
|
|
502
|
+
return errors
|
|
503
|
+
errors.append(
|
|
504
|
+
(
|
|
505
|
+
node.lineno,
|
|
506
|
+
node.col_offset,
|
|
507
|
+
SIM401.format(
|
|
508
|
+
key=key_str,
|
|
509
|
+
dict=dict_str,
|
|
510
|
+
default_value=default_str,
|
|
511
|
+
value=value_str,
|
|
512
|
+
),
|
|
513
|
+
)
|
|
514
|
+
)
|
|
515
|
+
return errors
|
|
@@ -0,0 +1,68 @@
|
|
|
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 is_same_expression, to_source
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_sim210(node: ast.IfExp) -> List[Tuple[int, int, str]]:
|
|
11
|
+
"""Get a list of all calls of the type "True if a else False"."""
|
|
12
|
+
SIM210 = "SIM210 Use 'bool({cond})' instead of 'True if {cond} else False'"
|
|
13
|
+
errors: List[Tuple[int, int, str]] = []
|
|
14
|
+
if (
|
|
15
|
+
not isinstance(node.body, BOOL_CONST_TYPES)
|
|
16
|
+
or node.body.value is not True
|
|
17
|
+
or not isinstance(node.orelse, BOOL_CONST_TYPES)
|
|
18
|
+
or node.orelse.value is not False
|
|
19
|
+
):
|
|
20
|
+
return errors
|
|
21
|
+
cond = to_source(node.test)
|
|
22
|
+
errors.append((node.lineno, node.col_offset, SIM210.format(cond=cond)))
|
|
23
|
+
return errors
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_sim211(node: ast.IfExp) -> List[Tuple[int, int, str]]:
|
|
27
|
+
"""Get a list of all calls of the type "False if a else True"."""
|
|
28
|
+
SIM211 = "SIM211 Use 'not {cond}' instead of 'False if {cond} else True'"
|
|
29
|
+
errors: List[Tuple[int, int, str]] = []
|
|
30
|
+
if (
|
|
31
|
+
not isinstance(node.body, BOOL_CONST_TYPES)
|
|
32
|
+
or node.body.value is not False
|
|
33
|
+
or not isinstance(node.orelse, BOOL_CONST_TYPES)
|
|
34
|
+
or node.orelse.value is not True
|
|
35
|
+
):
|
|
36
|
+
return errors
|
|
37
|
+
cond = to_source(node.test)
|
|
38
|
+
errors.append((node.lineno, node.col_offset, SIM211.format(cond=cond)))
|
|
39
|
+
return errors
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_sim212(node: ast.IfExp) -> List[Tuple[int, int, str]]:
|
|
43
|
+
"""
|
|
44
|
+
Get a list of all calls of the type "b if not a else a".
|
|
45
|
+
|
|
46
|
+
IfExp(
|
|
47
|
+
test=UnaryOp(
|
|
48
|
+
op=Not(),
|
|
49
|
+
operand=Name(id='a', ctx=Load()),
|
|
50
|
+
),
|
|
51
|
+
body=Name(id='b', ctx=Load()),
|
|
52
|
+
orelse=Name(id='a', ctx=Load()),
|
|
53
|
+
)
|
|
54
|
+
"""
|
|
55
|
+
SIM212 = (
|
|
56
|
+
"SIM212 Use '{a} if {a} else {b}' instead of '{b} if not {a} else {a}'"
|
|
57
|
+
)
|
|
58
|
+
errors: List[Tuple[int, int, str]] = []
|
|
59
|
+
if not (
|
|
60
|
+
isinstance(node.test, ast.UnaryOp)
|
|
61
|
+
and isinstance(node.test.op, ast.Not)
|
|
62
|
+
and is_same_expression(node.test.operand, node.orelse)
|
|
63
|
+
):
|
|
64
|
+
return errors
|
|
65
|
+
a = to_source(node.test.operand)
|
|
66
|
+
b = to_source(node.body)
|
|
67
|
+
errors.append((node.lineno, node.col_offset, SIM212.format(a=a, b=b)))
|
|
68
|
+
return errors
|
|
@@ -0,0 +1,59 @@
|
|
|
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 to_source
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_sim907(node: ast.Subscript) -> List[Tuple[int, int, str]]:
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
Subscript(
|
|
14
|
+
value=Name(id='Union', ctx=Load()),
|
|
15
|
+
slice=Tuple(
|
|
16
|
+
elts=[
|
|
17
|
+
Name(id='int', ctx=Load()),
|
|
18
|
+
Name(id='str', ctx=Load()),
|
|
19
|
+
Constant(value=None, kind=None),
|
|
20
|
+
],
|
|
21
|
+
...
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
"""
|
|
25
|
+
errors: List[Tuple[int, int, str]] = []
|
|
26
|
+
|
|
27
|
+
if not (isinstance(node.value, ast.Name) and node.value.id == "Union"):
|
|
28
|
+
return errors
|
|
29
|
+
|
|
30
|
+
if isinstance(node.slice, ast.Index) and isinstance(
|
|
31
|
+
node.slice.value, ast.Tuple # type: ignore
|
|
32
|
+
):
|
|
33
|
+
# Python 3.8
|
|
34
|
+
tuple_var = node.slice.value # type: ignore
|
|
35
|
+
elif isinstance(node.slice, ast.Tuple):
|
|
36
|
+
# Python 3.9+
|
|
37
|
+
tuple_var = node.slice
|
|
38
|
+
else:
|
|
39
|
+
return errors
|
|
40
|
+
|
|
41
|
+
has_none = False
|
|
42
|
+
others = []
|
|
43
|
+
for elt in tuple_var.elts: # type: ignore
|
|
44
|
+
if isinstance(elt, BOOL_CONST_TYPES) and elt.value is None:
|
|
45
|
+
has_none = True
|
|
46
|
+
else:
|
|
47
|
+
others.append(elt)
|
|
48
|
+
|
|
49
|
+
RULE = "SIM907 Use 'Optional[{type_}]' instead of '{original}'"
|
|
50
|
+
if len(others) == 1 and has_none:
|
|
51
|
+
type_ = to_source(others[0])
|
|
52
|
+
errors.append(
|
|
53
|
+
(
|
|
54
|
+
node.lineno,
|
|
55
|
+
node.col_offset,
|
|
56
|
+
RULE.format(type_=type_, original=to_source(node)),
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
return errors
|