flake8_simplify 0.31.0__tar.gz → 0.31.1__tar.gz

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.
Files changed (30) hide show
  1. {flake8_simplify-0.31.0 → flake8_simplify-0.31.1}/PKG-INFO +1 -1
  2. flake8_simplify-0.31.1/flake8_simplify/__init__.py +45 -0
  3. flake8_simplify-0.31.1/flake8_simplify/registry.py +71 -0
  4. {flake8_simplify-0.31.0 → flake8_simplify-0.31.1}/flake8_simplify/rules/ast_assign.py +25 -47
  5. {flake8_simplify-0.31.0 → flake8_simplify-0.31.1}/flake8_simplify/rules/ast_bool_op.py +46 -54
  6. {flake8_simplify-0.31.0 → flake8_simplify-0.31.1}/flake8_simplify/rules/ast_call.py +40 -84
  7. flake8_simplify-0.31.1/flake8_simplify/rules/ast_classdef.py +19 -0
  8. {flake8_simplify-0.31.0 → flake8_simplify-0.31.1}/flake8_simplify/rules/ast_compare.py +15 -24
  9. flake8_simplify-0.31.1/flake8_simplify/rules/ast_expr.py +64 -0
  10. {flake8_simplify-0.31.0 → flake8_simplify-0.31.1}/flake8_simplify/rules/ast_for.py +80 -80
  11. flake8_simplify-0.31.1/flake8_simplify/rules/ast_if.py +505 -0
  12. flake8_simplify-0.31.1/flake8_simplify/rules/ast_ifexp.py +61 -0
  13. {flake8_simplify-0.31.0 → flake8_simplify-0.31.1}/flake8_simplify/rules/ast_subscript.py +10 -14
  14. flake8_simplify-0.31.1/flake8_simplify/rules/ast_try.py +94 -0
  15. flake8_simplify-0.31.1/flake8_simplify/rules/ast_unary_op.py +82 -0
  16. {flake8_simplify-0.31.0 → flake8_simplify-0.31.1}/flake8_simplify/rules/ast_with.py +7 -8
  17. {flake8_simplify-0.31.0 → flake8_simplify-0.31.1}/flake8_simplify/utils.py +64 -69
  18. {flake8_simplify-0.31.0 → flake8_simplify-0.31.1}/pyproject.toml +4 -1
  19. {flake8_simplify-0.31.0 → flake8_simplify-0.31.1}/pyproject.toml.orig +5 -1
  20. flake8_simplify-0.31.0/flake8_simplify/__init__.py +0 -170
  21. flake8_simplify-0.31.0/flake8_simplify/constants.py +0 -5
  22. flake8_simplify-0.31.0/flake8_simplify/rules/ast_classdef.py +0 -22
  23. flake8_simplify-0.31.0/flake8_simplify/rules/ast_expr.py +0 -100
  24. flake8_simplify-0.31.0/flake8_simplify/rules/ast_if.py +0 -512
  25. flake8_simplify-0.31.0/flake8_simplify/rules/ast_ifexp.py +0 -65
  26. flake8_simplify-0.31.0/flake8_simplify/rules/ast_try.py +0 -93
  27. flake8_simplify-0.31.0/flake8_simplify/rules/ast_unary_op.py +0 -103
  28. {flake8_simplify-0.31.0 → flake8_simplify-0.31.1}/LICENSE +0 -0
  29. {flake8_simplify-0.31.0 → flake8_simplify-0.31.1}/README.md +0 -0
  30. {flake8_simplify-0.31.0 → flake8_simplify-0.31.1}/flake8_simplify/rules/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flake8_simplify
3
- Version: 0.31.0
3
+ Version: 0.31.1
4
4
  Summary: flake8 plugin which checks for code that can be simplified
5
5
  Keywords: flake8
6
6
  Author: Martin Thoma
