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,96 @@
|
|
|
1
|
+
# Core Library
|
|
2
|
+
import ast
|
|
3
|
+
from typing import List, Tuple
|
|
4
|
+
|
|
5
|
+
# First party
|
|
6
|
+
from flake8_simplify.utils import to_source
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_sim105(node: ast.Try) -> List[Tuple[int, int, str]]:
|
|
10
|
+
"""
|
|
11
|
+
Get a list of all "try-except-pass" patterns.
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
foo()
|
|
15
|
+
except ValueError:
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
which is
|
|
19
|
+
|
|
20
|
+
Try(
|
|
21
|
+
body=[
|
|
22
|
+
Expr(
|
|
23
|
+
value=Call(
|
|
24
|
+
func=Name(id='foo', ctx=Load()),
|
|
25
|
+
args=[],
|
|
26
|
+
keywords=[],
|
|
27
|
+
),
|
|
28
|
+
),
|
|
29
|
+
],
|
|
30
|
+
handlers=[
|
|
31
|
+
ExceptHandler(
|
|
32
|
+
type=Name(id='ValueError', ctx=Load()),
|
|
33
|
+
name=None,
|
|
34
|
+
body=[Pass()],
|
|
35
|
+
),
|
|
36
|
+
],
|
|
37
|
+
orelse=[],
|
|
38
|
+
finalbody=[],
|
|
39
|
+
),
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
SIM105 = "SIM105 Use 'contextlib.suppress({exception})'"
|
|
44
|
+
errors: List[Tuple[int, int, str]] = []
|
|
45
|
+
if (
|
|
46
|
+
len(node.body) != 1
|
|
47
|
+
or len(node.handlers) != 1
|
|
48
|
+
or not isinstance(node.handlers[0], ast.ExceptHandler)
|
|
49
|
+
or len(node.handlers[0].body) != 1
|
|
50
|
+
or not isinstance(node.handlers[0].body[0], ast.Pass)
|
|
51
|
+
or node.orelse != []
|
|
52
|
+
):
|
|
53
|
+
return errors
|
|
54
|
+
if node.handlers[0].type is None:
|
|
55
|
+
exception = "Exception"
|
|
56
|
+
else:
|
|
57
|
+
exception = to_source(node.handlers[0].type)
|
|
58
|
+
errors.append(
|
|
59
|
+
(node.lineno, node.col_offset, SIM105.format(exception=exception))
|
|
60
|
+
)
|
|
61
|
+
return errors
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_sim107(node: ast.Try) -> List[Tuple[int, int, str]]:
|
|
65
|
+
"""
|
|
66
|
+
Get a list of all calls where try/except and finally have 'return'.
|
|
67
|
+
"""
|
|
68
|
+
SIM107 = "SIM107 Don't use return in try/except and finally"
|
|
69
|
+
errors: List[Tuple[int, int, str]] = []
|
|
70
|
+
|
|
71
|
+
try_has_return = False
|
|
72
|
+
for stmt in node.body:
|
|
73
|
+
if isinstance(stmt, ast.Return):
|
|
74
|
+
try_has_return = True
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
except_has_return = False
|
|
78
|
+
for stmt2 in node.handlers:
|
|
79
|
+
if isinstance(stmt2, ast.Return):
|
|
80
|
+
except_has_return = True
|
|
81
|
+
break
|
|
82
|
+
|
|
83
|
+
finally_has_return = False
|
|
84
|
+
finally_return = None
|
|
85
|
+
for stmt in node.finalbody:
|
|
86
|
+
if isinstance(stmt, ast.Return):
|
|
87
|
+
finally_has_return = True
|
|
88
|
+
finally_return = stmt
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
if (try_has_return or except_has_return) and finally_has_return:
|
|
92
|
+
assert finally_return is not None, "hint for mypy"
|
|
93
|
+
errors.append(
|
|
94
|
+
(finally_return.lineno, finally_return.col_offset, SIM107)
|
|
95
|
+
)
|
|
96
|
+
return errors
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Core Library
|
|
2
|
+
import ast
|
|
3
|
+
from typing import List, Tuple
|
|
4
|
+
|
|
5
|
+
# First party
|
|
6
|
+
from flake8_simplify.utils import UnaryOp, is_exception_check, to_source
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_sim201(node: UnaryOp) -> List[Tuple[int, int, str]]:
|
|
10
|
+
"""
|
|
11
|
+
Get a list of all calls where an unary 'not' is used for an equality.
|
|
12
|
+
"""
|
|
13
|
+
SIM201 = (
|
|
14
|
+
"SIM201 Use '{left} != {right}' instead of 'not {left} == {right}'"
|
|
15
|
+
)
|
|
16
|
+
errors: List[Tuple[int, int, str]] = []
|
|
17
|
+
if (
|
|
18
|
+
(
|
|
19
|
+
not isinstance(node.op, ast.Not)
|
|
20
|
+
or not isinstance(node.operand, ast.Compare)
|
|
21
|
+
or len(node.operand.ops) != 1
|
|
22
|
+
or not isinstance(node.operand.ops[0], ast.Eq)
|
|
23
|
+
)
|
|
24
|
+
or isinstance(node.parent, ast.If)
|
|
25
|
+
and is_exception_check(node.parent)
|
|
26
|
+
):
|
|
27
|
+
return errors
|
|
28
|
+
comparison = node.operand
|
|
29
|
+
left = to_source(comparison.left)
|
|
30
|
+
right = to_source(comparison.comparators[0])
|
|
31
|
+
errors.append(
|
|
32
|
+
(node.lineno, node.col_offset, SIM201.format(left=left, right=right))
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
return errors
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_sim202(node: UnaryOp) -> List[Tuple[int, int, str]]:
|
|
39
|
+
"""
|
|
40
|
+
Get a list of all calls where an unary 'not' is used for an quality.
|
|
41
|
+
"""
|
|
42
|
+
SIM202 = (
|
|
43
|
+
"SIM202 Use '{left} == {right}' instead of 'not {left} != {right}'"
|
|
44
|
+
)
|
|
45
|
+
errors: List[Tuple[int, int, str]] = []
|
|
46
|
+
if (
|
|
47
|
+
(
|
|
48
|
+
not isinstance(node.op, ast.Not)
|
|
49
|
+
or not isinstance(node.operand, ast.Compare)
|
|
50
|
+
or len(node.operand.ops) != 1
|
|
51
|
+
or not isinstance(node.operand.ops[0], ast.NotEq)
|
|
52
|
+
)
|
|
53
|
+
or isinstance(node.parent, ast.If)
|
|
54
|
+
and is_exception_check(node.parent)
|
|
55
|
+
):
|
|
56
|
+
return errors
|
|
57
|
+
comparison = node.operand
|
|
58
|
+
left = to_source(comparison.left)
|
|
59
|
+
right = to_source(comparison.comparators[0])
|
|
60
|
+
errors.append(
|
|
61
|
+
(node.lineno, node.col_offset, SIM202.format(left=left, right=right))
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return errors
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_sim203(node: UnaryOp) -> List[Tuple[int, int, str]]:
|
|
68
|
+
"""
|
|
69
|
+
Get a list of all calls where an unary 'not' is used for an in-check.
|
|
70
|
+
"""
|
|
71
|
+
SIM203 = "SIM203 Use '{a} not in {b}' instead of 'not {a} in {b}'"
|
|
72
|
+
errors: List[Tuple[int, int, str]] = []
|
|
73
|
+
if (
|
|
74
|
+
(
|
|
75
|
+
not isinstance(node.op, ast.Not)
|
|
76
|
+
or not isinstance(node.operand, ast.Compare)
|
|
77
|
+
or len(node.operand.ops) != 1
|
|
78
|
+
or not isinstance(node.operand.ops[0], ast.In)
|
|
79
|
+
)
|
|
80
|
+
or isinstance(node.parent, ast.If)
|
|
81
|
+
and is_exception_check(node.parent)
|
|
82
|
+
):
|
|
83
|
+
return errors
|
|
84
|
+
comparison = node.operand
|
|
85
|
+
left = to_source(comparison.left)
|
|
86
|
+
right = to_source(comparison.comparators[0])
|
|
87
|
+
errors.append(
|
|
88
|
+
(node.lineno, node.col_offset, SIM203.format(a=left, b=right))
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return errors
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def get_sim208(node: ast.UnaryOp) -> List[Tuple[int, int, str]]:
|
|
95
|
+
"""Get a list of all calls of the type "not (not a)"."""
|
|
96
|
+
SIM208 = "SIM208 Use '{a}' instead of 'not (not {a})'"
|
|
97
|
+
errors: List[Tuple[int, int, str]] = []
|
|
98
|
+
if (
|
|
99
|
+
not isinstance(node.op, ast.Not)
|
|
100
|
+
or not isinstance(node.operand, ast.UnaryOp)
|
|
101
|
+
or not isinstance(node.operand.op, ast.Not)
|
|
102
|
+
):
|
|
103
|
+
return errors
|
|
104
|
+
a = to_source(node.operand.operand)
|
|
105
|
+
errors.append((node.lineno, node.col_offset, SIM208.format(a=a)))
|
|
106
|
+
return errors
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Core Library
|
|
2
|
+
import ast
|
|
3
|
+
from typing import List, Tuple
|
|
4
|
+
|
|
5
|
+
# First party
|
|
6
|
+
from flake8_simplify.utils import to_source
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_sim117(node: ast.With) -> List[Tuple[int, int, str]]:
|
|
10
|
+
"""
|
|
11
|
+
Find multiple with-statements with same scope.
|
|
12
|
+
|
|
13
|
+
With(
|
|
14
|
+
items=[
|
|
15
|
+
withitem(
|
|
16
|
+
context_expr=Call(
|
|
17
|
+
func=Name(id='A', ctx=Load()),
|
|
18
|
+
args=[],
|
|
19
|
+
keywords=[],
|
|
20
|
+
),
|
|
21
|
+
optional_vars=Name(id='a', ctx=Store()),
|
|
22
|
+
),
|
|
23
|
+
],
|
|
24
|
+
body=[
|
|
25
|
+
With(
|
|
26
|
+
items=[
|
|
27
|
+
withitem(
|
|
28
|
+
context_expr=Call(
|
|
29
|
+
func=Name(id='B', ctx=Load()),
|
|
30
|
+
args=[],
|
|
31
|
+
keywords=[],
|
|
32
|
+
),
|
|
33
|
+
optional_vars=Name(id='b', ctx=Store()),
|
|
34
|
+
),
|
|
35
|
+
],
|
|
36
|
+
body=[
|
|
37
|
+
Expr(
|
|
38
|
+
value=Call(
|
|
39
|
+
func=Name(id='print', ctx=Load()),
|
|
40
|
+
args=[Constant(value='hello', kind=None)],
|
|
41
|
+
keywords=[],
|
|
42
|
+
),
|
|
43
|
+
),
|
|
44
|
+
],
|
|
45
|
+
type_comment=None,
|
|
46
|
+
),
|
|
47
|
+
],
|
|
48
|
+
type_comment=None,
|
|
49
|
+
),
|
|
50
|
+
"""
|
|
51
|
+
SIM117 = "SIM117 Use '{merged_with}' instead of multiple with statements"
|
|
52
|
+
errors: List[Tuple[int, int, str]] = []
|
|
53
|
+
if not (len(node.body) == 1 and isinstance(node.body[0], ast.With)):
|
|
54
|
+
return errors
|
|
55
|
+
with_items = []
|
|
56
|
+
for withitem in node.items + node.body[0].items:
|
|
57
|
+
with_items.append(f"{to_source(withitem)}")
|
|
58
|
+
merged_with = f"with {', '.join(with_items)}:"
|
|
59
|
+
errors.append(
|
|
60
|
+
(node.lineno, node.col_offset, SIM117.format(merged_with=merged_with))
|
|
61
|
+
)
|
|
62
|
+
return errors
|
flake8_simplify/utils.py
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Core Library
|
|
2
|
+
import ast
|
|
3
|
+
import itertools
|
|
4
|
+
from collections import defaultdict
|
|
5
|
+
from typing import DefaultDict, List, Tuple, Union
|
|
6
|
+
|
|
7
|
+
# Third party
|
|
8
|
+
import astor
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# The following types were created to help mypy understand that there is a
|
|
12
|
+
# "parent" attribute on the ast.AST nodes.
|
|
13
|
+
class UnaryOp(ast.UnaryOp):
|
|
14
|
+
def __init__(self, orig: ast.UnaryOp) -> None:
|
|
15
|
+
self.op = orig.op
|
|
16
|
+
self.operand = orig.operand
|
|
17
|
+
# For all ast.*:
|
|
18
|
+
self.lineno = orig.lineno
|
|
19
|
+
self.col_offset = orig.col_offset
|
|
20
|
+
self.parent: ast.Expr = orig.parent # type: ignore
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Call(ast.Call):
|
|
24
|
+
"""For mypy so that it knows that added attributes exist."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, orig: ast.Call) -> None:
|
|
27
|
+
self.func = orig.func
|
|
28
|
+
self.args = orig.args
|
|
29
|
+
self.keywords = orig.keywords
|
|
30
|
+
# For all ast.*:
|
|
31
|
+
self.lineno = orig.lineno
|
|
32
|
+
self.col_offset = orig.col_offset
|
|
33
|
+
|
|
34
|
+
# Added attributes
|
|
35
|
+
self.parent: ast.Expr = orig.parent # type: ignore
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class If(ast.If):
|
|
39
|
+
"""For mypy so that it knows that added attributes exist."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, orig: ast.If) -> None:
|
|
42
|
+
self.test = orig.test
|
|
43
|
+
self.body = orig.body
|
|
44
|
+
self.orelse = orig.orelse
|
|
45
|
+
|
|
46
|
+
# For all ast.*:
|
|
47
|
+
self.lineno = orig.lineno
|
|
48
|
+
self.col_offset = orig.col_offset
|
|
49
|
+
|
|
50
|
+
# Added attributes
|
|
51
|
+
self.parent: ast.Expr = orig.parent # type: ignore
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class For(ast.For):
|
|
55
|
+
"""For mypy so that it knows that added attributes exist."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, orig: ast.For) -> None:
|
|
58
|
+
self.target = orig.target
|
|
59
|
+
self.iter = orig.iter
|
|
60
|
+
self.body = orig.body
|
|
61
|
+
self.orelse = orig.orelse
|
|
62
|
+
# For all ast.*:
|
|
63
|
+
self.lineno = orig.lineno
|
|
64
|
+
self.col_offset = orig.col_offset
|
|
65
|
+
|
|
66
|
+
# Added attributes
|
|
67
|
+
self.parent: ast.AST = orig.parent # type: ignore
|
|
68
|
+
self.previous_sibling = orig.previous_sibling # type: ignore
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Assign(ast.Assign):
|
|
72
|
+
"""For mypy so that it knows that added attributes exist."""
|
|
73
|
+
|
|
74
|
+
def __init__(self, orig: ast.Assign) -> None:
|
|
75
|
+
self.targets = orig.targets
|
|
76
|
+
self.value = orig.value
|
|
77
|
+
# For all ast.*:
|
|
78
|
+
self.orig = orig
|
|
79
|
+
self.lineno = orig.lineno
|
|
80
|
+
self.col_offset = orig.col_offset
|
|
81
|
+
|
|
82
|
+
# Added attributes
|
|
83
|
+
self.parent: ast.AST = orig.parent # type: ignore
|
|
84
|
+
self.previous_sibling = orig.previous_sibling # type: ignore
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def to_source(
|
|
88
|
+
node: Union[None, ast.expr, ast.Expr, ast.withitem, ast.slice, ast.Assign]
|
|
89
|
+
) -> str:
|
|
90
|
+
if node is None:
|
|
91
|
+
return "None"
|
|
92
|
+
source: str = astor.to_source(node).strip()
|
|
93
|
+
source = strip_parenthesis(source)
|
|
94
|
+
source = strip_triple_quotes(source)
|
|
95
|
+
source = use_double_quotes(source)
|
|
96
|
+
return source
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def strip_parenthesis(string: str) -> str:
|
|
100
|
+
if len(string) >= 2 and string[0] == "(" and string[-1] == ")":
|
|
101
|
+
return string[1:-1]
|
|
102
|
+
return string
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def strip_triple_quotes(string: str) -> str:
|
|
106
|
+
quotes = '"""'
|
|
107
|
+
is_tripple_quoted = string.startswith(quotes) and string.endswith(quotes)
|
|
108
|
+
if not (
|
|
109
|
+
is_tripple_quoted and '"' not in string[len(quotes) : -len(quotes)]
|
|
110
|
+
):
|
|
111
|
+
return string
|
|
112
|
+
string = string[len(quotes) : -len(quotes)]
|
|
113
|
+
string = f'"{string}"'
|
|
114
|
+
if len(string) == 0:
|
|
115
|
+
string = '""'
|
|
116
|
+
return string
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def use_double_quotes(string: str) -> str:
|
|
120
|
+
quotes = "'''"
|
|
121
|
+
if string.startswith(quotes) and string.endswith(quotes):
|
|
122
|
+
return f'"""{string[len(quotes):-len(quotes)]}"""'
|
|
123
|
+
if len(string) >= 2 and string[0] == "'" and string[-1] == "'":
|
|
124
|
+
return f'"{string[1:-1]}"'
|
|
125
|
+
return string
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def is_body_same(body1: List[ast.stmt], body2: List[ast.stmt]) -> bool:
|
|
129
|
+
"""Check if two lists of expressions are equivalent."""
|
|
130
|
+
if len(body1) != len(body2):
|
|
131
|
+
return False
|
|
132
|
+
for a, b in zip(body1, body2):
|
|
133
|
+
try:
|
|
134
|
+
stmt_equal = is_stmt_equal(a, b)
|
|
135
|
+
except RecursionError: # maximum recursion depth
|
|
136
|
+
stmt_equal = False
|
|
137
|
+
if not stmt_equal:
|
|
138
|
+
return False
|
|
139
|
+
return True
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def is_stmt_equal(a: ast.stmt, b: ast.stmt) -> bool:
|
|
143
|
+
if type(a) is not type(b):
|
|
144
|
+
return False
|
|
145
|
+
if isinstance(a, ast.AST):
|
|
146
|
+
specials = (
|
|
147
|
+
"lineno",
|
|
148
|
+
"col_offset",
|
|
149
|
+
"ctx",
|
|
150
|
+
"end_lineno",
|
|
151
|
+
"parent",
|
|
152
|
+
"previous_sibling",
|
|
153
|
+
"next_sibling",
|
|
154
|
+
)
|
|
155
|
+
for k, v in vars(a).items():
|
|
156
|
+
if k.startswith("_") or k in specials:
|
|
157
|
+
continue
|
|
158
|
+
if not is_stmt_equal(v, getattr(b, k)):
|
|
159
|
+
return False
|
|
160
|
+
return True
|
|
161
|
+
elif isinstance(a, list):
|
|
162
|
+
if len(a) != len(b):
|
|
163
|
+
return False
|
|
164
|
+
return all(itertools.starmap(is_stmt_equal, zip(a, b)))
|
|
165
|
+
else:
|
|
166
|
+
return a == b
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def get_if_body_pairs(node: ast.If) -> List[Tuple[ast.expr, List[ast.stmt]]]:
|
|
170
|
+
pairs = [(node.test, node.body)]
|
|
171
|
+
orelse = node.orelse
|
|
172
|
+
while (
|
|
173
|
+
isinstance(orelse, list)
|
|
174
|
+
and len(orelse) == 1
|
|
175
|
+
and isinstance(orelse[0], ast.If)
|
|
176
|
+
):
|
|
177
|
+
pairs.append((orelse[0].test, orelse[0].body))
|
|
178
|
+
orelse = orelse[0].orelse
|
|
179
|
+
return pairs
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def is_constant_increase(expr: ast.AugAssign) -> bool:
|
|
183
|
+
return isinstance(expr.op, ast.Add) and (
|
|
184
|
+
(isinstance(expr.value, ast.Constant) and expr.value.value == 1)
|
|
185
|
+
or (isinstance(expr.value, ast.Num) and expr.value.n == 1)
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def is_exception_check(node: ast.If) -> bool:
|
|
190
|
+
if len(node.body) == 1 and isinstance(node.body[0], ast.Raise):
|
|
191
|
+
return True
|
|
192
|
+
return False
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def is_same_expression(a: ast.expr, b: ast.expr) -> bool:
|
|
196
|
+
"""Check if two expressions are equal to each other."""
|
|
197
|
+
if isinstance(a, ast.Name) and isinstance(b, ast.Name):
|
|
198
|
+
return a.id == b.id
|
|
199
|
+
else:
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def expression_uses_variable(expr: ast.expr, var: str) -> bool:
|
|
204
|
+
if var in to_source(expr):
|
|
205
|
+
# This is WAY too broad, but it's better to have false-negatives
|
|
206
|
+
# than false-positives
|
|
207
|
+
return True
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _get_duplicated_isinstance_call_by_node(node: ast.BoolOp) -> List[str]:
|
|
212
|
+
"""
|
|
213
|
+
Get a list of isinstance arguments which could be shortened.
|
|
214
|
+
|
|
215
|
+
This checks SIM101.
|
|
216
|
+
|
|
217
|
+
Examples
|
|
218
|
+
--------
|
|
219
|
+
>> g = _get_duplicated_isinstance_call_by_node
|
|
220
|
+
>> g("isinstance(a, int) or isinstance(a, float) or isinstance(b, int)
|
|
221
|
+
['a']
|
|
222
|
+
>> g("isinstance(a, int) or isinstance(b, float) or isinstance(b, int)
|
|
223
|
+
['b']
|
|
224
|
+
"""
|
|
225
|
+
counter: DefaultDict[str, int] = defaultdict(int)
|
|
226
|
+
|
|
227
|
+
for call in node.values:
|
|
228
|
+
# Make sure that this function call is actually a call of the built-in
|
|
229
|
+
# "isinstance"
|
|
230
|
+
if not isinstance(call, ast.Call) or len(call.args) != 2:
|
|
231
|
+
continue
|
|
232
|
+
function_name = to_source(call.func)
|
|
233
|
+
if function_name != "isinstance":
|
|
234
|
+
continue
|
|
235
|
+
|
|
236
|
+
# Collect the name of the argument
|
|
237
|
+
isinstance_arg0_name = to_source(call.args[0])
|
|
238
|
+
counter[isinstance_arg0_name] += 1
|
|
239
|
+
return [arg0_name for arg0_name, count in counter.items() if count > 1]
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def body_contains_continue(stmts: List[ast.stmt]) -> bool:
|
|
243
|
+
return any(
|
|
244
|
+
isinstance(stmt, ast.Continue)
|
|
245
|
+
or (isinstance(stmt, ast.If) and body_contains_continue(stmt.body))
|
|
246
|
+
for stmt in stmts
|
|
247
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Martin Thoma
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|