Skip to content

Commit 69f36d6

Browse files
committed
ADD checks for name length in comprehensions, lambdas, with statements,
and except handlers
1 parent 6954495 commit 69f36d6

2 files changed

Lines changed: 133 additions & 0 deletions

File tree

src/community_of_python_flake8_plugin/checks/name_length.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,119 @@ def visit_ClassDef(self, ast_node: ast.ClassDef) -> None:
7878
self.validate_class_name_length(ast_node)
7979
self.generic_visit(ast_node)
8080

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+
81194
def validate_name_length(self, identifier: str, ast_node: ast.stmt, parent_class: ast.ClassDef | None) -> None:
82195
if check_is_ignored_name(identifier):
83196
return

tests/test_plugin.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,26 @@ def test_dataclass_validations(input_source: str, expected_output: list[str]) ->
459459
("class ExampleClass:\n a: int = 1", ["COP012", "COP004"]),
460460
# COP004: Class-level Final assignment should still be treated as attribute
461461
("class ExampleClass:\n client: typing.Final = CreditStatementClient()", ["COP012", "COP004"]),
462+
# COP005: List comprehension with short variable names should be flagged
463+
("result_long_name = [v for v in some_list]", ["COP005"]),
464+
# No violation: List comprehension with long variable names
465+
("result_long_name = [value for value in some_list]", []),
466+
# No violation: List comprehension with underscore variable
467+
("result_long_name = [_ for _ in some_list]", []),
468+
# COP005: With statement with short variable name should be flagged
469+
("with open('file.txt') as f: pass", ["COP005"]),
470+
# No violation: With statement with long variable name
471+
("with open('file.txt') as file_handle: pass", []),
472+
# COP005: Except handler with short variable name should be flagged
473+
("try: pass\nexcept Exception as e: pass", ["COP005"]),
474+
# No violation: Except handler with long variable name
475+
("try: pass\nexcept Exception as error_exc: pass", []),
476+
# COP006: Lambda with short argument names should be flagged
477+
("func_long_name = lambda x: x * 2", ["COP006"]),
478+
# No violation: Lambda with long argument names
479+
("func_long_name = lambda value: value * 2", []),
480+
# COP006: Lambda with multiple short arguments should be flagged
481+
("func_long_name = lambda a, b: a + b", ["COP006", "COP006"]),
462482
],
463483
)
464484
def test_module_vs_class_level_assignments(input_source: str, expected_output: list[str]) -> None:

0 commit comments

Comments
 (0)