diff --git a/PATTERNS.md b/PATTERNS.md index 0a7a1e578..20034afb1 100644 --- a/PATTERNS.md +++ b/PATTERNS.md @@ -310,8 +310,6 @@ class Foo { } ``` -*** - *Title*: Non final attributes *Code*: **P12** @@ -628,6 +626,21 @@ class Foo { } ``` +*** + +*Title*: Class inheritance + +*Code*: **P34** + +*Description*: Once a class extends another class via `extends`, it's a pattern. + +*Example*: + +```java +class MyList extends AbstractList { // here +} +``` + *** *Title*: Assign null diff --git a/aibolit/config.py b/aibolit/config.py index 53a2db219..fb849b841 100644 --- a/aibolit/config.py +++ b/aibolit/config.py @@ -28,6 +28,7 @@ from aibolit.patterns.assert_in_code.assert_in_code import AssertInCode as P1 from aibolit.patterns.assign_null_finder.assign_null_finder import NullAssignment as P28 from aibolit.patterns.classic_setter.classic_setter import ClassicSetter as P2 +from aibolit.patterns.class_inheritance.class_inheritance import ClassInheritance as P34 from aibolit.patterns.empty_rethrow.empty_rethrow import EmptyRethrow as P3 from aibolit.patterns.er_class.er_class import ErClass as P4 from aibolit.patterns.force_type_casting_finder.force_type_casting_finder import \ @@ -198,6 +199,7 @@ def get_patterns_config() -> PatternsConfig: ASTNodeType.FOR_STATEMENT, ASTNodeType.WHILE_STATEMENT)}, {'name': 'Incomplete For', 'code': 'P33', 'make': P33}, + {'name': 'Class inheritance', 'code': 'P34', 'make': P34}, ], 'metrics': [ {'name': 'Entropy', 'code': 'M1', 'make': M1}, # type: ignore diff --git a/aibolit/patterns/class_inheritance/__init__.py b/aibolit/patterns/class_inheritance/__init__.py new file mode 100644 index 000000000..57ecda640 --- /dev/null +++ b/aibolit/patterns/class_inheritance/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Aibolit +# SPDX-License-Identifier: MIT diff --git a/aibolit/patterns/class_inheritance/class_inheritance.py b/aibolit/patterns/class_inheritance/class_inheritance.py new file mode 100644 index 000000000..a6fb6ffff --- /dev/null +++ b/aibolit/patterns/class_inheritance/class_inheritance.py @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Aibolit +# SPDX-License-Identifier: MIT + +from typing import List + +from aibolit.ast_framework import AST, ASTNodeType + + +class ClassInheritance: + """ + Find classes that use implementation inheritance via the `extends` keyword. + """ + + def value(self, ast: AST) -> List[int]: + lines: List[int] = [] + for class_declaration in ast.proxy_nodes(ASTNodeType.CLASS_DECLARATION): + if class_declaration.extends is not None: + lines.append(class_declaration.line) + return lines diff --git a/test/patterns/class_inheritance/NestedInheritance.java b/test/patterns/class_inheritance/NestedInheritance.java new file mode 100644 index 000000000..066250149 --- /dev/null +++ b/test/patterns/class_inheritance/NestedInheritance.java @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright (c) 2019-2026 Aibolit +// SPDX-License-Identifier: MIT + +package patterns.class_inheritance; + +class Outer { + static class Base { + } + + class Inner extends Base { + } +} diff --git a/test/patterns/class_inheritance/OnlyImplements.java b/test/patterns/class_inheritance/OnlyImplements.java new file mode 100644 index 000000000..9b72496ed --- /dev/null +++ b/test/patterns/class_inheritance/OnlyImplements.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: Copyright (c) 2019-2026 Aibolit +// SPDX-License-Identifier: MIT + +package patterns.class_inheritance; + +interface Base {} +class MyList implements Base { +} diff --git a/test/patterns/class_inheritance/SimpleInheritance.java b/test/patterns/class_inheritance/SimpleInheritance.java new file mode 100644 index 000000000..705d50c4c --- /dev/null +++ b/test/patterns/class_inheritance/SimpleInheritance.java @@ -0,0 +1,8 @@ +// SPDX-FileCopyrightText: Copyright (c) 2019-2026 Aibolit +// SPDX-License-Identifier: MIT + +package patterns.class_inheritance; + +class Base {} +class MyList extends Base { +} diff --git a/test/patterns/class_inheritance/TypeParameterExtends.java b/test/patterns/class_inheritance/TypeParameterExtends.java new file mode 100644 index 000000000..580346f78 --- /dev/null +++ b/test/patterns/class_inheritance/TypeParameterExtends.java @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: Copyright (c) 2019-2026 Aibolit +// SPDX-License-Identifier: MIT + +package patterns.class_inheritance; + +class GenericBox { +} diff --git a/test/patterns/class_inheritance/test_class_inheritance.py b/test/patterns/class_inheritance/test_class_inheritance.py new file mode 100644 index 000000000..a6533457a --- /dev/null +++ b/test/patterns/class_inheritance/test_class_inheritance.py @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: Copyright (c) 2019-2026 Aibolit +# SPDX-License-Identifier: MIT + +from pathlib import Path +from unittest import TestCase + +from aibolit.ast_framework import AST +from aibolit.patterns.class_inheritance.class_inheritance import ClassInheritance +from aibolit.utils.ast_builder import build_ast + + +class ClassInheritanceTestCase(TestCase): + current_directory = Path(__file__).absolute().parent + + def test_find_class_inheritance(self): + filepath = self.current_directory / 'SimpleInheritance.java' + ast = AST.build_from_javalang(build_ast(filepath)) + pattern = ClassInheritance() + lines = pattern.value(ast) + self.assertEqual(lines, [7]) + + def test_ignore_implements_only(self): + filepath = self.current_directory / 'OnlyImplements.java' + ast = AST.build_from_javalang(build_ast(filepath)) + pattern = ClassInheritance() + lines = pattern.value(ast) + self.assertEqual(lines, []) + + def test_ignore_type_parameter_extends(self): + filepath = self.current_directory / 'TypeParameterExtends.java' + ast = AST.build_from_javalang(build_ast(filepath)) + pattern = ClassInheritance() + lines = pattern.value(ast) + self.assertEqual(lines, []) + + def test_find_nested_class_inheritance(self): + filepath = self.current_directory / 'NestedInheritance.java' + ast = AST.build_from_javalang(build_ast(filepath)) + pattern = ClassInheritance() + lines = pattern.value(ast) + self.assertEqual(lines, [10])