@@ -0,0 +1,45 @@
1
+ import ast
2
+ import importlib.metadata as importlib_metadata
3
+ from collections.abc import Generator
4
+ from typing import Any
5
+
6
+ from flake8_simplify.registry import get_rules
7
+ from flake8_simplify.utils import add_meta
8
+
9
+
10
+ class Visitor(ast.NodeVisitor):
11
+ """Run every rule registered for a node type on each node of that type."""
12
+
13
+ def __init__(self) -> None:
14
+ self.errors: list[tuple[int, int, str]] = []
15
+ self._rules = get_rules()
16
+
17
+ def visit(self, node: ast.AST) -> Any:
18
+ for rule in self._rules.get(type(node), ()):
19
+ for violation in rule.check(node):
20
+ self.errors.append(
21
+ (
22
+ violation.node.lineno,
23
+ violation.node.col_offset,
24
+ f"{rule.code} {violation.message}",
25
+ )
26
+ )
27
+ self.generic_visit(node)
28
+
29
+
30
+ class Plugin:
31
+ name = __name__
32
+ version = importlib_metadata.version(__name__) # type: ignore
33
+
34
+ def __init__(self, tree: ast.AST):
35
+ self._tree = tree
36
+
37
+ def run(self) -> Generator[tuple[int, int, str, type[Any]], None, None]:
38
+ visitor = Visitor()
39
+
40
+ # Add parent
41
+ add_meta(self._tree)
42
+ visitor.visit(self._tree)
43
+
44
+ for line, col, msg in visitor.errors:
45
+ yield line, col, msg, type(self)
@@ -0,0 +1,71 @@
1
+ """
2
+ Registry of all rules.
3
+
4
+ A rule is a generator function which receives a node of the given type and
5
+ yields a ``Violation`` for every problem it finds. It registers itself with
6
+ the ``rule`` decorator; modules in ``flake8_simplify/rules/`` are discovered
7
+ automatically, so adding a rule never requires touching any other file.
8
+
9
+ @rule("SIM120", ast.ClassDef)
10
+ def get_sim120(node: ast.ClassDef) -> Iterator[Violation]:
11
+ if ...:
12
+ yield Violation(node, "Use 'class Foo:' ...")
13
+ """
14
+
15
+ import ast
16
+ import functools
17
+ import importlib
18
+ import pkgutil
19
+ from collections import defaultdict
20
+ from collections.abc import Callable, Iterator
21
+ from dataclasses import dataclass
22
+ from typing import Any, NamedTuple
23
+
24
+
25
+ class Violation(NamedTuple):
26
+ """A problem found by a rule, reported at the position of ``node``."""
27
+
28
+ node: ast.expr | ast.stmt
29
+ message: str
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class Rule:
34
+ code: str
35
+ node_type: type[ast.AST]
36
+ check: Callable[[Any], Iterator[Violation]]
37
+
38
+
39
+ _RULES: list[Rule] = []
40
+
41
+
42
+ def rule(
43
+ code: str,
44
+ node_type: type[ast.AST],
45
+ ) -> Callable[
46
+ [Callable[[Any], Iterator[Violation]]],
47
+ Callable[[Any], Iterator[Violation]],
48
+ ]:
49
+ """Register a function as the check for ``code`` on ``node_type`` nodes."""
50
+
51
+ def register(
52
+ check: Callable[[Any], Iterator[Violation]],
53
+ ) -> Callable[[Any], Iterator[Violation]]:
54
+ _RULES.append(Rule(code, node_type, check))
55
+ return check
56
+
57
+ return register
58
+
59
+
60
+ @functools.cache
61
+ def get_rules() -> dict[type[ast.AST], list[Rule]]:
62
+ """Get all rules, grouped by the type of node they check."""
63
+ from flake8_simplify import rules
64
+
65
+ for module in pkgutil.iter_modules(rules.__path__):
66
+ importlib.import_module(f"{rules.__name__}.{module.name}")
67
+
68
+ by_node_type: dict[type[ast.AST], list[Rule]] = defaultdict(list)
69
+ for registered in _RULES:
70
+ by_node_type[registered.node_type].append(registered)
71
+ return by_node_type
@@ -1,25 +1,18 @@
1
1
  import ast
