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