-
Notifications
You must be signed in to change notification settings - Fork 52
Refactor patterns in config to accept ast: AST
#832
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
3624022
1de871b
7a5a0d0
b85d0cf
03484df
e0f0a84
0ad6855
dc146b4
ec7ee4b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,23 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2019-2025 Aibolit | ||
| # SPDX-License-Identifier: MIT | ||
|
|
||
| import re | ||
| from typing import List | ||
|
|
||
| from aibolit.utils.encoding_detector import read_text_with_autodetected_encoding | ||
| from aibolit.ast_framework.ast import AST | ||
| from aibolit.ast_framework.ast_node import ASTNode | ||
| from aibolit.ast_framework.ast_node_type import ASTNodeType | ||
| from aibolit.types_decl import LineNumber | ||
|
|
||
|
|
||
| class NullAssignment: | ||
| def value(self, filename: str) -> List: | ||
| source_code = read_text_with_autodetected_encoding(filename) | ||
| pattern = r'[^=!><]=(\s)*null(\s)*;' | ||
| return [lineIndex + 1 for lineIndex, line in | ||
| enumerate(source_code.split('\n')) if re.search(pattern, line)] | ||
| def value(self, ast: AST) -> List[LineNumber]: | ||
| lines = set() | ||
| for assignment in ast.get_proxy_nodes(ASTNodeType.ASSIGNMENT): | ||
| if self._is_null_literal(assignment.value): | ||
| lines.add(assignment.line) | ||
| for declarator in ast.get_proxy_nodes(ASTNodeType.VARIABLE_DECLARATOR): | ||
| if self._is_null_literal(declarator.initializer): | ||
| lines.add(declarator.line) | ||
| return sorted(lines) | ||
|
|
||
| def _is_null_literal(self, node: ASTNode | None) -> bool: | ||
| return node is not None and node.node_type == ASTNodeType.LITERAL and node.value == 'null' | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,11 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2019-2025 Aibolit | ||
| # SPDX-License-Identifier: MIT | ||
| from typing import List, Optional | ||
|
|
||
|
|
||
| from typing import List, Optional, Tuple, Dict | ||
|
|
||
| import javalang | ||
| import javalang.tree | ||
|
|
||
| from aibolit.utils.java_parser import JavalangImproved, ASTNode | ||
| from aibolit.ast_framework.ast import AST | ||
| from aibolit.ast_framework.ast_node import ASTNode | ||
| from aibolit.ast_framework.ast_node_type import ASTNodeType | ||
| from aibolit.types_decl import LineNumber | ||
|
|
||
|
|
||
| class VarDeclarationDistance: | ||
|
|
@@ -25,75 +23,59 @@ def __node_name(self, node) -> Optional[str]: | |
| name = node.name if hasattr(node, 'name') else None | ||
| return qualifier or member or name | ||
|
|
||
| def __group_vars_by_method(self, items: List[Tuple[ASTNode, Optional[str]]]) -> List[Dict]: | ||
| """ | ||
| Group variables by method scope and calculate for each the declaration | ||
| line and first usage line | ||
| """ | ||
| var_scopes: List[Dict] = [] | ||
| vars: Dict = {} | ||
| unique_methods = list(set( | ||
| map(lambda v: v[0].method_line, items) | ||
| )) | ||
|
|
||
| for method in unique_methods: | ||
| # Filter items for current method to avoid cell-var-from-loop issue | ||
| method_filtered_items = [item for item in items if item[0].method_line == method] | ||
| method_items = map( | ||
| lambda v: {'line': v[0].line, 'name': v[1], 'ntype': type(v[0].node)}, | ||
| method_filtered_items | ||
| ) | ||
| vars = {} | ||
| var_scopes += [vars] | ||
| for item in method_items: | ||
| if item['ntype'] in [javalang.tree.MethodDeclaration]: | ||
| vars = {} | ||
| var_scopes += [vars] | ||
| continue | ||
|
|
||
| if item['ntype'] == javalang.tree.VariableDeclarator: | ||
| vars[item['name']] = {'decl': item['line'], 'first_usage': None} | ||
| continue | ||
|
|
||
| if item['ntype'] == javalang.tree.VariableDeclarator: | ||
| vars[item['name']] = {'decl': item['line']} | ||
| continue | ||
|
|
||
| if item['name'] in vars and vars[item['name']]['first_usage'] is None: | ||
| vars[item['name']]['first_usage'] = item['line'] | ||
|
|
||
| return var_scopes | ||
|
|
||
| def __line_diff(self, usage_line: int, declaration_line: int, empty_lines: List[int]) -> int: | ||
| def __line_diff(self, usage_line: int, declaration_line: int, empty_lines: set[int]) -> int: | ||
| """ | ||
| Calculate line difference between variable declaration and first usage | ||
| taking into account empty lines | ||
| """ | ||
| lines_range = set(range(declaration_line + 1, usage_line)) | ||
| return len(lines_range.difference(empty_lines)) | ||
|
|
||
| def value(self, filename: str) -> List[int]: | ||
| """Find variables declared far from their first usage.""" | ||
| tree = JavalangImproved(filename) | ||
| empty_lines = tree.get_empty_lines() | ||
| items = list( | ||
| map(lambda v: (v, self.__node_name(v.node)), tree.tree_to_nodes()) | ||
| ) | ||
| var_scopes = self.__group_vars_by_method(items) | ||
| violations = [] | ||
| for scope in var_scopes: | ||
| for var, var_data in scope.items(): | ||
| if var_data['first_usage'] is None: | ||
| continue | ||
| def __find_empty_lines_in_ast(self, ast: AST) -> set[LineNumber]: | ||
| """Figure out lines that are either empty or multiline statements""" | ||
|
|
||
| lines_with_nodes = set() | ||
| ast.traverse(lambda node: lines_with_nodes.add(node.line)) | ||
| max_line = max(lines_with_nodes) | ||
| all_lines = set(range(1, max_line + 1)) | ||
|
Comment on lines
+36
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard against - ast.traverse(lambda node: lines_with_nodes.add(node.line))
+ # Skip nodes without a concrete line number to avoid mixing
+ # `None` with integers which breaks `max()` below.
+ ast.traverse(
+ lambda node: lines_with_nodes.add(node.line)
+ if getattr(node, "line", None) is not None
+ else None
+ )
+ if not lines_with_nodes:
+ return set()
🤖 Prompt for AI Agents |
||
| return all_lines.difference(lines_with_nodes) | ||
|
|
||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| def __value_for_method_declaration( | ||
| self, method_declaration: AST, empty_lines: set[LineNumber] | ||
| ) -> set[LineNumber]: | ||
| """Find variables declared far from their first usage in the method.""" | ||
| result = set() | ||
| name_to_declaration_line = {} | ||
| name_to_first_usage_line = {} | ||
|
|
||
| def collect_declaration_or_usage(node: ASTNode): | ||
| node_name = self.__node_name(node) | ||
| if node_name: | ||
| if node.node_type == ASTNodeType.VARIABLE_DECLARATOR: | ||
| name_to_declaration_line[node_name] = node.line | ||
| elif ( | ||
| node_name in name_to_declaration_line and | ||
| node_name not in name_to_first_usage_line | ||
| ): | ||
| name_to_first_usage_line[node_name] = node.line | ||
|
|
||
|
Comment on lines
+52
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Variable shadowing may overwrite first declaration When traversing the method, every 🤖 Prompt for AI Agents |
||
| method_declaration.traverse(collect_declaration_or_usage) | ||
| for name, usage_line in name_to_first_usage_line.items(): | ||
| line_diff = self.__line_diff( | ||
| usage_line, | ||
| name_to_declaration_line[name], | ||
| empty_lines, | ||
| ) | ||
| if line_diff >= self.__lines_th: | ||
| result.add(usage_line) | ||
| return result | ||
|
|
||
| line_diff = self.__line_diff( | ||
| var_data['first_usage'], | ||
| var_data['decl'], | ||
| empty_lines | ||
| ) | ||
| if line_diff < self.__lines_th: | ||
| continue | ||
| def value(self, ast: AST) -> List[LineNumber]: | ||
| """Find variables declared far from their first usage.""" | ||
|
|
||
| violations.append(var_data['first_usage']) | ||
| result = set() | ||
| empty_lines = self.__find_empty_lines_in_ast(ast) | ||
| for declaration in ast.get_subtrees(ASTNodeType.METHOD_DECLARATION): | ||
| result.update(self.__value_for_method_declaration(declaration, empty_lines)) | ||
|
|
||
| return violations | ||
| return sorted(result) | ||
Uh oh!
There was an error while loading. Please reload this page.