Skip to content

Commit

Permalink
Merge branch '__rultor'
Browse files Browse the repository at this point in the history
  • Loading branch information
rultor committed Jul 15, 2020
2 parents 1fc6e6a + 0aeb37d commit e961ec1
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 95 deletions.
1 change: 1 addition & 0 deletions aibolit/ast_framework/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from aibolit.ast_framework.ast_node_type import ASTNodeType # noqa: F401
from aibolit.ast_framework.ast_node import ASTNode # noqa: F401
from aibolit.ast_framework.ast import AST # noqa: F401
22 changes: 22 additions & 0 deletions aibolit/ast_framework/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from javalang.tree import Node
from typing import Union, Any, Set, List, Iterator, Tuple, Dict, cast
from networkx import DiGraph, dfs_labeled_edges # type: ignore
from deprecated import deprecated # type: ignore

from aibolit.ast_framework.ast_node_type import ASTNodeType
from aibolit.ast_framework._auxiliary_data import javalang_to_ast_node_type, attributes_by_node_type, ASTNodeReference
Expand Down Expand Up @@ -73,6 +74,10 @@ def __str__(self) -> str:
def get_root(self) -> ASTNode:
return ASTNode(self.tree, self.root)

def __iter__(self) -> Iterator[ASTNode]:
for node_index in self.tree.nodes:
yield ASTNode(self.tree, node_index)

