Skip to content
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

feat(spoon): Add implicit array to string rule #910

Merged
merged 2 commits into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());
}
}