2
+ from collections.abc import Iterator
2
3
 
3
- from flake8_simplify.utils import Assign, expression_uses_variable, to_source
4
+ from flake8_simplify.registry import Violation, rule
5
+ from flake8_simplify.utils import (
6
+ expression_uses_variable,
7
+ get_next_sibling,
8
+ get_parent,
9
+ in_same_block,
10
+ to_source,
11
+ )
4
12
 
5
13
 
6
- def _in_same_block(first: ast.stmt, second: ast.stmt) -> bool:
7
- """
8
- Check that both statements are part of the same statement list.
9
-
10
- Siblings are linked across the fields of a node, so the last statement of
11
- ``body`` has the first statement of ``orelse`` as next sibling.
12
- """
13
- parent = getattr(first, "parent", None)
14
- if parent is None:
15
- return True
16
- for _, value in ast.iter_fields(parent):
17
- if isinstance(value, list) and first in value:
18
- return second in value
19
- return True
20
-
21
-
22
- def get_sim904(node: ast.Assign) -> list[tuple[int, int, str]]:
14
+ @rule("SIM904", ast.Assign)
15
+ def get_sim904(node: ast.Assign) -> Iterator[Violation]:
23
16
  """
24
17
  Assign values to dictionary directly at initialization.
25
18
 
@@ -52,9 +45,8 @@ def get_sim904(node: ast.Assign) -> list[tuple[int, int, str]]:
52
45
  ),
53
46
  ]
54
47
  """
55
- RULE = "SIM904 Initialize dictionary '{dict_name}' directly"
56
- errors: list[tuple[int, int, str]] = []
57
- n2 = node.next_sibling # type: ignore
48
+ RULE = "Initialize dictionary '{dict_name}' directly"
49
+ n2 = get_next_sibling(node)
58
50
  if not (
59
51
  isinstance(node.value, ast.Dict)
60
52
  and isinstance(n2, ast.Assign)
@@ -64,26 +56,20 @@ def get_sim904(node: ast.Assign) -> list[tuple[int, int, str]]:
64
56
  and isinstance(n2.targets[0].value, ast.Name)
65
57
  and isinstance(node.targets[0], ast.Name)
66
58
  and n2.targets[0].value.id == node.targets[0].id
67
- and _in_same_block(node, n2)
59
+ and in_same_block(node, n2)
68
60
  ):
69
- return errors
61
+ return
70
62
 
71
63
  dict_name = to_source(node.targets[0])
72
64
  # Capture cases where the assigned value uses another dictionary value
73
65
  if expression_uses_variable(n2.value, dict_name):
74
- return errors
66
+ return
75
67
 
76
- errors.append(
77
- (
78
- node.lineno,
79
- node.col_offset,
80
- RULE.format(dict_name=dict_name),
81
- )
82
- )
83
- return errors
68
+ yield Violation(node, RULE.format(dict_name=dict_name))
84
69
 
85
70
 
86
- def get_sim909(node: Assign) -> list[tuple[int, int, str]]:
71
+ @rule("SIM909", ast.Assign)
72
+ def get_sim909(node: ast.Assign) -> Iterator[Violation]:
87
73
  """
88
74
  Avoid reflexive assignments
89
75
 
@@ -103,8 +89,7 @@ def get_sim909(node: Assign) -> list[tuple[int, int, str]]:
103
89
  ),
104
90
  ]
105
91
  """
106
- RULE = "SIM909 Remove reflexive assignment '{code}'"
107
- errors: list[tuple[int, int, str]] = []
92
+ RULE = "Remove reflexive assignment '{code}'"
108
93
 
109
94
  names = []
110
95
  if isinstance(node.value, (ast.Name, ast.Subscript, ast.Tuple)):