def get_subtrees(self, root_type: ASTNodeType) -> Iterator['AST']:
'''
Yields subtrees with given type of the root.
Expand All @@ -96,6 +101,7 @@ def get_subtrees(self, root_type: ASTNodeType) -> Iterator['AST']:
subtree = []
current_subtree_root = -1

@deprecated(reason='Use ASTNode functionality instead.')
def children_with_type(self, node: int, child_type: ASTNodeType) -> Iterator[int]:
'''
Yields children of node with given type.
Expand All @@ -104,6 +110,7 @@ def children_with_type(self, node: int, child_type: ASTNodeType) -> Iterator[int
if self.tree.nodes[child]['node_type'] == child_type:
yield child

@deprecated(reason='Use ASTNode functionality instead.')
def list_all_children_with_type(self, node: int, child_type: ASTNodeType) -> List[int]:
list_node: List[int] = []
for child in self.tree.succ[node]:
Expand All @@ -112,13 +119,15 @@ def list_all_children_with_type(self, node: int, child_type: ASTNodeType) -> Lis
list_node.append(child)
return sorted(list_node)

@deprecated(reason='Use ASTNode functionality instead.')
def all_children_with_type(self, node: int, child_type: ASTNodeType) -> Iterator[int]:
'''
Yields all children of node with given type.
'''
for child in self.list_all_children_with_type(node, child_type):
yield child

@deprecated(reason='Use ASTNode functionality instead.')
def get_first_n_children_with_type(self, node: int, child_type: ASTNodeType, quantity: int) -> List[int]:
'''
Returns first quantity of children of node with type child_type.
Expand All @@ -128,29 +137,40 @@ def get_first_n_children_with_type(self, node: int, child_type: ASTNodeType, qua
children_with_type_padded = chain(children_with_type, repeat(None))
return list(islice(children_with_type_padded, 0, quantity))

@deprecated(reason='Use ASTNode functionality instead.')
def get_binary_operation_name(self, node: int) -> str:
assert(self.get_type(node) == ASTNodeType.BINARY_OPERATION)
name_node, = islice(self.children_with_type(node, ASTNodeType.STRING), 1)
return self.get_attr(name_node, 'string')

@deprecated(reason='Use ASTNode functionality instead.')
def get_line_number_from_children(self, node: int) -> int:
for child in self.tree.succ[node]:
cur_line = self.get_attr(child, 'line')
if cur_line is not None:
return cur_line
return 0

@deprecated(reason='Use get_proxy_nodes instead.')
def get_nodes(self, type: Union[ASTNodeType, None] = None) -> Iterator[int]:
for node in self.tree.nodes:
if type is None or self.tree.nodes[node]['node_type'] == type:
yield node

def get_proxy_nodes(self, type: ASTNodeType) -> Iterator[ASTNode]:
for node in self.tree.nodes:
if self.tree.nodes[node]['node_type'] == type:
yield ASTNode(self.tree, node)

@deprecated(reason='Use ASTNode functionality instead.')
def get_attr(self, node: int, attr_name: str, default_value: Any = None) -> Any:
return self.tree.nodes[node].get(attr_name, default_value)

@deprecated(reason='Use ASTNode functionality instead.')
def get_type(self, node: int) -> ASTNodeType:
return self.get_attr(node, 'node_type')

@deprecated(reason='Use ASTNode functionality instead.')
def get_method_invocation_params(self, invocation_node: int) -> MethodInvocationParams:
assert(self.get_type(invocation_node) == ASTNodeType.METHOD_INVOCATION)
# first two STRING nodes represent object and method names
Expand All @@ -161,6 +181,7 @@ def get_method_invocation_params(self, invocation_node: int) -> MethodInvocation
return MethodInvocationParams(self.get_attr(children[0], 'string'),
self.get_attr(children[1], 'string'))

@deprecated(reason='Use ASTNode functionality instead.')
def get_member_reference_params(self, member_reference_node: int) -> MemberReferenceParams:
assert(self.get_type(member_reference_node) == ASTNodeType.MEMBER_REFERENCE)
params = [self.get_attr(child, 'string') for child in
Expand All @@ -181,6 +202,7 @@ def get_member_reference_params(self, member_reference_node: int) -> MemberRefer

return member_reference_params

@deprecated(reason='Use ASTNode functionality instead.')
def get_binary_operation_params(self, binary_operation_node: int) -> BinaryOperationParams:
assert(self.get_type(binary_operation_node) == ASTNodeType.BINARY_OPERATION)
operation_node, left_side_node, right_side_node = self.tree.succ[binary_operation_node]
Expand Down
2 changes: 1 addition & 1 deletion aibolit/ast_framework/ast_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,4 @@ def __str__(self) -> str:
return text_representation

def __repr__(self) -> str:
return f'<ASTNode node_type: {self.__getattr__("type")}, node_index: {self._node_index}>'
return f'<ASTNode node_type: {self.__getattr__("node_type")}, node_index: {self._node_index}>'
39 changes: 31 additions & 8 deletions aibolit/patterns/implements_multi/implements_multi.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,36 @@
import javalang
# The MIT License (MIT)
#
# Copyright (c) 2020 Aibolit
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from typing import List

from aibolit.ast_framework.ast import AST, ASTNodeType
from aibolit.utils.ast_builder import build_ast


class ImplementsMultiFinder:

def __init__(self):
pass

def value(self, filename: str):
tree = build_ast(filename).filter(javalang.tree.ClassDeclaration)
return [node._position.line for _, node in tree if node.implements and (len(node.implements) > 1)]
def value(self, filename: str) -> List[int]:
tree = AST.build_from_javalang(build_ast(filename))
lines: List[int] = []
for node in tree.get_proxy_nodes(ASTNodeType.CLASS_DECLARATION):
if node.implements and len(node.implements) > 1:
lines.append(node.line)
return lines
59 changes: 24 additions & 35 deletions aibolit/patterns/joined_validation/joined_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,47 +20,36 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from typing import List, Tuple
from typing import List

from aibolit.utils.ast_builder import build_ast
from aibolit.ast_framework import AST, ASTNodeType
from aibolit.ast_framework import AST, ASTNode, ASTNodeType


class JoinedValidation:
"""
Pattern which matches joined validations: validations (if with a single throw inside) which condition
contains more than condition joined with OR
"""

def __init__(self):
pass

def check_throw(self, node: int, tree: 'AST', lines: List[int]) -> Tuple[List[int], bool]:
children_throw = list(tree.children_with_type(node, ASTNodeType.THROW_STATEMENT))
if len(children_throw) > 0:
lines.append(tree.get_attr(node, 'line'))
return lines, True
return lines, False
'''
Finds all if statements, which "then" branch consist of a single throw statement
and logical or "||" used in condition.
'''

def value(self, filename: str) -> List[int]:
"""
Returns the line number of joined validations found in file.
"""
tree = AST.build_from_javalang(build_ast(filename))
ast = AST.build_from_javalang(build_ast(filename))
lines: List[int] = []
for node in tree.get_nodes(ASTNodeType.IF_STATEMENT):
flag_or = False
for child in tree.all_children_with_type(node, ASTNodeType.BINARY_OPERATION):
operation_name = tree.get_binary_operation_name(child)
if operation_name == '||':
flag_or = True
if not flag_or:
continue
lines, flag = self.check_throw(node, tree, lines)
if flag:
continue
child_block = list(tree.children_with_type(node, ASTNodeType.BLOCK_STATEMENT))
if len(child_block) == 0:
continue
lines, _ = self.check_throw(child_block[0], tree, lines)
for if_statement in ast.get_proxy_nodes(ASTNodeType.IF_STATEMENT):
if self._is_logical_or_used_in_expression(if_statement.condition) and \
self._is_block_consist_of_single_throw(if_statement.then_statement):
lines.append(if_statement.line)
return lines

def _is_logical_or_used_in_expression(self, expression: ASTNode) -> bool:
if expression.node_type == ASTNodeType.BINARY_OPERATION and expression.operator == '||':
return True

return any(self._is_logical_or_used_in_expression(child) for child in expression.children)

def _is_block_consist_of_single_throw(self, block: ASTNode) -> bool:
if block.node_type == ASTNodeType.THROW_STATEMENT:
return True

children = list(block.children)
return len(children) == 1 and children[0].node_type == ASTNodeType.THROW_STATEMENT
49 changes: 23 additions & 26 deletions aibolit/patterns/return_null/return_null.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,37 +23,34 @@
from typing import List

from aibolit.utils.ast_builder import build_ast
from aibolit.ast_framework import AST, ASTNodeType
from aibolit.ast_framework import AST, ASTNode, ASTNodeType


class ReturnNull:

def __init__(self):
pass

def __check_null_in_return_args(self, node: int, tree: AST) -> bool:
for child1 in tree.children_with_type(node, ASTNodeType.LITERAL):
for child2 in tree.children_with_type(child1, ASTNodeType.STRING):
if tree.get_attr(child2, 'string') == 'null':
return True
return False

def __check_null_in_return_statement(self, node: int, tree: AST) -> bool:
child_ternary = list(tree.children_with_type(node, ASTNodeType.TERNARY_EXPRESSION))
if len(child_ternary) > 0:
return self.__check_null_in_return_args(child_ternary[0], tree)
return self.__check_null_in_return_args(node, tree)
'''
FInds all return statements that returns null directly or by ternary operator.
NOTICE: nested ternary operators are not checked.
'''

def value(self, filename: str) -> List[int]:
"""
Travers over AST tree and finds pattern
:param filename:
"""
tree = AST.build_from_javalang(build_ast(filename))
ast = AST.build_from_javalang(build_ast(filename))
lines: List[int] = []
for node in tree.get_nodes(ASTNodeType.METHOD_DECLARATION):
for child in tree.children_with_type(node, ASTNodeType.RETURN_STATEMENT):
if self.__check_null_in_return_statement(child, tree):
lines.append(tree.get_attr(child, 'line'))
for return_statement in ast.get_proxy_nodes(ASTNodeType.RETURN_STATEMENT):
if self._check_null_return_statement(return_statement):
lines.append(return_statement.line)

return lines

def _check_null_return_statement(self, return_statement: ASTNode) -> bool:
# return statement with no expression `return;` does not return null
if return_statement.expression is None:
return False

if return_statement.expression.node_type == ASTNodeType.TERNARY_EXPRESSION:
return self._check_null_expression(return_statement.expression.if_true) or \
self._check_null_expression(return_statement.expression.if_false)

return self._check_null_expression(return_statement.expression)

def _check_null_expression(self, expression: ASTNode) -> bool:
return expression.node_type == ASTNodeType.LITERAL and expression.value == 'null'
55 changes: 32 additions & 23 deletions aibolit/patterns/supermethod/supermethod.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
from aibolit.ast_framework.ast import ASTNodeType
from aibolit.ast_framework.java_package import JavaPackage
from typing import List
# The MIT License (MIT)
#
# Copyright (c) 2020 Aibolit
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from typing import List

class SuperMethod:
from aibolit.ast_framework.ast import AST, ASTNodeType
from aibolit.utils.ast_builder import build_ast

def __init__(self):
pass

class SuperMethod:
def value(self, filename: str) -> List[int]:
"""
Iterates over functions and finds super.func() calls.
Javalang doesn't have code line for super.func() call,
that's why we can only count the first match of a call inside some function.
It has MULTIPLE MATCHES if we call super.func() inside a ANONYMOUS CLASS.
:param filename:
:return: Lines of code
"""
results = []
tree = JavaPackage(filename)
for ast_method in tree.get_subtrees(ASTNodeType.METHOD_DECLARATION):
for statement in ast_method.get_nodes(ASTNodeType.STATEMENT_EXPRESSION):
code_line = ast_method.get_attr(statement, 'line')
all_super_methods = ast_method.children_with_type(statement, ASTNodeType.SUPER_METHOD_INVOCATION)
if len(list(all_super_methods)):
results.append(code_line)
return results
ast = AST.build_from_javalang(build_ast(filename))
lines: List[int] = []
for statement in ast.get_proxy_nodes(ASTNodeType.STATEMENT_EXPRESSION):
if any(child.node_type == ASTNodeType.SUPER_METHOD_INVOCATION
for child in statement.children):
lines.append(statement.line)
return lines
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ catboost==0.22
cchardet==2.1.6
lxml==4.5.0
cached-property==1.2.0
deprecated==1.2.10
typing-extensions; python_version<'3.8'
2 changes: 1 addition & 1 deletion test/patterns/return_null/test_return_null.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class TestMethodChain(TestCase):

def test_anonymous(self):
lines = self.method_chain_finder.value(Path(self.dir_path, 'Anonymous.java'))
self.assertEqual(lines, [28, 24])
self.assertEqual(lines, [24, 28])

def test_empty(self):
lines = self.method_chain_finder.value(Path(self.dir_path, 'Empty.java'))
Expand Down
2 changes: 1 addition & 1 deletion test/patterns/supermethod/test_supermethod.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def test_nested_class(self):

def test_constructor(self):
file = str(Path(self.cur_file_dir, 'Constructor.java'))
self.assertEqual(len(self.testClass.value(file)), 0)
self.assertEqual(len(self.testClass.value(file)), 3)

def test_complicated_constructor(self):
file = str(Path(self.cur_file_dir, 'ComplicatedChainConstructor.java'))
Expand Down

0 comments on commit e961ec1

Please sign in to comment.