community-of-python-flake8-plugin 0.2.0__tar.gz → 0.3.0__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.2.0 → community_of_python_flake8_plugin-0.3.0}/PKG-INFO +1 -1
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/pyproject.toml +1 -1
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/checks/dataclass_config.py +17 -13
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/checks/disabled/module_import_many_names.py +10 -9
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/checks/final_class.py +5 -5
- community_of_python_flake8_plugin-0.3.0/src/community_of_python_flake8_plugin/checks/for_loop_one_prefix.py +121 -0
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/checks/function_verb.py +1 -1
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/checks/mapping_proxy.py +52 -0
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/checks/name_length.py +113 -0
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/checks/temp_var.py +2 -2
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/violation_codes.py +5 -0
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/README.md +0 -0
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/__init__.py +0 -0
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/checks/__init__.py +0 -0
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/checks/async_get_prefix.py +0 -0
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/checks/disabled/__init__.py +0 -0
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/checks/module_import_stdlib.py +0 -0
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/checks/scalar_annotation.py +0 -0
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/constants.py +0 -0
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/plugin.py +0 -0
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/py.typed +0 -0
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/utils.py +0 -0
- {community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/src/community_of_python_flake8_plugin/violations.py +0 -0
|
@@ -24,7 +24,11 @@ def has_required_dataclass_params(decorator: ast.expr) -> bool:
|
|
|
24
24
|
if not isinstance(decorator, ast.Call):
|
|
25
25
|
return False
|
|
26
26
|
|
|
27
|
-
keywords: typing.Final = {
|
|
27
|
+
keywords: typing.Final = {
|
|
28
|
+
one_keyword_item.arg: one_keyword_item.value
|
|
29
|
+
for one_keyword_item in decorator.keywords
|
|
30
|
+
if isinstance(one_keyword_item.value, ast.Constant)
|
|
31
|
+
}
|
|
28
32
|
kw_only_param: typing.Final = keywords.get("kw_only")
|
|
29
33
|
slots_param: typing.Final = keywords.get("slots")
|
|
30
34
|
frozen_param: typing.Final = keywords.get("frozen")
|
|
@@ -43,20 +47,20 @@ def has_required_dataclass_params(decorator: ast.expr) -> bool:
|
|
|
43
47
|
|
|
44
48
|
def is_pydantic_model(class_node: ast.ClassDef) -> bool:
|
|
45
49
|
"""Check if class inherits from Pydantic BaseModel or RootModel."""
|
|
46
|
-
for
|
|
47
|
-
if isinstance(
|
|
50
|
+
for one_base_class in class_node.bases:
|
|
51
|
+
if isinstance(one_base_class, ast.Name) and one_base_class.id in {"BaseModel", "RootModel"}:
|
|
48
52
|
return True
|
|
49
|
-
if isinstance(
|
|
53
|
+
if isinstance(one_base_class, ast.Attribute) and one_base_class.attr in {"BaseModel", "RootModel"}:
|
|
50
54
|
return True
|
|
51
55
|
return False
|
|
52
56
|
|
|
53
57
|
|
|
54
58
|
def is_model_factory(class_node: ast.ClassDef) -> bool:
|
|
55
59
|
"""Check if class inherits from ModelFactory."""
|
|
56
|
-
for
|
|
57
|
-
if isinstance(
|
|
60
|
+
for one_base_class in class_node.bases:
|
|
61
|
+
if isinstance(one_base_class, ast.Name) and one_base_class.id in {"ModelFactory", "SQLAlchemyFactory"}:
|
|
58
62
|
return True
|
|
59
|
-
if isinstance(
|
|
63
|
+
if isinstance(one_base_class, ast.Attribute) and one_base_class.attr in {"ModelFactory", "SQLAlchemyFactory"}:
|
|
60
64
|
return True
|
|
61
65
|
return False
|
|
62
66
|
|
|
@@ -78,9 +82,9 @@ class DataclassConfigCheck(ast.NodeVisitor):
|
|
|
78
82
|
return
|
|
79
83
|
|
|
80
84
|
# Check for dataclass decorator
|
|
81
|
-
for
|
|
82
|
-
if is_dataclass_decorator(
|
|
83
|
-
if not has_required_dataclass_params(
|
|
85
|
+
for one_decorator in ast_node.decorator_list:
|
|
86
|
+
if is_dataclass_decorator(one_decorator):
|
|
87
|
+
if not has_required_dataclass_params(one_decorator):
|
|
84
88
|
self.violations.append(
|
|
85
89
|
Violation(
|
|
86
90
|
line_number=ast_node.lineno,
|
|
@@ -94,9 +98,9 @@ class DataclassConfigCheck(ast.NodeVisitor):
|
|
|
94
98
|
|
|
95
99
|
def _inherits_from_exception(self, ast_node: ast.ClassDef) -> bool:
|
|
96
100
|
"""Check if class inherits from Exception or its subclasses."""
|
|
97
|
-
for
|
|
98
|
-
if isinstance(
|
|
101
|
+
for one_base in ast_node.bases:
|
|
102
|
+
if isinstance(one_base, ast.Name) and ("Error" in one_base.id or "Exception" in one_base.id):
|
|
99
103
|
return True
|
|
100
|
-
if isinstance(
|
|
104
|
+
if isinstance(one_base, ast.Attribute) and ("Error" in one_base.attr or "Exception" in one_base.attr):
|
|
101
105
|
return True
|
|
102
106
|
return False
|
|
@@ -17,15 +17,16 @@ from community_of_python_flake8_plugin.violations import Violation
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def check_module_has_all_declaration(module_node: ast.Module) -> bool:
|
|
20
|
-
for
|
|
21
|
-
if isinstance(
|
|
22
|
-
isinstance(
|
|
20
|
+
for one_statement in module_node.body:
|
|
21
|
+
if isinstance(one_statement, ast.Assign) and any(
|
|
22
|
+
isinstance(one_target_element, ast.Name) and one_target_element.id == "__all__"
|
|
23
|
+
for one_target_element in one_statement.targets
|
|
23
24
|
):
|
|
24
25
|
return True
|
|
25
26
|
if (
|
|
26
|
-
isinstance(
|
|
27
|
-
and isinstance(
|
|
28
|
-
and
|
|
27
|
+
isinstance(one_statement, ast.AnnAssign)
|
|
28
|
+
and isinstance(one_statement.target, ast.Name)
|
|
29
|
+
and one_statement.target.id == "__all__"
|
|
29
30
|
):
|
|
30
31
|
return True
|
|
31
32
|
return False
|
|
@@ -76,9 +77,9 @@ class ModuleImportManyNamesCheck(ast.NodeVisitor):
|
|
|
76
77
|
return
|
|
77
78
|
|
|
78
79
|
if not any(
|
|
79
|
-
check_module_path_exists(f"{module_name}.{
|
|
80
|
-
for
|
|
81
|
-
if isinstance(
|
|
80
|
+
check_module_path_exists(f"{module_name}.{one_alias_element.name}")
|
|
81
|
+
for one_alias_element in ast_node.names
|
|
82
|
+
if isinstance(one_alias_element, ast.alias) and module_name is not None
|
|
82
83
|
):
|
|
83
84
|
self.violations.append(
|
|
84
85
|
Violation(
|
|
@@ -7,8 +7,8 @@ from community_of_python_flake8_plugin.violations import Violation
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def contains_final_decorator(class_node: ast.ClassDef) -> bool:
|
|
10
|
-
for
|
|
11
|
-
target_name =
|
|
10
|
+
for one_decorator in class_node.decorator_list:
|
|
11
|
+
target_name = one_decorator.func if isinstance(one_decorator, ast.Call) else one_decorator
|
|
12
12
|
if isinstance(target_name, ast.Name) and target_name.id == "final":
|
|
13
13
|
return True
|
|
14
14
|
if isinstance(target_name, ast.Attribute) and target_name.attr == "final":
|
|
@@ -18,12 +18,12 @@ def contains_final_decorator(class_node: ast.ClassDef) -> bool:
|
|
|
18
18
|
|
|
19
19
|
def is_protocol_class(class_node: ast.ClassDef) -> bool:
|
|
20
20
|
"""Check if the class directly inherits from typing.Protocol."""
|
|
21
|
-
for
|
|
21
|
+
for one_base in class_node.bases:
|
|
22
22
|
# Check for direct Protocol reference: class MyClass(Protocol):
|
|
23
|
-
if isinstance(
|
|
23
|
+
if isinstance(one_base, ast.Name) and one_base.id == "Protocol":
|
|
24
24
|
return True
|
|
25
25
|
# Check for attributed Protocol reference: class MyClass(typing.Protocol):
|
|
26
|
-
if isinstance(
|
|
26
|
+
if isinstance(one_base, ast.Attribute) and one_base.attr == "Protocol":
|
|
27
27
|
return True
|
|
28
28
|
return False
|
|
29
29
|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import ast
|
|
3
|
+
import typing
|
|
4
|
+
|
|
5
|
+
from community_of_python_flake8_plugin.violation_codes import ViolationCodes
|
|
6
|
+
from community_of_python_flake8_plugin.violations import Violation
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _is_ignored_target(target_node: ast.expr) -> bool:
|
|
10
|
+
"""Check if target should be ignored (e.g., underscore variables)."""
|
|
11
|
+
# Ignore underscore variables
|
|
12
|
+
return bool(isinstance(target_node, ast.Name) and target_node.id == "_")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@typing.final
|
|
16
|
+
class COP015ForLoopOnePrefixCheck(ast.NodeVisitor):
|
|
17
|
+
def __init__(self, syntax_tree: ast.AST) -> None:
|
|
18
|
+
self.violations: list[Violation] = []
|
|
19
|
+
self.syntax_tree: typing.Final[ast.AST] = syntax_tree
|
|
20
|
+
|
|
21
|
+
def visit_ListComp(self, ast_node: ast.ListComp) -> None:
|
|
22
|
+
# Validate targets in generators (the 'v' in 'for v in lst')
|
|
23
|
+
for one_comprehension in ast_node.generators:
|
|
24
|
+
if not self._is_partial_unpacking(ast_node.elt, one_comprehension.target):
|
|
25
|
+
self._validate_comprehension_target(one_comprehension.target)
|
|
26
|
+
self.generic_visit(ast_node)
|
|
27
|
+
|
|
28
|
+
def visit_SetComp(self, ast_node: ast.SetComp) -> None:
|
|
29
|
+
# Validate targets in generators (the 'v' in 'for v in lst')
|
|
30
|
+
for one_comprehension in ast_node.generators:
|
|
31
|
+
if not self._is_partial_unpacking(ast_node.elt, one_comprehension.target):
|
|
32
|
+
self._validate_comprehension_target(one_comprehension.target)
|
|
33
|
+
self.generic_visit(ast_node)
|
|
34
|
+
|
|
35
|
+
def visit_DictComp(self, ast_node: ast.DictComp) -> None:
|
|
36
|
+
# Validate targets in generators (the 'v' in 'for v in lst')
|
|
37
|
+
# For dict comprehensions, we consider both key and value
|
|
38
|
+
# key and value are both used
|
|
39
|
+
for one_comprehension in ast_node.generators:
|
|
40
|
+
if not self._is_partial_unpacking_expr_count(2, one_comprehension.target):
|
|
41
|
+
self._validate_comprehension_target(one_comprehension.target)
|
|
42
|
+
self.generic_visit(ast_node)
|
|
43
|
+
|
|
44
|
+
def visit_For(self, ast_node: ast.For) -> None:
|
|
45
|
+
# Validate target variables in regular for-loops
|
|
46
|
+
# Apply same unpacking logic as comprehensions
|
|
47
|
+
# For-loops don't have an expression that references vars
|
|
48
|
+
if not self._is_partial_unpacking_expr_count(1, ast_node.target):
|
|
49
|
+
self._validate_comprehension_target(ast_node.target)
|
|
50
|
+
self.generic_visit(ast_node)
|
|
51
|
+
|
|
52
|
+
def visit_GeneratorExp(self, ast_node: ast.GeneratorExp) -> None:
|
|
53
|
+
# Validate targets in generators (the 'v' in 'for v in lst')
|
|
54
|
+
for one_comprehension in ast_node.generators:
|
|
55
|
+
if not self._is_partial_unpacking(ast_node.elt, one_comprehension.target):
|
|
56
|
+
self._validate_comprehension_target(one_comprehension.target)
|
|
57
|
+
self.generic_visit(ast_node)
|
|
58
|
+
|
|
59
|
+
def _is_partial_unpacking(self, expression: ast.expr, target_node: ast.expr) -> bool:
|
|
60
|
+
"""Check if this is partial unpacking (referencing fewer vars than unpacked)."""
|
|
61
|
+
return self._is_partial_unpacking_expr_count(self._count_referenced_vars(expression), target_node)
|
|
62
|
+
|
|
63
|
+
def _is_partial_unpacking_expr_count(self, expression_count: int, target_node: ast.expr) -> bool:
|
|
64
|
+
"""Check if this is partial unpacking given expression variable count."""
|
|
65
|
+
target_count: typing.Final = self._count_unpacked_vars(target_node)
|
|
66
|
+
return target_count > expression_count and target_count > 1
|
|
67
|
+
|
|
68
|
+
def _count_referenced_vars(self, expression: ast.expr) -> int:
|
|
69
|
+
"""Count how many variables are referenced in the expression."""
|
|
70
|
+
if isinstance(expression, ast.Name):
|
|
71
|
+
return 1
|
|
72
|
+
if isinstance(expression, ast.Tuple):
|
|
73
|
+
return len([one_element for one_element in expression.elts if isinstance(one_element, ast.Name)])
|
|
74
|
+
# For simplicity, assume other expressions reference 1 variable
|
|
75
|
+
# This covers cases like function calls, attributes, etc.
|
|
76
|
+
return 1
|
|
77
|
+
|
|
78
|
+
def _count_unpacked_vars(self, target_node: ast.expr) -> int:
|
|
79
|
+
"""Count how many variables are unpacked in the target."""
|
|
80
|
+
if isinstance(target_node, ast.Name):
|
|
81
|
+
return 1
|
|
82
|
+
if isinstance(target_node, ast.Tuple):
|
|
83
|
+
return len([one_element for one_element in target_node.elts if isinstance(one_element, ast.Name)])
|
|
84
|
+
return 0
|
|
85
|
+
|
|
86
|
+
def _validate_comprehension_target(self, target_node: ast.expr) -> None:
|
|
87
|
+
"""Validate that comprehension target follows the one_ prefix rule."""
|
|
88
|
+
# Skip ignored targets (underscore, unpacking)
|
|
89
|
+
if _is_ignored_target(target_node):
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
# For simple names, validate the prefix
|
|
93
|
+
if isinstance(target_node, ast.Name):
|
|
94
|
+
if not self._has_valid_one_prefix(target_node.id):
|
|
95
|
+
self.violations.append(
|
|
96
|
+
Violation(
|
|
97
|
+
line_number=target_node.lineno,
|
|
98
|
+
column_number=target_node.col_offset,
|
|
99
|
+
violation_code=ViolationCodes.FOR_LOOP_VARIABLE_PREFIX,
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
# For tuples (unpacking), validate each element
|
|
103
|
+
elif isinstance(target_node, ast.Tuple):
|
|
104
|
+
for one_element in target_node.elts:
|
|
105
|
+
if isinstance(one_element, ast.Name) and not self._has_valid_one_prefix(one_element.id):
|
|
106
|
+
self.violations.append(
|
|
107
|
+
Violation(
|
|
108
|
+
line_number=one_element.lineno,
|
|
109
|
+
column_number=one_element.col_offset,
|
|
110
|
+
violation_code=ViolationCodes.FOR_LOOP_VARIABLE_PREFIX,
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def _has_valid_one_prefix(self, identifier: str) -> bool:
|
|
115
|
+
"""Check if identifier has valid one_ prefix."""
|
|
116
|
+
# Allow underscore variables
|
|
117
|
+
if identifier == "_":
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
# Check for one_ prefix
|
|
121
|
+
return identifier.startswith("one_")
|
|
@@ -17,7 +17,7 @@ def check_is_ignored_name(identifier: str) -> bool:
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def check_is_verb_name(identifier: str) -> bool:
|
|
20
|
-
return any(identifier ==
|
|
20
|
+
return any(identifier == verb_name or identifier.startswith(f"{verb_name}_") for verb_name in VERB_PREFIXES)
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
def check_is_property(function_node: ast.AST) -> bool:
|
|
@@ -17,6 +17,54 @@ def is_mapping_proxy_type(annotation: ast.expr | None) -> bool:
|
|
|
17
17
|
return False
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
def _get_base_name(annotation_value: ast.expr) -> str:
|
|
21
|
+
"""Extract the base name from an annotation value."""
|
|
22
|
+
if isinstance(annotation_value, ast.Name):
|
|
23
|
+
return annotation_value.id
|
|
24
|
+
if isinstance(annotation_value, ast.Attribute):
|
|
25
|
+
return annotation_value.attr
|
|
26
|
+
return ""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def is_dict_type_annotation(annotation: ast.expr | None) -> bool:
|
|
30
|
+
"""Check if annotation represents a dict type that should trigger COP013.
|
|
31
|
+
|
|
32
|
+
Returns True for:
|
|
33
|
+
- dict
|
|
34
|
+
- Final[dict]
|
|
35
|
+
- dict[key, value]
|
|
36
|
+
- Final[dict[key, value]]
|
|
37
|
+
|
|
38
|
+
Returns False for TypedDict and other non-dict annotations.
|
|
39
|
+
"""
|
|
40
|
+
is_dict_annotation = False
|
|
41
|
+
|
|
42
|
+
if annotation is not None:
|
|
43
|
+
# Handle simple name annotations like 'dict'
|
|
44
|
+
if isinstance(annotation, ast.Name):
|
|
45
|
+
is_dict_annotation = annotation.id == "dict"
|
|
46
|
+
# Handle attribute annotations like 'typing.Final'
|
|
47
|
+
elif isinstance(annotation, ast.Attribute):
|
|
48
|
+
is_dict_annotation = annotation.attr == "dict"
|
|
49
|
+
# Handle subscript annotations like 'dict[str, int]' or 'Final[dict]'
|
|
50
|
+
elif isinstance(annotation, ast.Subscript):
|
|
51
|
+
base_name: typing.Final = _get_base_name(annotation.value)
|
|
52
|
+
if base_name:
|
|
53
|
+
# Check for Final[...] annotations
|
|
54
|
+
if base_name == "Final":
|
|
55
|
+
# Extract the inner type from Final[inner_type]
|
|
56
|
+
inner_type = annotation.slice
|
|
57
|
+
# Handle Python 3.8 vs 3.9+ differences
|
|
58
|
+
if hasattr(inner_type, "value"): # Python 3.8
|
|
59
|
+
inner_type = inner_type.value
|
|
60
|
+
is_dict_annotation = is_dict_type_annotation(inner_type)
|
|
61
|
+
# Check for dict[...] annotations
|
|
62
|
+
elif base_name == "dict":
|
|
63
|
+
is_dict_annotation = True
|
|
64
|
+
|
|
65
|
+
return is_dict_annotation
|
|
66
|
+
|
|
67
|
+
|
|
20
68
|
@typing.final
|
|
21
69
|
class MappingProxyCheck(ast.NodeVisitor):
|
|
22
70
|
def __init__(self, syntax_tree: ast.AST) -> None: # noqa: ARG002
|
|
@@ -33,6 +81,10 @@ class MappingProxyCheck(ast.NodeVisitor):
|
|
|
33
81
|
if isinstance(ast_node, ast.AnnAssign) and is_mapping_proxy_type(ast_node.annotation):
|
|
34
82
|
return
|
|
35
83
|
|
|
84
|
+
# Skip annotated assignments that are not dict-like types
|
|
85
|
+
if isinstance(ast_node, ast.AnnAssign) and not is_dict_type_annotation(ast_node.annotation):
|
|
86
|
+
return
|
|
87
|
+
|
|
36
88
|
# Check for dictionary literals assigned to module-level variables
|
|
37
89
|
assigned_value: ast.expr | None
|
|
38
90
|
assignment_targets: list[ast.expr]
|
|
@@ -78,6 +78,119 @@ class COP004NameLengthCheck(ast.NodeVisitor):
|
|
|
78
78
|
self.validate_class_name_length(ast_node)
|
|
79
79
|
self.generic_visit(ast_node)
|
|
80
80
|
|
|
81
|
+
def visit_ListComp(self, ast_node: ast.ListComp) -> None:
|
|
82
|
+
for comprehension in ast_node.generators:
|
|
83
|
+
self._validate_comprehension_target(comprehension.target)
|
|
84
|
+
self.generic_visit(ast_node)
|
|
85
|
+
|
|
86
|
+
def visit_SetComp(self, ast_node: ast.SetComp) -> None:
|
|
87
|
+
for comprehension in ast_node.generators:
|
|
88
|
+
self._validate_comprehension_target(comprehension.target)
|
|
89
|
+
self.generic_visit(ast_node)
|
|
90
|
+
|
|
91
|
+
def visit_DictComp(self, ast_node: ast.DictComp) -> None:
|
|
92
|
+
for comprehension in ast_node.generators:
|
|
93
|
+
self._validate_comprehension_target(comprehension.target)
|
|
94
|
+
self.generic_visit(ast_node)
|
|
95
|
+
|
|
96
|
+
def visit_Lambda(self, ast_node: ast.Lambda) -> None:
|
|
97
|
+
self._validate_function_args(ast_node.args)
|
|
98
|
+
self.generic_visit(ast_node)
|
|
99
|
+
|
|
100
|
+
def visit_With(self, ast_node: ast.With) -> None:
|
|
101
|
+
for item in ast_node.items:
|
|
102
|
+
if item.optional_vars is not None:
|
|
103
|
+
self._validate_with_target(item.optional_vars)
|
|
104
|
+
self.generic_visit(ast_node)
|
|
105
|
+
|
|
106
|
+
def visit_ExceptHandler(self, ast_node: ast.ExceptHandler) -> None:
|
|
107
|
+
if ast_node.name is not None:
|
|
108
|
+
self._validate_except_target(ast_node)
|
|
109
|
+
self.generic_visit(ast_node)
|
|
110
|
+
|
|
111
|
+
def visit_GeneratorExp(self, ast_node: ast.GeneratorExp) -> None:
|
|
112
|
+
for comprehension in ast_node.generators:
|
|
113
|
+
self._validate_comprehension_target(comprehension.target)
|
|
114
|
+
self.generic_visit(ast_node)
|
|
115
|
+
|
|
116
|
+
def _validate_function_args(self, arguments_node: ast.arguments) -> None:
|
|
117
|
+
# Process all argument types
|
|
118
|
+
for argument in arguments_node.posonlyargs:
|
|
119
|
+
self._validate_argument_name_length(argument)
|
|
120
|
+
for argument in arguments_node.args:
|
|
121
|
+
self._validate_argument_name_length(argument)
|
|
122
|
+
for argument in arguments_node.kwonlyargs:
|
|
123
|
+
self._validate_argument_name_length(argument)
|
|
124
|
+
|
|
125
|
+
if arguments_node.vararg is not None:
|
|
126
|
+
self._validate_argument_name_length(arguments_node.vararg)
|
|
127
|
+
if arguments_node.kwarg is not None:
|
|
128
|
+
self._validate_argument_name_length(arguments_node.kwarg)
|
|
129
|
+
|
|
130
|
+
def _validate_argument_name_length(self, argument: ast.arg) -> None:
|
|
131
|
+
if argument.arg in {"self", "cls"}:
|
|
132
|
+
return
|
|
133
|
+
if check_is_ignored_name(argument.arg):
|
|
134
|
+
return
|
|
135
|
+
if check_is_whitelisted_annotation(argument.annotation):
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
if len(argument.arg) < MIN_NAME_LENGTH:
|
|
139
|
+
self.violations.append(
|
|
140
|
+
Violation(
|
|
141
|
+
line_number=argument.lineno,
|
|
142
|
+
column_number=argument.col_offset,
|
|
143
|
+
violation_code=ViolationCodes.ARGUMENT_NAME_LENGTH,
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def _validate_comprehension_target(self, comprehension_target: ast.expr) -> None:
|
|
148
|
+
if isinstance(comprehension_target, ast.Name):
|
|
149
|
+
# For comprehension targets, we'll treat them as variables
|
|
150
|
+
if not check_is_ignored_name(comprehension_target.id) and len(comprehension_target.id) < MIN_NAME_LENGTH:
|
|
151
|
+
self.violations.append(
|
|
152
|
+
Violation(
|
|
153
|
+
line_number=comprehension_target.lineno,
|
|
154
|
+
column_number=comprehension_target.col_offset,
|
|
155
|
+
violation_code=ViolationCodes.VARIABLE_NAME_LENGTH,
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
elif isinstance(comprehension_target, ast.Tuple):
|
|
159
|
+
# Handle tuple unpacking in comprehensions like [(a, b) for a, b in pairs]
|
|
160
|
+
for elt in comprehension_target.elts:
|
|
161
|
+
self._validate_comprehension_target(elt)
|
|
162
|
+
|
|
163
|
+
def _validate_with_target(self, target_node: ast.expr) -> None:
|
|
164
|
+
if isinstance(target_node, ast.Name):
|
|
165
|
+
# For with targets, we'll treat them as variables
|
|
166
|
+
if not check_is_ignored_name(target_node.id) and len(target_node.id) < MIN_NAME_LENGTH:
|
|
167
|
+
self.violations.append(
|
|
168
|
+
Violation(
|
|
169
|
+
line_number=target_node.lineno,
|
|
170
|
+
column_number=target_node.col_offset,
|
|
171
|
+
violation_code=ViolationCodes.VARIABLE_NAME_LENGTH,
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
elif isinstance(target_node, ast.Tuple):
|
|
175
|
+
# Handle tuple unpacking in with statements like with open(f1) as f1, open(f2) as f2:
|
|
176
|
+
for elt in target_node.elts:
|
|
177
|
+
self._validate_with_target(elt)
|
|
178
|
+
|
|
179
|
+
def _validate_except_target(self, ast_node: ast.ExceptHandler) -> None:
|
|
180
|
+
# For except targets, we'll treat them as variables
|
|
181
|
+
if (
|
|
182
|
+
ast_node.name is not None
|
|
183
|
+
and not check_is_ignored_name(ast_node.name)
|
|
184
|
+
and len(ast_node.name) < MIN_NAME_LENGTH
|
|
185
|
+
):
|
|
186
|
+
self.violations.append(
|
|
187
|
+
Violation(
|
|
188
|
+
line_number=ast_node.lineno,
|
|
189
|
+
column_number=0, # ast.ExceptHandler doesn't have col_offset
|
|
190
|
+
violation_code=ViolationCodes.VARIABLE_NAME_LENGTH,
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
|
|
81
194
|
def validate_name_length(self, identifier: str, ast_node: ast.stmt, parent_class: ast.ClassDef | None) -> None:
|
|
82
195
|
if check_is_ignored_name(identifier):
|
|
83
196
|
return
|
|
@@ -73,8 +73,8 @@ class TempVarCheck(ast.NodeVisitor):
|
|
|
73
73
|
continue
|
|
74
74
|
|
|
75
75
|
if (
|
|
76
|
-
len([
|
|
77
|
-
and len([
|
|
76
|
+
len([usage_element for usage_element in usages if isinstance(usage_element.ctx, ast.Store)]) == 1
|
|
77
|
+
and len([usage_element for usage_element in usages if isinstance(usage_element.ctx, ast.Load)]) == 1
|
|
78
78
|
and variable_name in usage_and_stores[1]
|
|
79
79
|
and not found_temporary_variable
|
|
80
80
|
):
|
|
@@ -47,3 +47,8 @@ class ViolationCodes:
|
|
|
47
47
|
DATACLASS_CONFIG = ViolationCodeItem(
|
|
48
48
|
code="COP014", description="Use dataclasses with kw_only=True, slots=True, frozen=True"
|
|
49
49
|
)
|
|
50
|
+
|
|
51
|
+
# For-loop variable prefix violations
|
|
52
|
+
FOR_LOOP_VARIABLE_PREFIX = ViolationCodeItem(
|
|
53
|
+
code="COP015", description="For-loop variables must be prefixed with 'one_'"
|
|
54
|
+
)
|
{community_of_python_flake8_plugin-0.2.0 → community_of_python_flake8_plugin-0.3.0}/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
|