community-of-python-flake8-plugin 0.3.1__tar.gz → 0.3.2__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.
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/PKG-INFO +1 -1
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/pyproject.toml +1 -1
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/final_class.py +14 -2
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/function_verb.py +7 -6
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/mapping_proxy.py +5 -5
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/name_length.py +45 -36
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/temp_var.py +38 -37
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/constants.py +2 -0
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/plugin.py +10 -10
- community_of_python_flake8_plugin-0.3.2/src/community_of_python_flake8_plugin/utils.py +46 -0
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/violation_codes.py +3 -1
- community_of_python_flake8_plugin-0.3.1/src/community_of_python_flake8_plugin/utils.py +0 -35
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/README.md +0 -0
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/__init__.py +0 -0
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/__init__.py +0 -0
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/async_get_prefix.py +0 -0
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/dataclass_config.py +0 -0
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/disabled/__init__.py +0 -0
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/disabled/module_import_many_names.py +0 -0
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/for_loop_one_prefix.py +0 -0
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/module_import_stdlib.py +0 -0
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/checks/scalar_annotation.py +0 -0
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/py.typed +0 -0
- {community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/src/community_of_python_flake8_plugin/violations.py +0 -0
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
import ast
|
|
3
3
|
import typing
|
|
4
4
|
|
|
5
|
+
from community_of_python_flake8_plugin.utils import check_inherits_from_bases
|
|
5
6
|
from community_of_python_flake8_plugin.violation_codes import ViolationCodes
|
|
6
7
|
from community_of_python_flake8_plugin.violations import Violation
|
|
7
8
|
|
|
@@ -25,9 +26,20 @@ def is_protocol_class(class_node: ast.ClassDef) -> bool:
|
|
|
25
26
|
# Check for attributed Protocol reference: class MyClass(typing.Protocol):
|
|
26
27
|
if isinstance(one_base, ast.Attribute) and one_base.attr == "Protocol":
|
|
27
28
|
return True
|
|
29
|
+
# Check for subscripted Protocol reference: class MyClass(Protocol[SomeType]):
|
|
30
|
+
if isinstance(one_base, ast.Subscript):
|
|
31
|
+
if isinstance(one_base.value, ast.Name) and one_base.value.id == "Protocol":
|
|
32
|
+
return True
|
|
33
|
+
if isinstance(one_base.value, ast.Attribute) and one_base.value.attr == "Protocol":
|
|
34
|
+
return True
|
|
28
35
|
return False
|
|
29
36
|
|
|
30
37
|
|
|
38
|
+
def is_model_factory_class(class_node: ast.ClassDef) -> bool:
|
|
39
|
+
"""Check if the class inherits from ModelFactory or SQLAlchemyFactory."""
|
|
40
|
+
return check_inherits_from_bases(class_node, {"ModelFactory", "SQLAlchemyFactory"})
|
|
41
|
+
|
|
42
|
+
|
|
31
43
|
@typing.final
|
|
32
44
|
class FinalClassCheck(ast.NodeVisitor):
|
|
33
45
|
def __init__(self, syntax_tree: ast.AST) -> None: # noqa: ARG002
|
|
@@ -38,8 +50,8 @@ class FinalClassCheck(ast.NodeVisitor):
|
|
|
38
50
|
self.generic_visit(ast_node)
|
|
39
51
|
|
|
40
52
|
def _check_final_decorator(self, ast_node: ast.ClassDef) -> None:
|
|
41
|
-
# Skip Protocol classes and
|
|
42
|
-
if is_protocol_class(ast_node) or ast_node.name.startswith("Test"):
|
|
53
|
+
# Skip Protocol classes, test classes, and ModelFactory classes
|
|
54
|
+
if is_protocol_class(ast_node) or ast_node.name.startswith("Test") or is_model_factory_class(ast_node):
|
|
43
55
|
return
|
|
44
56
|
|
|
45
57
|
if not contains_final_decorator(ast_node):
|
|
@@ -11,19 +11,20 @@ from community_of_python_flake8_plugin.violations import Violation
|
|
|
11
11
|
def check_is_ignored_name(identifier: str) -> bool:
|
|
12
12
|
if identifier == "main":
|
|
13
13
|
return True
|
|
14
|
-
|
|
15
|
-
return True
|
|
16
|
-
return bool(identifier.startswith("_"))
|
|
14
|
+
return bool(identifier.startswith("__") and identifier.endswith("__"))
|
|
17
15
|
|
|
18
16
|
|
|
19
17
|
def check_is_verb_name(identifier: str) -> bool:
|
|
20
|
-
return any(
|
|
18
|
+
return any(
|
|
19
|
+
identifier == one_verb_name or identifier.startswith((f"{one_verb_name}_", f"_{one_verb_name}"))
|
|
20
|
+
for one_verb_name in VERB_PREFIXES
|
|
21
|
+
)
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def check_is_property(function_node: ast.AST) -> bool:
|
|
24
25
|
if not isinstance(function_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
25
26
|
return False
|
|
26
|
-
return any(check_is_property_decorator(
|
|
27
|
+
return any(check_is_property_decorator(one_decorator) for one_decorator in function_node.decorator_list) # noqa: COP011
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
def check_is_property_decorator(decorator: ast.expr) -> bool: # noqa: PLR0911
|
|
@@ -56,7 +57,7 @@ def check_is_property_decorator(decorator: ast.expr) -> bool: # noqa: PLR0911
|
|
|
56
57
|
def check_is_pytest_fixture(function_node: ast.AST) -> bool:
|
|
57
58
|
if not isinstance(function_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
58
59
|
return False
|
|
59
|
-
return any(check_is_fixture_decorator(
|
|
60
|
+
return any(check_is_fixture_decorator(one_decorator) for one_decorator in function_node.decorator_list) # noqa: COP011
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
def check_is_fixture_decorator(decorator: ast.expr) -> bool:
|
|
@@ -71,9 +71,9 @@ class MappingProxyCheck(ast.NodeVisitor):
|
|
|
71
71
|
self.violations: list[Violation] = []
|
|
72
72
|
|
|
73
73
|
def visit_Module(self, ast_node: ast.Module) -> None:
|
|
74
|
-
for
|
|
75
|
-
if isinstance(
|
|
76
|
-
self._check_mapping_assignment(
|
|
74
|
+
for one_statement in ast_node.body:
|
|
75
|
+
if isinstance(one_statement, (ast.Assign, ast.AnnAssign)):
|
|
76
|
+
self._check_mapping_assignment(one_statement)
|
|
77
77
|
self.generic_visit(ast_node)
|
|
78
78
|
|
|
79
79
|
def _check_mapping_assignment(self, ast_node: ast.Assign | ast.AnnAssign) -> None:
|
|
@@ -99,8 +99,8 @@ class MappingProxyCheck(ast.NodeVisitor):
|
|
|
99
99
|
# Only check module-level assignments (no parent function/class)
|
|
100
100
|
if assigned_value is not None and isinstance(assigned_value, ast.Dict) and assignment_targets:
|
|
101
101
|
# Check if this is a module-level assignment
|
|
102
|
-
for
|
|
103
|
-
if isinstance(
|
|
102
|
+
for one_target in assignment_targets: # noqa: COP011
|
|
103
|
+
if isinstance(one_target, ast.Name):
|
|
104
104
|
self.violations.append(
|
|
105
105
|
Violation(
|
|
106
106
|
line_number=ast_node.lineno,
|
|
@@ -3,7 +3,11 @@ import ast
|
|
|
3
3
|
import typing
|
|
4
4
|
|
|
5
5
|
from community_of_python_flake8_plugin.constants import FINAL_CLASS_EXCLUDED_BASES, MIN_NAME_LENGTH
|
|
6
|
-
from community_of_python_flake8_plugin.utils import
|
|
6
|
+
from community_of_python_flake8_plugin.utils import (
|
|
7
|
+
check_inherits_from_bases,
|
|
8
|
+
find_parent_class_definition,
|
|
9
|
+
find_parent_function_definition,
|
|
10
|
+
)
|
|
7
11
|
from community_of_python_flake8_plugin.violation_codes import ViolationCodes
|
|
8
12
|
from community_of_python_flake8_plugin.violations import Violation
|
|
9
13
|
|
|
@@ -33,7 +37,7 @@ def check_is_whitelisted_annotation(annotation: ast.expr | None) -> bool:
|
|
|
33
37
|
def check_is_pytest_fixture(ast_node: ast.AST) -> bool:
|
|
34
38
|
if not isinstance(ast_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
35
39
|
return False
|
|
36
|
-
return any(check_is_fixture_decorator(
|
|
40
|
+
return any(check_is_fixture_decorator(one_decorator) for one_decorator in ast_node.decorator_list) # noqa: COP011
|
|
37
41
|
|
|
38
42
|
|
|
39
43
|
def check_is_fixture_decorator(decorator: ast.expr) -> bool:
|
|
@@ -58,9 +62,11 @@ class COP004NameLengthCheck(ast.NodeVisitor):
|
|
|
58
62
|
self.generic_visit(ast_node)
|
|
59
63
|
|
|
60
64
|
def visit_Assign(self, ast_node: ast.Assign) -> None:
|
|
61
|
-
for
|
|
62
|
-
if isinstance(
|
|
63
|
-
self.validate_name_length(
|
|
65
|
+
for one_target in ast_node.targets:
|
|
66
|
+
if isinstance(one_target, ast.Name):
|
|
67
|
+
self.validate_name_length(
|
|
68
|
+
one_target.id, ast_node, find_parent_class_definition(self.syntax_tree, ast_node)
|
|
69
|
+
)
|
|
64
70
|
self.generic_visit(ast_node)
|
|
65
71
|
|
|
66
72
|
def visit_FunctionDef(self, ast_node: ast.FunctionDef) -> None:
|
|
@@ -79,18 +85,18 @@ class COP004NameLengthCheck(ast.NodeVisitor):
|
|
|
79
85
|
self.generic_visit(ast_node)
|
|
80
86
|
|
|
81
87
|
def visit_ListComp(self, ast_node: ast.ListComp) -> None:
|
|
82
|
-
for
|
|
83
|
-
self._validate_comprehension_target(
|
|
88
|
+
for one_comprehension in ast_node.generators:
|
|
89
|
+
self._validate_comprehension_target(one_comprehension.target)
|
|
84
90
|
self.generic_visit(ast_node)
|
|
85
91
|
|
|
86
92
|
def visit_SetComp(self, ast_node: ast.SetComp) -> None:
|
|
87
|
-
for
|
|
88
|
-
self._validate_comprehension_target(
|
|
93
|
+
for one_comprehension in ast_node.generators:
|
|
94
|
+
self._validate_comprehension_target(one_comprehension.target)
|
|
89
95
|
self.generic_visit(ast_node)
|
|
90
96
|
|
|
91
97
|
def visit_DictComp(self, ast_node: ast.DictComp) -> None:
|
|
92
|
-
for
|
|
93
|
-
self._validate_comprehension_target(
|
|
98
|
+
for one_comprehension in ast_node.generators:
|
|
99
|
+
self._validate_comprehension_target(one_comprehension.target)
|
|
94
100
|
self.generic_visit(ast_node)
|
|
95
101
|
|
|
96
102
|
def visit_Lambda(self, ast_node: ast.Lambda) -> None:
|
|
@@ -98,9 +104,9 @@ class COP004NameLengthCheck(ast.NodeVisitor):
|
|
|
98
104
|
self.generic_visit(ast_node)
|
|
99
105
|
|
|
100
106
|
def visit_With(self, ast_node: ast.With) -> None:
|
|
101
|
-
for
|
|
102
|
-
if
|
|
103
|
-
self._validate_with_target(
|
|
107
|
+
for one_item in ast_node.items:
|
|
108
|
+
if one_item.optional_vars is not None:
|
|
109
|
+
self._validate_with_target(one_item.optional_vars)
|
|
104
110
|
self.generic_visit(ast_node)
|
|
105
111
|
|
|
106
112
|
def visit_ExceptHandler(self, ast_node: ast.ExceptHandler) -> None:
|
|
@@ -109,18 +115,18 @@ class COP004NameLengthCheck(ast.NodeVisitor):
|
|
|
109
115
|
self.generic_visit(ast_node)
|
|
110
116
|
|
|
111
117
|
def visit_GeneratorExp(self, ast_node: ast.GeneratorExp) -> None:
|
|
112
|
-
for
|
|
113
|
-
self._validate_comprehension_target(
|
|
118
|
+
for one_comprehension in ast_node.generators:
|
|
119
|
+
self._validate_comprehension_target(one_comprehension.target)
|
|
114
120
|
self.generic_visit(ast_node)
|
|
115
121
|
|
|
116
122
|
def _validate_function_args(self, arguments_node: ast.arguments) -> None:
|
|
117
123
|
# Process all argument types
|
|
118
|
-
for
|
|
119
|
-
self._validate_argument_name_length(
|
|
120
|
-
for
|
|
121
|
-
self._validate_argument_name_length(
|
|
122
|
-
for
|
|
123
|
-
self._validate_argument_name_length(
|
|
124
|
+
for one_argument in arguments_node.posonlyargs:
|
|
125
|
+
self._validate_argument_name_length(one_argument)
|
|
126
|
+
for one_argument in arguments_node.args:
|
|
127
|
+
self._validate_argument_name_length(one_argument)
|
|
128
|
+
for one_argument in arguments_node.kwonlyargs:
|
|
129
|
+
self._validate_argument_name_length(one_argument)
|
|
124
130
|
|
|
125
131
|
if arguments_node.vararg is not None:
|
|
126
132
|
self._validate_argument_name_length(arguments_node.vararg)
|
|
@@ -157,8 +163,8 @@ class COP004NameLengthCheck(ast.NodeVisitor):
|
|
|
157
163
|
)
|
|
158
164
|
elif isinstance(comprehension_target, ast.Tuple):
|
|
159
165
|
# Handle tuple unpacking in comprehensions like [(a, b) for a, b in pairs]
|
|
160
|
-
for
|
|
161
|
-
self._validate_comprehension_target(
|
|
166
|
+
for one_elt in comprehension_target.elts:
|
|
167
|
+
self._validate_comprehension_target(one_elt)
|
|
162
168
|
|
|
163
169
|
def _validate_with_target(self, target_node: ast.expr) -> None:
|
|
164
170
|
if isinstance(target_node, ast.Name):
|
|
@@ -173,8 +179,8 @@ class COP004NameLengthCheck(ast.NodeVisitor):
|
|
|
173
179
|
)
|
|
174
180
|
elif isinstance(target_node, ast.Tuple):
|
|
175
181
|
# Handle tuple unpacking in with statements like with open(f1) as f1, open(f2) as f2:
|
|
176
|
-
for
|
|
177
|
-
self._validate_with_target(
|
|
182
|
+
for one_elt in target_node.elts:
|
|
183
|
+
self._validate_with_target(one_elt)
|
|
178
184
|
|
|
179
185
|
def _validate_except_target(self, ast_node: ast.ExceptHandler) -> None:
|
|
180
186
|
# For except targets, we'll treat them as variables
|
|
@@ -204,15 +210,18 @@ class COP004NameLengthCheck(ast.NodeVisitor):
|
|
|
204
210
|
return
|
|
205
211
|
|
|
206
212
|
if len(identifier) < MIN_NAME_LENGTH:
|
|
207
|
-
# Determine if this is an attribute (inside a class
|
|
213
|
+
# Determine if this is an attribute (inside a class but not in a method) or variable
|
|
214
|
+
parent_function: typing.Final = find_parent_function_definition(self.syntax_tree, ast_node)
|
|
215
|
+
|
|
216
|
+
# It's an attribute only if it's in a class but NOT in a function/method
|
|
217
|
+
is_attribute: typing.Final = parent_class is not None and parent_function is None
|
|
218
|
+
|
|
208
219
|
self.violations.append(
|
|
209
220
|
Violation(
|
|
210
221
|
line_number=ast_node.lineno,
|
|
211
222
|
column_number=ast_node.col_offset,
|
|
212
223
|
violation_code=(
|
|
213
|
-
ViolationCodes.ATTRIBUTE_NAME_LENGTH
|
|
214
|
-
if parent_class is not None
|
|
215
|
-
else ViolationCodes.VARIABLE_NAME_LENGTH
|
|
224
|
+
ViolationCodes.ATTRIBUTE_NAME_LENGTH if is_attribute else ViolationCodes.VARIABLE_NAME_LENGTH
|
|
216
225
|
),
|
|
217
226
|
)
|
|
218
227
|
)
|
|
@@ -240,12 +249,12 @@ class COP004NameLengthCheck(ast.NodeVisitor):
|
|
|
240
249
|
|
|
241
250
|
def validate_function_args(self, ast_node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
|
|
242
251
|
# Process all argument types
|
|
243
|
-
for
|
|
244
|
-
self.validate_argument_name_length(
|
|
245
|
-
for
|
|
246
|
-
self.validate_argument_name_length(
|
|
247
|
-
for
|
|
248
|
-
self.validate_argument_name_length(
|
|
252
|
+
for one_argument in ast_node.args.posonlyargs:
|
|
253
|
+
self.validate_argument_name_length(one_argument)
|
|
254
|
+
for one_argument in ast_node.args.args:
|
|
255
|
+
self.validate_argument_name_length(one_argument)
|
|
256
|
+
for one_argument in ast_node.args.kwonlyargs:
|
|
257
|
+
self.validate_argument_name_length(one_argument)
|
|
249
258
|
|
|
250
259
|
if ast_node.args.vararg is not None:
|
|
251
260
|
self.validate_argument_name_length(ast_node.args.vararg)
|
|
@@ -15,32 +15,30 @@ def extract_names(expression: ast.expr) -> typing.Iterable[str]:
|
|
|
15
15
|
if isinstance(expression, ast.Name):
|
|
16
16
|
yield expression.id
|
|
17
17
|
elif isinstance(expression, ast.Tuple):
|
|
18
|
-
for
|
|
19
|
-
yield from extract_names(
|
|
18
|
+
for one_elt in expression.elts:
|
|
19
|
+
yield from extract_names(one_elt)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def is_single_line_assignment(assign_node: ast.Assign) -> bool:
|
|
22
|
+
def is_single_line_assignment(assign_node: ast.Assign | ast.AnnAssign) -> bool:
|
|
23
23
|
"""Check if assignment value fits on a single line."""
|
|
24
24
|
# For simple values, they're always single line
|
|
25
25
|
if isinstance(assign_node.value, (ast.Constant, ast.Name, ast.Attribute)):
|
|
26
26
|
return True
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
# For complex expressions, check if they span multiple lines
|
|
29
29
|
# We'll consider it multi-line if the end line is different from start line
|
|
30
|
-
if hasattr(assign_node,
|
|
30
|
+
if hasattr(assign_node, "lineno") and assign_node.value is not None and hasattr(assign_node.value, "end_lineno"):
|
|
31
31
|
return assign_node.lineno == assign_node.value.end_lineno
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
return True
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def collect_variable_usage_and_stores_with_nodes(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
dict[str, ast.Assign]
|
|
40
|
-
]:
|
|
36
|
+
def collect_variable_usage_and_stores_with_nodes(
|
|
37
|
+
function_node: ast.AST,
|
|
38
|
+
) -> tuple[dict[str, list[ast.Name]], set[str], dict[str, ast.Assign | ast.AnnAssign]]:
|
|
41
39
|
variable_usage: typing.Final[defaultdict[str, list[ast.Name]]] = defaultdict(list)
|
|
42
40
|
assigned_variable_names: typing.Final[set[str]] = set()
|
|
43
|
-
variable_assignments: typing.Final[dict[str, ast.Assign]] = {}
|
|
41
|
+
variable_assignments: typing.Final[dict[str, ast.Assign | ast.AnnAssign]] = {}
|
|
44
42
|
|
|
45
43
|
@typing.final
|
|
46
44
|
class UsageCollector(ast.NodeVisitor):
|
|
@@ -54,43 +52,42 @@ def collect_variable_usage_and_stores_with_nodes(function_node: ast.AST) -> tupl
|
|
|
54
52
|
self.generic_visit(assign_node)
|
|
55
53
|
return
|
|
56
54
|
|
|
57
|
-
for
|
|
58
|
-
|
|
59
|
-
assigned_variable_names.update(
|
|
55
|
+
for one_target in assign_node.targets:
|
|
56
|
+
one_names = list(extract_names(one_target))
|
|
57
|
+
assigned_variable_names.update(one_names)
|
|
60
58
|
# Store the assignment node for each variable
|
|
61
|
-
for
|
|
62
|
-
variable_assignments[
|
|
59
|
+
for one_name in one_names:
|
|
60
|
+
variable_assignments[one_name] = assign_node
|
|
63
61
|
self.generic_visit(assign_node)
|
|
64
62
|
|
|
65
63
|
def visit_AugAssign(self, aug_assign_node: ast.AugAssign) -> None:
|
|
66
|
-
|
|
67
|
-
assigned_variable_names.update(names)
|
|
64
|
+
assigned_variable_names.update(list(extract_names(aug_assign_node.target)))
|
|
68
65
|
self.generic_visit(aug_assign_node)
|
|
69
66
|
|
|
70
67
|
def visit_AnnAssign(self, ann_assign_node: ast.AnnAssign) -> None:
|
|
71
|
-
|
|
72
|
-
assigned_variable_names.update(
|
|
68
|
+
extracted_names: typing.Final = list(extract_names(ann_assign_node.target)) # noqa: COP011
|
|
69
|
+
assigned_variable_names.update(extracted_names)
|
|
73
70
|
# Store the assignment node for each variable
|
|
74
|
-
for
|
|
75
|
-
variable_assignments[
|
|
71
|
+
for one_name in extracted_names:
|
|
72
|
+
variable_assignments[one_name] = ann_assign_node
|
|
76
73
|
self.generic_visit(ann_assign_node)
|
|
77
74
|
|
|
78
75
|
UsageCollector().visit(function_node)
|
|
79
76
|
return dict(variable_usage), assigned_variable_names, variable_assignments
|
|
80
77
|
|
|
81
78
|
|
|
82
|
-
def is_used_in_next_line(assign_node: ast.Assign, usage_nodes: list[ast.Name]) -> bool:
|
|
79
|
+
def is_used_in_next_line(assign_node: ast.Assign | ast.AnnAssign, usage_nodes: list[ast.Name]) -> bool:
|
|
83
80
|
"""Check if variable is used in the immediate next line."""
|
|
84
81
|
if not usage_nodes:
|
|
85
82
|
return False
|
|
86
|
-
|
|
83
|
+
|
|
87
84
|
# Find the load usage (actual use of the variable)
|
|
88
|
-
load_usages = [
|
|
85
|
+
load_usages: typing.Final = [one_node for one_node in usage_nodes if isinstance(one_node.ctx, ast.Load)]
|
|
89
86
|
if not load_usages:
|
|
90
87
|
return False
|
|
91
|
-
|
|
92
|
-
first_load = load_usages[0]
|
|
93
|
-
|
|
88
|
+
|
|
89
|
+
first_load: typing.Final = load_usages[0]
|
|
90
|
+
|
|
94
91
|
# Check if the usage is on the line immediately following the assignment
|
|
95
92
|
return first_load.lineno == assign_node.lineno + 1
|
|
96
93
|
|
|
@@ -110,16 +107,20 @@ class TempVarCheck(ast.NodeVisitor):
|
|
|
110
107
|
|
|
111
108
|
def _check_temporary_variables(self, ast_node: ast.FunctionDef | ast.AsyncFunctionDef) -> None:
|
|
112
109
|
usage_and_stores: typing.Final = collect_variable_usage_and_stores_with_nodes(ast_node)
|
|
113
|
-
variable_usages = usage_and_stores[0]
|
|
114
|
-
assigned_variable_names = usage_and_stores[1]
|
|
115
|
-
variable_assignments = usage_and_stores[2]
|
|
110
|
+
variable_usages: typing.Final = usage_and_stores[0]
|
|
111
|
+
assigned_variable_names: typing.Final = usage_and_stores[1]
|
|
112
|
+
variable_assignments: typing.Final = usage_and_stores[2]
|
|
116
113
|
|
|
117
114
|
for variable_name, usages in variable_usages.items():
|
|
118
115
|
if variable_name.startswith("_") or variable_name in {"self", "cls"}:
|
|
119
116
|
continue
|
|
120
117
|
|
|
121
|
-
store_count = len(
|
|
122
|
-
|
|
118
|
+
store_count = len(
|
|
119
|
+
[one_usage_element for one_usage_element in usages if isinstance(one_usage_element.ctx, ast.Store)]
|
|
120
|
+
)
|
|
121
|
+
load_count = len(
|
|
122
|
+
[one_usage_element for one_usage_element in usages if isinstance(one_usage_element.ctx, ast.Load)]
|
|
123
|
+
)
|
|
123
124
|
|
|
124
125
|
# Check if variable is assigned once and used once
|
|
125
126
|
if store_count == 1 and load_count == 1 and variable_name in assigned_variable_names:
|
|
@@ -127,16 +128,16 @@ class TempVarCheck(ast.NodeVisitor):
|
|
|
127
128
|
assign_node = variable_assignments.get(variable_name)
|
|
128
129
|
if not assign_node:
|
|
129
130
|
continue
|
|
130
|
-
|
|
131
|
+
|
|
131
132
|
# Check if it's a single-line assignment
|
|
132
133
|
if not is_single_line_assignment(assign_node):
|
|
133
134
|
continue
|
|
134
|
-
|
|
135
|
+
|
|
135
136
|
# Check if it's used in the next line
|
|
136
137
|
if is_used_in_next_line(assign_node, usages):
|
|
137
138
|
# Only flag the variable that is assigned and then immediately used
|
|
138
139
|
# Find the store usage (assignment)
|
|
139
|
-
store_usages = [
|
|
140
|
+
store_usages = [one_node for one_node in usages if isinstance(one_node.ctx, ast.Store)]
|
|
140
141
|
if store_usages:
|
|
141
142
|
first_store = store_usages[0]
|
|
142
143
|
self.violations.append(
|
|
@@ -31,23 +31,23 @@ class CommunityOfPythonFlake8Plugin:
|
|
|
31
31
|
self.ast_syntax_tree: typing.Final[ast.AST] = tree
|
|
32
32
|
|
|
33
33
|
def run(self) -> Iterable[tuple[int, int, str, type[object]]]: # noqa: COP007
|
|
34
|
-
for
|
|
35
|
-
for
|
|
34
|
+
for one_check_instance in self._collect_checks():
|
|
35
|
+
for one_violation in one_check_instance.violations:
|
|
36
36
|
yield (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
f"{
|
|
37
|
+
one_violation.line_number,
|
|
38
|
+
one_violation.column_number,
|
|
39
|
+
f"{one_violation.violation_code.code} {one_violation.violation_code.description}",
|
|
40
40
|
type(self),
|
|
41
41
|
)
|
|
42
42
|
|
|
43
43
|
def _collect_checks(self) -> list[PluginCheckProtocol]:
|
|
44
44
|
checks_collection: typing.Final = []
|
|
45
|
-
for _,
|
|
46
|
-
imported_module = importlib.import_module(f"{checks_module.__name__}.{
|
|
45
|
+
for _, one_module_name, _ in pkgutil.iter_modules(checks_module.__path__):
|
|
46
|
+
imported_module = importlib.import_module(f"{checks_module.__name__}.{one_module_name}")
|
|
47
47
|
|
|
48
|
-
for
|
|
49
|
-
attribute = getattr(imported_module,
|
|
50
|
-
if isinstance(attribute, type) and
|
|
48
|
+
for one_attribute_name in dir(imported_module):
|
|
49
|
+
attribute = getattr(imported_module, one_attribute_name)
|
|
50
|
+
if isinstance(attribute, type) and one_attribute_name.endswith("Check") and hasattr(attribute, "visit"):
|
|
51
51
|
check_instance = attribute(self.ast_syntax_tree)
|
|
52
52
|
check_instance.visit(self.ast_syntax_tree)
|
|
53
53
|
checks_collection.append(check_instance)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import ast
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def find_parent_class_definition(syntax_tree: ast.AST, target_node: ast.AST) -> ast.ClassDef | None:
|
|
6
|
+
for one_potential_parent in ast.walk(syntax_tree):
|
|
7
|
+
if isinstance(one_potential_parent, ast.ClassDef):
|
|
8
|
+
for one_child_node in ast.walk(one_potential_parent):
|
|
9
|
+
if one_child_node is target_node:
|
|
10
|
+
return one_potential_parent
|
|
11
|
+
return None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def find_parent_function_definition(
|
|
15
|
+
syntax_tree: ast.AST, target_node: ast.AST
|
|
16
|
+
) -> ast.FunctionDef | ast.AsyncFunctionDef | None:
|
|
17
|
+
for one_potential_parent in ast.walk(syntax_tree):
|
|
18
|
+
if isinstance(one_potential_parent, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
19
|
+
for one_child_node in ast.walk(one_potential_parent):
|
|
20
|
+
if one_child_node is target_node:
|
|
21
|
+
return one_potential_parent
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def check_inherits_from_bases(class_definition: ast.ClassDef, base_classes: set[str]) -> bool:
|
|
26
|
+
for one_base_class in class_definition.bases:
|
|
27
|
+
if isinstance(one_base_class, ast.Name) and one_base_class.id in base_classes:
|
|
28
|
+
return True
|
|
29
|
+
if isinstance(one_base_class, ast.Attribute) and one_base_class.attr in base_classes:
|
|
30
|
+
return True
|
|
31
|
+
# Handle generic types like ModelFactory[SomeType]
|
|
32
|
+
if isinstance(one_base_class, ast.Subscript):
|
|
33
|
+
if isinstance(one_base_class.value, ast.Name) and one_base_class.value.id in base_classes:
|
|
34
|
+
return True
|
|
35
|
+
if isinstance(one_base_class.value, ast.Attribute) and one_base_class.value.attr in base_classes:
|
|
36
|
+
return True
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def find_parent_node(syntax_tree: ast.AST, target_node: ast.AST, node_types: tuple[type, ...]) -> ast.AST | None:
|
|
41
|
+
for one_potential_parent in ast.walk(syntax_tree):
|
|
42
|
+
if isinstance(one_potential_parent, node_types):
|
|
43
|
+
for one_child_node in ast.walk(one_potential_parent):
|
|
44
|
+
if one_child_node is target_node:
|
|
45
|
+
return one_potential_parent
|
|
46
|
+
return None
|
|
@@ -35,7 +35,9 @@ class ViolationCodes:
|
|
|
35
35
|
ASYNC_GET_PREFIX = ViolationCodeItem(code="COP010", description="Avoid get_ prefix in async function names")
|
|
36
36
|
|
|
37
37
|
# Variable usage violations
|
|
38
|
-
TEMP_VAR = ViolationCodeItem(
|
|
38
|
+
TEMP_VAR = ViolationCodeItem(
|
|
39
|
+
code="COP011", description="Inline those temporary variables that are used only once and close to assignment"
|
|
40
|
+
)
|
|
39
41
|
|
|
40
42
|
# Class related violations
|
|
41
43
|
FINAL_CLASS = ViolationCodeItem(code="COP012", description="Classes must be marked final with @typing.final")
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
import ast
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def find_parent_class_definition(syntax_tree: ast.AST, target_node: ast.AST) -> ast.ClassDef | None:
|
|
6
|
-
for potential_parent in ast.walk(syntax_tree):
|
|
7
|
-
if isinstance(potential_parent, ast.ClassDef):
|
|
8
|
-
for child_node in ast.walk(potential_parent):
|
|
9
|
-
if child_node is target_node:
|
|
10
|
-
return potential_parent
|
|
11
|
-
return None
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def check_inherits_from_bases(class_definition: ast.ClassDef, base_classes: set[str]) -> bool:
|
|
15
|
-
for base_class in class_definition.bases:
|
|
16
|
-
if isinstance(base_class, ast.Name) and base_class.id in base_classes:
|
|
17
|
-
return True
|
|
18
|
-
if isinstance(base_class, ast.Attribute) and base_class.attr in base_classes:
|
|
19
|
-
return True
|
|
20
|
-
# Handle generic types like ModelFactory[SomeType]
|
|
21
|
-
if isinstance(base_class, ast.Subscript):
|
|
22
|
-
if isinstance(base_class.value, ast.Name) and base_class.value.id in base_classes:
|
|
23
|
-
return True
|
|
24
|
-
if isinstance(base_class.value, ast.Attribute) and base_class.value.attr in base_classes:
|
|
25
|
-
return True
|
|
26
|
-
return False
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def find_parent_node(syntax_tree: ast.AST, target_node: ast.AST, node_types: tuple[type, ...]) -> ast.AST | None:
|
|
30
|
-
for potential_parent in ast.walk(syntax_tree):
|
|
31
|
-
if isinstance(potential_parent, node_types):
|
|
32
|
-
for child_node in ast.walk(potential_parent):
|
|
33
|
-
if child_node is target_node:
|
|
34
|
-
return potential_parent
|
|
35
|
-
return None
|
{community_of_python_flake8_plugin-0.3.1 → community_of_python_flake8_plugin-0.3.2}/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|