@@ -113,18 +98,11 @@ def get_sim909(node: Assign) -> list[tuple[int, int, str]]:
113
98
  names.append(to_source(target))
114
99
 
115
100
  if len(names) == len(set(names)):
116
- return errors
101
+ return
117
102
 
118
- if isinstance(node.parent, ast.ClassDef):
119
- return errors
103
+ if isinstance(get_parent(node), ast.ClassDef):
104
+ return
120
105
 
121
- code = to_source(node.orig)
106
+ code = to_source(node)
122
107
 
123
- errors.append(
124
- (
125
- node.lineno,
126
- node.col_offset,
127
- RULE.format(code=code),
128
- )
129
- )
130
- return errors
108
+ yield Violation(node, RULE.format(code=code))
@@ -1,7 +1,8 @@
1
1
  import ast
2
2
  from collections import defaultdict
3
+ from collections.abc import Iterator
3
4
 
4
- from flake8_simplify.constants import BOOL_CONST_TYPES
5
+ from flake8_simplify.registry import Violation, rule
5
6
  from flake8_simplify.utils import (
6
7
  _get_duplicated_isinstance_call_by_node,
7
8
  is_same_expression,
@@ -9,24 +10,24 @@ from flake8_simplify.utils import (
9
10
  )
10
11
 
11
12
 
13
+ @rule("SIM101", ast.BoolOp)
12
14
  def get_sim101(
13
15
  node: ast.BoolOp,
14
- ) -> list[tuple[int, int, str]]:
16
+ ) -> Iterator[Violation]:
15
17
  """Get a positions where the duplicate isinstance problem appears."""
16
- errors: list[tuple[int, int, str]] = []
17
18
  if not isinstance(node.op, ast.Or):
18
- return errors
19
+ return
19
20
 
20
21
  RULE = (
21
- "SIM101 Multiple isinstance-calls which can be merged into a single "
22
+ "Multiple isinstance-calls which can be merged into a single "
22
23
  "call for variable '{var}'"
23
24
  )
24
25
  for var in _get_duplicated_isinstance_call_by_node(node):
25
- errors.append((node.lineno, node.col_offset, RULE.format(var=var)))
26
- return errors
26
+ yield Violation(node, RULE.format(var=var))
27
27
 
28
28
 
29
- def get_sim109(node: ast.BoolOp) -> list[tuple[int, int, str]]:
29
+ @rule("SIM109", ast.BoolOp)
30
+ def get_sim109(node: ast.BoolOp) -> Iterator[Violation]:
30
31
  """
31
32
  Check if multiple equalities with the same value are combined via "or".
32
33
 
@@ -46,9 +47,8 @@ def get_sim109(node: ast.BoolOp) -> list[tuple[int, int, str]]:
46
47
  ],
47
48
  )
48
49
  """
49
- errors: list[tuple[int, int, str]] = []
50
50
  if not isinstance(node.op, ast.Or):
51
- return errors
51
+ return
52
52
  equalities = [
53
53
  value
54
54
  for value in node.values
@@ -64,25 +64,22 @@ def get_sim109(node: ast.BoolOp) -> list[tuple[int, int, str]]:
64
64
  and isinstance(eq.comparators[0], ast.Name)
65
65
  ):
66
66
  id2vals[eq.left.id].append(eq.comparators[0])
67
- RULE = "SIM109 Use '{value} in {values}' instead of '{or_op}'"
67
+ RULE = "Use '{value} in {values}' instead of '{or_op}'"
68
68
  for value, values in id2vals.items():
69
69
  if len(values) == 1:
70
70
  continue
