Skip to content

Commit

Permalink
feat(spoon): Add implicit array to string rule (#910)
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinWitt authored Aug 1, 2023
1 parent c2ca653 commit 56aa480
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> trygetOriginalSourceCode(CtElement element) {
try {
File file = element.getPosition().getCompilationUnit().getFile();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -68,6 +69,10 @@ default U visit(EqualsHashcode badSmell) {
return emptyResult();
}

default U visit(ImplicitArrayToString badSmell) {
return emptyResult();
}

default U emptyResult() {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand Down
Original file line number Diff line number Diff line change
@@ -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> T accept(BadSmellVisitor<T> visitor) {
return visitor.visit(this);
}
}
Original file line number Diff line number Diff line change
@@ -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<BadSmell> analyze(CtType<?> clazz) {
List<BadSmell> badSmells = new ArrayList<BadSmell>();
ImplicitToStringMatcher matcher = new ImplicitToStringMatcher();
List<CtInvocation<?>> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}

0 comments on commit 56aa480

Please sign in to comment.