From e79160e10ff93bee76ba25b8f8686f3203b6cb20 Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Tue, 23 Apr 2013 16:28:26 +0200 Subject: [PATCH] IDEA-105612 (Add "redundant toString() call" inspection) --- .../InspectionGadgets/src/META-INF/plugin.xml | 4 + .../siyeh/InspectionGadgetsBundle.properties | 2 + .../UnnecessaryToStringCallInspection.java | 200 ++++++++++++++++++ .../UnnecessaryToStringCall.html | 13 ++ .../UnnecessaryToString.java | 30 +++ .../style/unnecessary_tostring/expected.xml | 32 +++ ...UnnecessaryToStringCallInspectionTest.java | 10 + 7 files changed, 291 insertions(+) create mode 100644 plugins/InspectionGadgets/src/com/siyeh/ig/style/UnnecessaryToStringCallInspection.java create mode 100644 plugins/InspectionGadgets/src/inspectionDescriptions/UnnecessaryToStringCall.html create mode 100644 plugins/InspectionGadgets/test/com/siyeh/igtest/style/unnecessary_tostring/UnnecessaryToString.java create mode 100644 plugins/InspectionGadgets/test/com/siyeh/igtest/style/unnecessary_tostring/expected.xml create mode 100644 plugins/InspectionGadgets/testsrc/com/siyeh/ig/style/UnnecessaryToStringCallInspectionTest.java diff --git a/plugins/InspectionGadgets/src/META-INF/plugin.xml b/plugins/InspectionGadgets/src/META-INF/plugin.xml index 1b3efecf24ca..2e8c2d98e469 100644 --- a/plugins/InspectionGadgets/src/META-INF/plugin.xml +++ b/plugins/InspectionGadgets/src/META-INF/plugin.xml @@ -2297,6 +2297,10 @@ key="unnecessary.call.to.string.valueof.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.code.style.issues" enabledByDefault="false" level="WARNING" implementationClass="com.siyeh.ig.style.UnnecessaryCallToStringValueOfInspection"/> + #ref can be simplified to ''{0}'' #loc +unnecessary.tostring.call.problem.descriptor=Unnecessary #ref() call #loc unnecessary.call.to.string.valueof.quickfix=Replace with ''{0}'' throwable.result.of.method.call.ignored.display.name=Throwable result of method call ignored throwable.result.of.method.call.ignored.problem.descriptor=Result of #ref() not thrown #loc diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/style/UnnecessaryToStringCallInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/style/UnnecessaryToStringCallInspection.java new file mode 100644 index 000000000000..d7cad4ef4f13 --- /dev/null +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/style/UnnecessaryToStringCallInspection.java @@ -0,0 +1,200 @@ +/* + * Copyright 2008-2013 Bas Leijdekkers + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.siyeh.ig.style; + +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.util.IncorrectOperationException; +import com.siyeh.InspectionGadgetsBundle; +import com.siyeh.ig.BaseInspection; +import com.siyeh.ig.BaseInspectionVisitor; +import com.siyeh.ig.InspectionGadgetsFix; +import com.siyeh.ig.psiutils.ParenthesesUtils; +import com.siyeh.ig.psiutils.TypeUtils; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class UnnecessaryToStringCallInspection extends BaseInspection { + + @Override + @Nls + @NotNull + public String getDisplayName() { + return InspectionGadgetsBundle.message("unnecessary.tostring.call.display.name"); + } + + @Override + @NotNull + protected String buildErrorString(Object... infos) { + final String text = (String)infos[0]; + return InspectionGadgetsBundle.message("unnecessary.tostring.call.problem.descriptor", text); + } + + @Override + @Nullable + protected InspectionGadgetsFix buildFix(Object... infos) { + final String text = (String)infos[0]; + return new UnnecessaryCallToStringValueOfFix(text); + } + + public static String calculateReplacementText(PsiExpression expression) { + if (expression == null) { + return "this"; + } + return expression.getText(); + } + + private static class UnnecessaryCallToStringValueOfFix extends InspectionGadgetsFix { + + private final String replacementText; + + UnnecessaryCallToStringValueOfFix(String replacementText) { + this.replacementText = replacementText; + } + + @NotNull + public String getName() { + return InspectionGadgetsBundle.message("unnecessary.call.to.string.valueof.quickfix", replacementText); + } + + @Override + protected void doFix(Project project, ProblemDescriptor descriptor) throws IncorrectOperationException { + final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)descriptor.getPsiElement().getParent().getParent(); + final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression(); + final PsiExpression qualifier = methodExpression.getQualifierExpression(); + if (qualifier == null) { + replaceExpression(methodCallExpression, "this"); + } else { + methodCallExpression.replace(qualifier); + } + } + } + + @Override + public BaseInspectionVisitor buildVisitor() { + return new UnnecessaryCallToStringValueOfVisitor(); + } + + private static class UnnecessaryCallToStringValueOfVisitor extends BaseInspectionVisitor { + + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + super.visitMethodCallExpression(expression); + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + final String referenceName = methodExpression.getReferenceName(); + if (!"toString".equals(referenceName)) { + return; + } + if (isToStringCallNecessary(expression)) { + return; + } + final PsiExpressionList argumentList = expression.getArgumentList(); + final PsiExpression[] arguments = argumentList.getExpressions(); + if (arguments.length != 0) { + return; + } + final PsiExpression qualifier = methodExpression.getQualifierExpression(); + if (qualifier != null && qualifier.getType() instanceof PsiArrayType) { + // do not warn on nonsensical code + return; + } + registerMethodCallError(expression, calculateReplacementText(qualifier)); + } + + private boolean isToStringCallNecessary(PsiMethodCallExpression expression) { + final PsiElement parent = ParenthesesUtils.getParentSkipParentheses(expression); + if (parent instanceof PsiPolyadicExpression) { + final PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression)parent; + final PsiType type = polyadicExpression.getType(); + if (!TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING, type)) { + return true; + } + final PsiExpression[] operands = polyadicExpression.getOperands(); + int index = -1; + for (int i = 0, length = operands.length; i < length; i++) { + final PsiExpression operand = operands[i]; + if (expression.equals(operand)) { + index = i; + } + } + if (index > 0) { + if (!TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING, operands[index - 1].getType())) { + return true; + } + } else if (operands.length > 1) { + if (!TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_STRING, operands[index + 1].getType())) { + return true; + } + } else { + return true; + } + } else if (parent instanceof PsiExpressionList) { + final PsiExpressionList expressionList = (PsiExpressionList)parent; + final PsiElement grandParent = expressionList.getParent(); + if (!(grandParent instanceof PsiMethodCallExpression)) { + return true; + } + final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)grandParent; + final PsiReferenceExpression methodExpression1 = methodCallExpression.getMethodExpression(); + final String name = methodExpression1.getReferenceName(); + final PsiExpression[] expressions = expressionList.getExpressions(); + if ("insert".equals(name)) { + if (expressions.length < 2 || !expression.equals(ParenthesesUtils.stripParentheses(expressions[1]))) { + return true; + } + if (!isCallToMethodIn(methodCallExpression, "java.lang.StringBuilder", "java.lang.StringBuffer")) { + return true; + } + + } else if ("append".equals(name)) { + if (expressions.length < 1 || !expression.equals(ParenthesesUtils.stripParentheses(expressions[0]))) { + return true; + } + if (!isCallToMethodIn(methodCallExpression, "java.lang.StringBuilder", "java.lang.StringBuffer")) { + return true; + } + } else if ("print".equals(name) || "println".equals(name)) { + if (!isCallToMethodIn(methodCallExpression, "java.io.PrintStream", "java.io.PrintWriter")) { + return true; + } + } + } else { + return true; + } + return false; + } + + private boolean isCallToMethodIn(PsiMethodCallExpression methodCallExpression, String... classNames) { + final PsiMethod method = methodCallExpression.resolveMethod(); + if (method == null) { + return false; + } + final PsiClass containingClass = method.getContainingClass(); + if (containingClass == null) { + return false; + } + final String qualifiedName = containingClass.getQualifiedName(); + for (String className : classNames) { + if (className.equals(qualifiedName)) { + return true; + } + } + return false; + } + } +} diff --git a/plugins/InspectionGadgets/src/inspectionDescriptions/UnnecessaryToStringCall.html b/plugins/InspectionGadgets/src/inspectionDescriptions/UnnecessaryToStringCall.html new file mode 100644 index 000000000000..69cfb6134430 --- /dev/null +++ b/plugins/InspectionGadgets/src/inspectionDescriptions/UnnecessaryToStringCall.html @@ -0,0 +1,13 @@ + + +Reports on any calls to .toString() used in string concatenations and as arguments to the +print and println methods of java.io.PrintWriter and java.io.PrintStream. The conversion +to string will be handled by the underlying library methods without an explicit call to .toString(). +

