From 56aa480320a9a60d3bc94db6309f49dc713b93a3 Mon Sep 17 00:00:00 2001 From: Martin Wittlinger Date: Tue, 1 Aug 2023 18:47:06 +0200 Subject: [PATCH] feat(spoon): Add implicit array to string rule (#910) --- .../analyzer/spoon/AnalyzerResultVisitor.java | 9 ++++ .../spoonutils/ImplicitToStringMatcher.java | 2 +- .../spoon_analyzer/BadSmellVisitor.java | 5 +++ .../spoon_analyzer/badsmells/SpoonRules.java | 3 ++ .../ImplicitArrayToString.java | 41 +++++++++++++++++++ .../ImplicitArrayToStringAnalyzer.java | 29 +++++++++++++ .../ImplicitArrayToStringAnalyzerTest.java | 32 +++++++++++++++ 7 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 spoon-analyzer/src/main/java/io/github/martinwitt/spoon_analyzer/badsmells/implicit_array_to_string/ImplicitArrayToString.java create mode 100644 spoon-analyzer/src/main/java/io/github/martinwitt/spoon_analyzer/badsmells/implicit_array_to_string/ImplicitArrayToStringAnalyzer.java create mode 100644 spoon-analyzer/src/test/java/io/github/martinwitt/spoon_analyzer/badsmells/implicit_array_to_string/ImplicitArrayToStringAnalyzerTest.java diff --git a/code-transformation/src/main/java/xyz/keksdose/spoon/code_solver/analyzer/spoon/AnalyzerResultVisitor.java b/code-transformation/src/main/java/xyz/keksdose/spoon/code_solver/analyzer/spoon/AnalyzerResultVisitor.java index c5d768a26..50f3648b0 100644 --- a/code-transformation/src/main/java/xyz/keksdose/spoon/code_solver/analyzer/spoon/AnalyzerResultVisitor.java +++ b/code-transformation/src/main/java/xyz/keksdose/spoon/code_solver/analyzer/spoon/AnalyzerResultVisitor.java @@ -13,6 +13,7 @@ import io.github.martinwitt.spoon_analyzer.badsmells.charset_object_can_be_used.CharsetObjectCanBeUsed; import io.github.martinwitt.spoon_analyzer.badsmells.equals_hashcode.EqualsHashcode; import io.github.martinwitt.spoon_analyzer.badsmells.final_static_method.FinalStaticMethod; +import io.github.martinwitt.spoon_analyzer.badsmells.implicit_array_to_string.ImplicitArrayToString; import io.github.martinwitt.spoon_analyzer.badsmells.innerclass_may_be_static.InnerClassMayBeStatic; import io.github.martinwitt.spoon_analyzer.badsmells.non_protected_constructor_In_abstract_class.NonProtectedConstructorInAbstractClass; import io.github.martinwitt.spoon_analyzer.badsmells.private_final_method.PrivateFinalMethod; @@ -191,6 +192,14 @@ public AnalyzerResult visit(EqualsHashcode badSmell) { return toSpoonAnalyzerResult(badSmell, badSmell.getAffectedType().getPosition(), snippet); } + @Override + public AnalyzerResult visit(ImplicitArrayToString badSmell) { + String snippet = trygetOriginalSourceCode(badSmell.getImplicitToStringCaller()) + .orElse(badSmell.getImplicitToStringCaller().toString()); + return toSpoonAnalyzerResult( + badSmell, badSmell.getImplicitToStringCaller().getPosition(), snippet); + } + private Optional trygetOriginalSourceCode(CtElement element) { try { File file = element.getPosition().getCompilationUnit().getFile(); diff --git a/matcher/src/main/java/io/github/martinwitt/laughing_train/spoonutils/ImplicitToStringMatcher.java b/matcher/src/main/java/io/github/martinwitt/laughing_train/spoonutils/ImplicitToStringMatcher.java index 2dcef072f..d659be9bb 100644 --- a/matcher/src/main/java/io/github/martinwitt/laughing_train/spoonutils/ImplicitToStringMatcher.java +++ b/matcher/src/main/java/io/github/martinwitt/laughing_train/spoonutils/ImplicitToStringMatcher.java @@ -91,7 +91,7 @@ private boolean valueOfMethod(CtInvocation element, String qualifiedName) { * @return true if the specified {@link CtInvocation} object is a call to one of the methods above, false otherwise */ private boolean printMethod(CtInvocation element, String qualifiedName) { - CtTypeReference type = element.getType(); + CtTypeReference type = element.getExecutable().getDeclaringType(); if (type == null) { return false; } diff --git a/spoon-analyzer/src/main/java/io/github/martinwitt/spoon_analyzer/BadSmellVisitor.java b/spoon-analyzer/src/main/java/io/github/martinwitt/spoon_analyzer/BadSmellVisitor.java index 3f99c6e27..47e9860a3 100644 --- a/spoon-analyzer/src/main/java/io/github/martinwitt/spoon_analyzer/BadSmellVisitor.java +++ b/spoon-analyzer/src/main/java/io/github/martinwitt/spoon_analyzer/BadSmellVisitor.java @@ -6,6 +6,7 @@ import io.github.martinwitt.spoon_analyzer.badsmells.charset_object_can_be_used.CharsetObjectCanBeUsed; import io.github.martinwitt.spoon_analyzer.badsmells.equals_hashcode.EqualsHashcode; import io.github.martinwitt.spoon_analyzer.badsmells.final_static_method.FinalStaticMethod; +import io.github.martinwitt.spoon_analyzer.badsmells.implicit_array_to_string.ImplicitArrayToString; import io.github.martinwitt.spoon_analyzer.badsmells.innerclass_may_be_static.InnerClassMayBeStatic; import io.github.martinwitt.spoon_analyzer.badsmells.non_protected_constructor_In_abstract_class.NonProtectedConstructorInAbstractClass; import io.github.martinwitt.spoon_analyzer.badsmells.private_final_method.PrivateFinalMethod; @@ -68,6 +69,10 @@ default U visit(EqualsHashcode badSmell) { return emptyResult(); } + default U visit(ImplicitArrayToString badSmell) { + return emptyResult(); + } + default U emptyResult() { return null; } diff --git a/spoon-analyzer/src/main/java/io/github/martinwitt/spoon_analyzer/badsmells/SpoonRules.java b/spoon-analyzer/src/main/java/io/github/martinwitt/spoon_analyzer/badsmells/SpoonRules.java index bed174f78..51a7ae883 100644 --- a/spoon-analyzer/src/main/java/io/github/martinwitt/spoon_analyzer/badsmells/SpoonRules.java +++ b/spoon-analyzer/src/main/java/io/github/martinwitt/spoon_analyzer/badsmells/SpoonRules.java @@ -31,6 +31,9 @@ public enum SpoonRules { SIZE_REPLACEABLE_BY_IS_EMPTY( "SizeReplaceableByIsEmpty", "Checking if an collection is empty by comparing its size to 0 is redundant. Use isEmpty() instead."), + IMPLICIT_ARRAY_TO_STRING( + "ImplicitArrayToString", + "Calling toString() on an array returns not the content but the toString from the array object itself."), UNNECESSARY_IMPLEMENTS( "UnnecessaryImplements", "This class has 1 or more interfaces which are already implemented."), UNNECESSARY_TOSTRING("UnnecessaryTostring", "Calling to String on a String object is unnecessary."); diff --git a/spoon-analyzer/src/main/java/io/github/martinwitt/spoon_analyzer/badsmells/implicit_array_to_string/ImplicitArrayToString.java b/spoon-analyzer/src/main/java/io/github/martinwitt/spoon_analyzer/badsmells/implicit_array_to_string/ImplicitArrayToString.java new file mode 100644 index 000000000..d370cd679 --- /dev/null +++ b/spoon-analyzer/src/main/java/io/github/martinwitt/spoon_analyzer/badsmells/implicit_array_to_string/ImplicitArrayToString.java @@ -0,0 +1,41 @@ +package io.github.martinwitt.spoon_analyzer.badsmells.implicit_array_to_string; + +import io.github.martinwitt.spoon_analyzer.BadSmell; +import io.github.martinwitt.spoon_analyzer.BadSmellVisitor; +import spoon.reflect.code.CtInvocation; +import spoon.reflect.declaration.CtType; + +public class ImplicitArrayToString implements BadSmell { + + private final CtType clazz; + private final CtInvocation implicitToStringCaller; + + public ImplicitArrayToString(CtType clazz, CtInvocation implicitToStringCaller) { + this.clazz = clazz; + this.implicitToStringCaller = implicitToStringCaller; + } + + @Override + public String getName() { + return "ImplicitArrayToString"; + } + + @Override + public String getDescription() { + return "Calling toString() on an array returns not the content but the toString from the array object itself."; + } + + @Override + public CtType getAffectedType() { + return clazz; + } + + public CtInvocation getImplicitToStringCaller() { + return implicitToStringCaller; + } + + @Override + public T accept(BadSmellVisitor visitor) { + return visitor.visit(this); + } +} diff --git a/spoon-analyzer/src/main/java/io/github/martinwitt/spoon_analyzer/badsmells/implicit_array_to_string/ImplicitArrayToStringAnalyzer.java b/spoon-analyzer/src/main/java/io/github/martinwitt/spoon_analyzer/badsmells/implicit_array_to_string/ImplicitArrayToStringAnalyzer.java new file mode 100644 index 000000000..5c4d775a4 --- /dev/null +++ b/spoon-analyzer/src/main/java/io/github/martinwitt/spoon_analyzer/badsmells/implicit_array_to_string/ImplicitArrayToStringAnalyzer.java @@ -0,0 +1,29 @@ +package io.github.martinwitt.spoon_analyzer.badsmells.implicit_array_to_string; + +import io.github.martinwitt.laughing_train.spoonutils.ImplicitToStringMatcher; +import io.github.martinwitt.spoon_analyzer.BadSmell; +import io.github.martinwitt.spoon_analyzer.LocalAnalyzer; +import java.util.ArrayList; +import java.util.List; +import spoon.reflect.code.CtInvocation; +import spoon.reflect.declaration.CtType; +import spoon.reflect.visitor.filter.TypeFilter; + +public class ImplicitArrayToStringAnalyzer implements LocalAnalyzer { + + @Override + public List analyze(CtType clazz) { + List badSmells = new ArrayList(); + ImplicitToStringMatcher matcher = new ImplicitToStringMatcher(); + List> invocations = clazz.getElements(new TypeFilter<>(CtInvocation.class)); + for (CtInvocation invocation : invocations) { + if (matcher.matches(invocation)) { + if (invocation.getArguments().stream() + .anyMatch(v -> v.getType() != null && v.getType().isArray())) { + badSmells.add(new ImplicitArrayToString(clazz, invocation)); + } + } + } + return badSmells; + } +} diff --git a/spoon-analyzer/src/test/java/io/github/martinwitt/spoon_analyzer/badsmells/implicit_array_to_string/ImplicitArrayToStringAnalyzerTest.java b/spoon-analyzer/src/test/java/io/github/martinwitt/spoon_analyzer/badsmells/implicit_array_to_string/ImplicitArrayToStringAnalyzerTest.java new file mode 100644 index 000000000..c5e992457 --- /dev/null +++ b/spoon-analyzer/src/test/java/io/github/martinwitt/spoon_analyzer/badsmells/implicit_array_to_string/ImplicitArrayToStringAnalyzerTest.java @@ -0,0 +1,32 @@ +package io.github.martinwitt.spoon_analyzer.badsmells.implicit_array_to_string; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import spoon.Launcher; +import spoon.reflect.declaration.CtType; +import spoon.support.compiler.VirtualFile; + +public class ImplicitArrayToStringAnalyzerTest { + + @Test + void UpperClassEqualsNoHashcode() { + String code = + """ + class A { + public void print(Object[] obj) { + System.out.println(obj); + } + } + """; + + Launcher launcher = new Launcher(); + launcher.addInputResource(new VirtualFile(code)); + var model = launcher.buildModel(); + ImplicitArrayToStringAnalyzer analyzer = new ImplicitArrayToStringAnalyzer(); + CtType simpleClass = model.getAllTypes().stream().findFirst().get(); + var result = analyzer.analyze(simpleClass); + + assertEquals(1, result.size()); + } +}