71
- errors.append(
72
- (
73
- node.lineno,
74
- node.col_offset,
75
- RULE.format(
76
- or_op=to_source(node),
77
- value=value,
78
- values=f"({to_source(ast.Tuple(elts=values))})", # type: ignore
79
- ),
80
- )
71
+ yield Violation(
72
+ node,
73
+ RULE.format(
74
+ or_op=to_source(node),
75
+ value=value,
76
+ values=f"({to_source(ast.Tuple(elts=values))})", # type: ignore
77
+ ),
81
78
  )
82
- return errors
83
79
 
84
80
 
85
- def get_sim220(node: ast.BoolOp) -> list[tuple[int, int, str]]:
81
+ @rule("SIM220", ast.BoolOp)
82
+ def get_sim220(node: ast.BoolOp) -> Iterator[Violation]:
86
83
  """
87
84
  Get a list of all calls of the type "a and not a".
88
85
 
@@ -97,9 +94,8 @@ def get_sim220(node: ast.BoolOp) -> list[tuple[int, int, str]]:
97
94
  ],
98
95
  )
99
96
  """
100
- errors: list[tuple[int, int, str]] = []
101
97
  if not (isinstance(node.op, ast.And) and len(node.values) >= 2):
102
- return errors
98
+ return
103
99
  # We have a boolean And. Let's make sure there is two times the same
104
100
  # expression, but once with a "not"
105
101
  negated_expressions = []
@@ -110,19 +106,19 @@ def get_sim220(node: ast.BoolOp) -> list[tuple[int, int, str]]:
110
106
  else:
111
107
  non_negated_expressions.append(exp)
112
108
  if len(negated_expressions) == 0:
113
- return errors
109
+ return
114
110
 
115
- RULE = "SIM220 Use 'False' instead of '{a} and not {a}'"
111
+ RULE = "Use 'False' instead of '{a} and not {a}'"
116
112
  for negated_expression in negated_expressions:
117
113
  for non_negated_expression in non_negated_expressions:
118
114
  if is_same_expression(negated_expression, non_negated_expression):
119
115
  a = to_source(negated_expression)
120
- errors.append((node.lineno, node.col_offset, RULE.format(a=a)))
121
- return errors
122
- return errors
116
+ yield Violation(node, RULE.format(a=a))
117
+ return
123
118
 
124
119
 
125
- def get_sim221(node: ast.BoolOp) -> list[tuple[int, int, str]]:
120
+ @rule("SIM221", ast.BoolOp)
121
+ def get_sim221(node: ast.BoolOp) -> Iterator[Violation]:
126
122
  """
127
123
  Get a list of all calls of the type "a or not a".
128
124
 
@@ -137,9 +133,8 @@ def get_sim221(node: ast.BoolOp) -> list[tuple[int, int, str]]:
137
133
  ],
138
134
  )
139
135
  """
140
- errors: list[tuple[int, int, str]] = []
141
136
  if not (isinstance(node.op, ast.Or) and len(node.values) >= 2):
142
- return errors
137
+ return
143
138
  # We have a boolean OR. Let's make sure there is two times the same
144
139
  # expression, but once with a "not"
145
140
  negated_expressions = []
@@ -150,19 +145,19 @@ def get_sim221(node: ast.BoolOp) -> list[tuple[int, int, str]]:
150
145
  else:
151
146
  non_negated_expressions.append(exp)
152
147
  if len(negated_expressions) == 0:
153
- return errors
148
+ return
154
149
 
155
- RULE = "SIM221 Use 'True' instead of '{a} or not {a}'"
150
+ RULE = "Use 'True' instead of '{a} or not {a}'"
156
151
  for negated_expression in negated_expressions:
157
152
  for non_negated_expression in non_negated_expressions:
158
153
  if is_same_expression(negated_expression, non_negated_expression):
159
154
  a = to_source(negated_expression)
160
- errors.append((node.lineno, node.col_offset, RULE.format(a=a)))
161
- return errors
162
- return errors
155
+ yield Violation(node, RULE.format(a=a))
156
+ return
163
157
 
164
158
 
165
- def get_sim222(node: ast.BoolOp) -> list[tuple[int, int, str]]:
159
+ @rule("SIM222", ast.BoolOp)
160
+ def get_sim222(node: ast.BoolOp) -> Iterator[Violation]:
166
161
  """
167
162
  Get a list of all calls of the type "... or True".
168
163
 
@@ -177,19 +172,18 @@ def get_sim222(node: ast.BoolOp) -> list[tuple[int, int, str]]:
177
172
  ],
178
173
  )
179
174
  """
180
- errors: list[tuple[int, int, str]] = []
181
175
  if not (isinstance(node.op, ast.Or)):
182
- return errors
176
+ return
183
177
 
184
- RULE = "SIM222 Use 'True' instead of '... or True'"
178
+ RULE = "Use 'True' instead of '... or True'"
185
179
  for exp in node.values:
186
- if isinstance(exp, BOOL_CONST_TYPES) and exp.value is True:
187
- errors.append((node.lineno, node.col_offset, RULE))
188
- return errors
189
- return errors
180
+ if isinstance(exp, ast.Constant) and exp.value is True:
181
+ yield Violation(node, RULE)
182
+ return
190
183
 
191
184
 
192
- def get_sim223(node: ast.BoolOp) -> list[tuple[int, int, str]]:
185
+ @rule("SIM223", ast.BoolOp)
186
+ def get_sim223(node: ast.BoolOp) -> Iterator[Violation]:
193
187
  """
194
188
  Get a list of all calls of the type "... and False".
195
189
 
@@ -204,13 +198,11 @@ def get_sim223(node: ast.BoolOp) -> list[tuple[int, int, str]]:
204
198
  ],
205
199
  )
206
200
  """
207
- errors: list[tuple[int, int, str]] = []
208
201
  if not (isinstance(node.op, ast.And)):
209
- return errors
202
+ return
210
203
 
211
- RULE = "SIM223 Use 'False' instead of '... and False'"
204
+ RULE = "Use 'False' instead of '... and False'"
212
205
  for exp in node.values:
213
- if isinstance(exp, BOOL_CONST_TYPES) and exp.value is False:
214
- errors.append((node.lineno, node.col_offset, RULE))
215
- return errors
216
- return errors
206
+ if isinstance(exp, ast.Constant) and exp.value is False:
207
+ yield Violation(node, RULE)
208
+ return
@@ -1,14 +1,13 @@
1
1
  import ast
2
2
  import json
3
- import logging
3
+ from collections.abc import Iterator
4
4
 
5
- from flake8_simplify.constants import BOOL_CONST_TYPES
6
- from flake8_simplify.utils import Call, to_source
5
+ from flake8_simplify.registry import Violation, rule
6
+ from flake8_simplify.utils import get_parent, to_source
7
7
 
8
- logger = logging.getLogger(__name__)
9
8
 
10
-
11
- def get_sim115(node: Call) -> list[tuple[int, int, str]]:
9
+ @rule("SIM115", ast.Call)
10
+ def get_sim115(node: ast.Call) -> Iterator[Violation]:
12
11
  """
13
12
  Find places where open() is called without a context handler.
14
13
 
@@ -36,22 +35,21 @@ def get_sim115(node: Call) -> list[tuple[int, int, str]]:
36
35
  ),
37
36
  ),
38
37
  """
39
- RULE = "SIM115 Use context handler for opening files"
40
- errors: list[tuple[int, int, str]] = []
38
+ RULE = "Use context handler for opening files"
41
39
  if not (
42
40
  isinstance(node.func, ast.Name)
43
41
  and node.func.id == "open"
44
- and not isinstance(node.parent, ast.withitem)
42
+ and not isinstance(get_parent(node), ast.withitem)
45
43
  ):
46
- return errors
47
- errors.append((node.lineno, node.col_offset, RULE))
48
- return errors
44
+ return
45
+ yield Violation(node, RULE)
49
46
 
50
47
 
51
48
  # Experimental rules
52
49
 
53
50
 
54
- def get_sim901(node: ast.Call) -> list[tuple[int, int, str]]:
51
+ @rule("SIM901", ast.Call)
52
+ def get_sim901(node: ast.Call) -> Iterator[Violation]:
55
53
  """
56
54
  Get a list of all calls of the type "bool(comparison)".
57
55
 
@@ -67,60 +65,44 @@ def get_sim901(node: ast.Call) -> list[tuple[int, int, str]]:
67
65
  keywords=[],
68
66
  )
69
67
  """
70
- RULE = "SIM901 Use '{expected}' instead of '{actual}'"
71
- errors: list[tuple[int, int, str]] = []
68
+ RULE = "Use '{expected}' instead of '{actual}'"
72
69
  if not (
73
70
  isinstance(node.func, ast.Name)
74
71
  and node.func.id == "bool"
75
72
  and len(node.args) == 1
76
73
  and isinstance(node.args[0], ast.Compare)
77
74
  ):
78
- return errors
75
+ return
79
76
 
80
77
  actual = to_source(node)
81
78
  expected = to_source(node.args[0])
82
79
 
83
- errors.append(
84
- (
85
- node.lineno,
86
- node.col_offset,
87
- RULE.format(actual=actual, expected=expected),
88
- )
89
- )
90
- return errors
80
+ yield Violation(node, RULE.format(actual=actual, expected=expected))
91
81
 
92
82
 
93
- def get_sim905(node: ast.Call) -> list[tuple[int, int, str]]:
94
- RULE = "SIM905 Use '{expected}' instead of '{actual}'"
95
- errors: list[tuple[int, int, str]] = []
83
+ @rule("SIM905", ast.Call)
84
+ def get_sim905(node: ast.Call) -> Iterator[Violation]:
85
+ RULE = "Use '{expected}' instead of '{actual}'"
96
86
  if not (
97
87
  isinstance(node.func, ast.Attribute)
98
88
  and node.func.attr == "split"
99
89
  and isinstance(node.func.value, ast.Constant)
100
90
  and isinstance(node.func.value.value, str)
91
+ and not node.args
92
+ and not node.keywords
101
93
  ):
102
- return errors
94
+ return
103
95
 
104
- if isinstance(node.func.value, ast.Constant):
105
- value = node.func.value.value
106
- else:
107
- value = node.func.value.value
96
+ value = node.func.value.value
108
97
 
109
98
  expected = json.dumps(value.split())
110
99
  actual = to_source(node.func.value) + ".split()"
111
- errors.append(
112
- (
113
- node.lineno,
114
- node.col_offset,
115
- RULE.format(expected=expected, actual=actual),
116
- )
117
- )
118
- return errors
100
+ yield Violation(node, RULE.format(expected=expected, actual=actual))
119
101
 
120
102
 
121
- def get_sim906(node: ast.Call) -> list[tuple[int, int, str]]:
122
- RULE = "SIM906 Use '{expected}' instead of '{actual}'"
123
- errors: list[tuple[int, int, str]] = []
103
+ @rule("SIM906", ast.Call)
104
+ def get_sim906(node: ast.Call) -> Iterator[Violation]:
105
+ RULE = "Use '{expected}' instead of '{actual}'"
124
106
  if not (
125
107
  isinstance(node.func, ast.Attribute)
126
108
  and isinstance(node.func.value, ast.Attribute)
@@ -142,7 +124,7 @@ def get_sim906(node: ast.Call) -> list[tuple[int, int, str]]:
142
124
  for arg in node.args
143
125
  )
144
126
  ):
