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