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.
@@ -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