145
- return errors
127
+ return
146
128
 
147
129
  def get_os_path_join_args(node: ast.Call) -> list[str]:
148
130
  names: list[str] = []
@@ -157,31 +139,21 @@ def get_sim906(node: ast.Call) -> list[tuple[int, int, str]]:
157
139
  and arg.func.attr == "join"
158
140
  ):
159
141
  names = names + get_os_path_join_args(arg)
160
- elif isinstance(arg, ast.Name):
161
- names.append(arg.id)
162
142
  elif isinstance(arg, ast.Constant) and isinstance(arg.value, str):
163
- names.append(f"'{arg.value}'")
143
+ names.append(repr(arg.value))
164
144
  else:
165
- logger.debug(
166
- f"Unexpected os.path.join arg: {arg} -- {to_source(arg)}"
167
- )
145
+ names.append(to_source(arg))
168
146
  return names
169
147
 
170
148
  names = get_os_path_join_args(node)
171
149
 
172
150
  actual = to_source(node)
173
151
  expected = f"os.path.join({', '.join(names)})"
174
- errors.append(
175
- (
176
- node.lineno,
177
- node.col_offset,
178
- RULE.format(actual=actual, expected=expected),
179
- )
180
- )
181
- return errors
152
+ yield Violation(node, RULE.format(actual=actual, expected=expected))
182
153
 