+Note that without the .toString() the expression will have different semantics (the string "null" will be used instead of + throwing a NullPointerException). + +

+Powered by InspectionGadgets + + \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/style/unnecessary_tostring/UnnecessaryToString.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/style/unnecessary_tostring/UnnecessaryToString.java new file mode 100644 index 000000000000..a77660ab714a --- /dev/null +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/style/unnecessary_tostring/UnnecessaryToString.java @@ -0,0 +1,30 @@ +package com.siyeh.igtest.style.unnecessary_tostring; + +public class UnnecessaryToString { + + String foo(Object o) { + return "star" + o.toString(); + } + + String bar() { + char[] cs = {'!'}; + return "wars" + cs.toString(); + } + + void fizzz(Object o) { + boolean c = true; + System.out.println(o.toString() + c); + } + + void polyadic(Object s) { + s = "abc" + s.toString() + "efg"; + } + + void printStream(Object o) { + System.out.print(o.toString()); + } + + void builder(StringBuilder builder, Object o) { + builder.append(o.toString()); + } +} \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/style/unnecessary_tostring/expected.xml b/plugins/InspectionGadgets/test/com/siyeh/igtest/style/unnecessary_tostring/expected.xml new file mode 100644 index 000000000000..9aea4d234a81 --- /dev/null +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/style/unnecessary_tostring/expected.xml @@ -0,0 +1,32 @@ + + + + + UnnecessaryToString.java + 6 + Unnecessary call to '.toString()' + Unnecessary <code>toString()</code> call #loc + + + + UnnecessaryToString.java + 20 + Unnecessary call to '.toString()' + Unnecessary <code>toString()</code> call #loc + + + + UnnecessaryToString.java + 24 + Unnecessary call to '.toString()' + Unnecessary <code>toString()</code> call #loc + + + + UnnecessaryToString.java + 28 + Unnecessary call to '.toString()' + Unnecessary <code>toString()</code> call #loc + + + \ No newline at end of file diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/style/UnnecessaryToStringCallInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/style/UnnecessaryToStringCallInspectionTest.java new file mode 100644 index 000000000000..797b4273106d --- /dev/null +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/style/UnnecessaryToStringCallInspectionTest.java @@ -0,0 +1,10 @@ +package com.siyeh.ig.style; + +import com.siyeh.ig.IGInspectionTestCase; + +public class UnnecessaryToStringCallInspectionTest extends IGInspectionTestCase { + + public void test() throws Exception { + doTest("com/siyeh/igtest/style/unnecessary_tostring", new UnnecessaryToStringCallInspection()); + } +} \ No newline at end of file