183
154
 
184
- def get_sim910(node: Call) -> list[tuple[int, int, str]]:
155
+ @rule("SIM910", ast.Call)
156
+ def get_sim910(node: ast.Call) -> Iterator[Violation]:
185
157
  """
186
158
  Get a list of all usages of "dict.get(key, None)"
187
159
 
@@ -202,38 +174,31 @@ def get_sim910(node: Call) -> list[tuple[int, int, str]]:
202
174
  ),
203
175
  ),
204
176
  """
205
- RULE = "SIM910 Use '{expected}' instead of '{actual}'"
206
- errors: list[tuple[int, int, str]] = []
177
+ RULE = "Use '{expected}' instead of '{actual}'"
207
178
  if not (
208
179
  isinstance(node.func, ast.Attribute)
209
180
  and node.func.attr == "get"
210
181
  and isinstance(node.func.ctx, ast.Load)
211
182
  ):
212
- return errors
183
+ return
213
184
 
214
185
  # check the argument value
215
186
  if not (
216
187
  len(node.args) == 2
217
- and isinstance(node.args[1], BOOL_CONST_TYPES)
188
+ and isinstance(node.args[1], ast.Constant)
218
189
  and node.args[1].value is None
219
190
  ):
220
- return errors
191
+ return
221
192
 
222
193
  actual = to_source(node)
223
194
  func = to_source(node.func)
224
195
  key = to_source(node.args[0])
225
196
  expected = f"{func}({key})"
226
- errors.append(
227
- (
228
- node.lineno,
229
- node.col_offset,
230
- RULE.format(actual=actual, expected=expected),
231
- )
232
- )
233
- return errors
197
+ yield Violation(node, RULE.format(actual=actual, expected=expected))
234
198
 
235
199
 
236
- def get_sim911(node: ast.AST) -> list[tuple[int, int, str]]:
200
+ @rule("SIM911", ast.Call)
201
+ def get_sim911(node: ast.AST) -> Iterator[Violation]:
237
202
  """
238
203
  Find nodes representing the expression "zip(_.keys(), _.values())".
239
204
 
@@ -267,10 +232,8 @@ def get_sim911(node: ast.AST) -> list[tuple[int, int, str]]:
267
232
  )
268
233
  """
269
234
  RULE = (
270
- "SIM911 Use '{name}.items()' instead of "
271
- "'zip({name}.keys(), {name}.values())'"
235
+ "Use '{name}.items()' instead of 'zip({name}.keys(), {name}.values())'"
272
236
  )
273
- errors: list[tuple[int, int, str]] = []
274
237
 
275
238
  if isinstance(node, ast.Call) and (
276
239
  isinstance(node.func, ast.Name)
@@ -289,11 +252,4 @@ def get_sim911(node: ast.AST) -> list[tuple[int, int, str]]:
289
252
  and second_arg.func.attr == "values"
290
253
  and first_arg.func.value.id == second_arg.func.value.id
291
254
  ):
292
- errors.append(
293
- (
294
- node.lineno,
295
- node.col_offset,
296
- RULE.format(name=first_arg.func.value.id),
297
- )
298
- )
299
- return errors
255
+ yield Violation(node, RULE.format(name=first_arg.func.value.id))