Unnecessary boxing: added support for static valueOf import, fixed problem with clashing (IDEA-CR-43627)

This commit is contained in:
Artemiy Sartakov
2019-02-20 16:41:37 +07:00
parent 8aa936dc00
commit 85fc490df4
10 changed files with 119 additions and 71 deletions
@@ -16,6 +16,7 @@ import com.intellij.util.ArrayUtil;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.JavaPsiBoxingUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@@ -28,19 +29,9 @@ public class WrapperTypeMayBePrimitiveInspection extends AbstractBaseJavaLocalIn
private static final CallMatcher HASH_CODE = CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, "hashCode");
private static final CallMatcher VALUE_OF = getValueOfMatcher();
private static final Map<String, String> ourReplacementMap = new HashMap<>();
private static final Set<String> ourAllowedInstanceCalls = new HashSet<>();
static {
ourReplacementMap.put(CommonClassNames.JAVA_LANG_INTEGER, "parseInt");
ourReplacementMap.put(CommonClassNames.JAVA_LANG_LONG, "parseLong");
ourReplacementMap.put(CommonClassNames.JAVA_LANG_FLOAT, "parseFloat");
ourReplacementMap.put(CommonClassNames.JAVA_LANG_BOOLEAN, "parseBoolean");
ourReplacementMap.put(CommonClassNames.JAVA_LANG_DOUBLE, "parseDouble");
ourReplacementMap.put(CommonClassNames.JAVA_LANG_SHORT, "parseShort");
ourReplacementMap.put(CommonClassNames.JAVA_LANG_BYTE, "parseByte");
ourAllowedInstanceCalls.add("isInfinite");
ourAllowedInstanceCalls.add("isNaN");
ourAllowedInstanceCalls.add("byteValue");
@@ -340,7 +331,7 @@ public class WrapperTypeMayBePrimitiveInspection extends AbstractBaseJavaLocalIn
PsiExpression argument = arguments[0];
if (containingClass == null) return;
String containingClassName = containingClass.getQualifiedName();
String replacementMethodCall = ourReplacementMap.get(containingClassName);
String replacementMethodCall = JavaPsiBoxingUtils.getParseMethod(containingClassName);
if (replacementMethodCall == null) return;
CommentTracker tracker = new CommentTracker();
String argumentText = tracker.text(argument);
@@ -1136,7 +1136,6 @@ for.loop.with.missing.component.problem.descriptor6=<code>#ref</code> statement
for.loop.with.missing.component.problem.descriptor7=<code>#ref</code> statement lacks initializer, condition and update #loc
foreach.replace.quickfix=Replace with 'foreach'
unnecessary.boxing.remove.quickfix=Remove boxing
unnecessary.boxing.use.parse.quickfix=Replace with ''{0}'' method call
unnecessary.unboxing.remove.quickfix=Remove unboxing
misordered.assert.equals.arguments.flip.quickfix=Flip compared arguments
simplify.junit.assertion.simplify.quickfix=Simplify assertion
@@ -15,6 +15,7 @@
*/
package com.siyeh.ig.migration;
import com.intellij.codeInspection.CommonQuickFixBundle;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.openapi.project.Project;
@@ -23,7 +24,7 @@ import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.hash.HashMap;
import com.intellij.util.ObjectUtils;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
@@ -35,23 +36,12 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Map;
public class UnnecessaryBoxingInspection extends BaseInspection {
@SuppressWarnings("PublicField")
public boolean onlyReportSuperfluouslyBoxed = false;
private static final Map<PsiType, String> replacementMap = new HashMap<>();
static {
replacementMap.put(PsiType.SHORT, "Short.parseShort");
replacementMap.put(PsiType.INT, "Integer.parseInt");
replacementMap.put(PsiType.LONG, "Long.parseLong");
replacementMap.put(PsiType.DOUBLE, "Double.parseDouble");
replacementMap.put(PsiType.FLOAT, "Float.parseFloat");
}
@Override
@NotNull
public String getDisplayName() {
@@ -92,8 +82,8 @@ public class UnnecessaryBoxingInspection extends BaseInspection {
this.name = InspectionGadgetsBundle.message("unnecessary.boxing.remove.quickfix");
}
private UnnecessaryBoxingFix(PsiType expectedType) {
this.name = InspectionGadgetsBundle.message("unnecessary.boxing.use.parse.quickfix", replacementMap.get(expectedType));
private UnnecessaryBoxingFix(PsiType retType) {
this.name = CommonQuickFixBundle.message("fix.replace.with.x", getParseMethod(retType));
}
@Override
@@ -120,12 +110,12 @@ public class UnnecessaryBoxingInspection extends BaseInspection {
}
final CommentTracker commentTracker = new CommentTracker();
if (unboxedExpressionType.getCanonicalText().equals("java.lang.String")) {
final PsiType expectedType = ExpectedTypeUtils.findExpectedType(expression, false, true);
final String parseMethodName = replacementMap.get(expectedType);
PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expression;
final String parseMethodName = getParseMethod(methodCall.getType());
if (parseMethodName == null) {
return;
}
PsiReplacementUtil.replaceExpression(expression, parseMethodName + "(" + unboxedExpression.getText() + ")", commentTracker);
ExpressionUtils.bindCallTo(methodCall, parseMethodName);
return;
}
final Object value = ExpressionUtils.computeConstantExpression(unboxedExpression);
@@ -256,24 +246,31 @@ public class UnnecessaryBoxingInspection extends BaseInspection {
if (!"valueOf".equals(referenceName)) {
return;
}
final PsiMethod method = ObjectUtils.tryCast(methodExpression.resolve(), PsiMethod.class);
if (method == null) {
return;
}
final PsiClass aClass = method.getContainingClass();
if (aClass == null) {
return;
}
final String canonicalText = aClass.getQualifiedName();
if (PsiTypesUtil.unboxIfPossible(canonicalText) == canonicalText) {
return;
}
final PsiType boxedExpressionType = boxedExpression.getType();
if (boxedExpressionType != null && boxedExpressionType.getCanonicalText().equals("java.lang.String")) {
final PsiType expectedType = ExpectedTypeUtils.findExpectedType(expression, false, true);
if (replacementMap.containsKey(expectedType)) {
registerError(expression, expectedType);
final PsiType methodReturnType = method.getReturnType();
if (expectedType instanceof PsiPrimitiveType && getParseMethod(methodReturnType) != null) {
registerError(expression, methodReturnType);
}
return;
}
if (!(boxedExpressionType instanceof PsiPrimitiveType)) {
return;
}
final PsiExpression qualifierExpression = methodExpression.getQualifierExpression();
if (!(qualifierExpression instanceof PsiReferenceExpression)) {
return;
}
final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)qualifierExpression;
final String canonicalText = referenceExpression.getCanonicalText();
if (PsiTypesUtil.unboxIfPossible(canonicalText) == canonicalText || isBoxingNecessary(expression, boxedExpression)) {
if (isBoxingNecessary(expression, boxedExpression)) {
return;
}
if (onlyReportSuperfluouslyBoxed) {
@@ -352,4 +349,16 @@ public class UnnecessaryBoxingInspection extends BaseInspection {
return false;
}
}
@Nullable
private static String getParseMethod(@Nullable PsiType type) {
if (type == null) {
return null;
}
final String typeText = type.getCanonicalText();
if (CommonClassNames.JAVA_LANG_BOOLEAN.equals(typeText) || CommonClassNames.JAVA_LANG_BYTE.equals(typeText)) {
return null;
}
return JavaPsiBoxingUtils.getParseMethod(typeText);
}
}
@@ -27,6 +27,7 @@ import com.siyeh.ig.BaseInspectionVisitor;
import com.siyeh.ig.InspectionGadgetsFix;
import com.siyeh.ig.PsiReplacementUtil;
import com.siyeh.ig.psiutils.CommentTracker;
import com.siyeh.ig.psiutils.JavaPsiBoxingUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -38,21 +39,6 @@ import java.util.Map;
public class UnnecessaryTemporaryOnConversionFromStringInspection
extends BaseInspection {
/**
*/
@NonNls private static final Map<String, String> s_conversionMap =
new HashMap<>(7);
static {
s_conversionMap.put(CommonClassNames.JAVA_LANG_BOOLEAN, "valueOf");
s_conversionMap.put(CommonClassNames.JAVA_LANG_BYTE, "parseByte");
s_conversionMap.put(CommonClassNames.JAVA_LANG_DOUBLE, "parseDouble");
s_conversionMap.put(CommonClassNames.JAVA_LANG_FLOAT, "parseFloat");
s_conversionMap.put(CommonClassNames.JAVA_LANG_INTEGER, "parseInt");
s_conversionMap.put(CommonClassNames.JAVA_LANG_LONG, "parseLong");
s_conversionMap.put(CommonClassNames.JAVA_LANG_SHORT, "parseShort");
}
@Override
@NotNull
public String getDisplayName() {
@@ -68,7 +54,7 @@ public class UnnecessaryTemporaryOnConversionFromStringInspection
@Override
@NotNull
public String buildErrorString(Object... infos) {
final String replacementString = calculateReplacementExpression((PsiMethodCallExpression)infos[0], new CommentTracker());
final String replacementString = calculateReplacementExpression((PsiMethodCallExpression)infos[0], new CommentTracker(), false);
return InspectionGadgetsBundle.message(
"unnecessary.temporary.on.conversion.from.string.problem.descriptor",
replacementString);
@@ -76,7 +62,9 @@ public class UnnecessaryTemporaryOnConversionFromStringInspection
@Nullable
@NonNls
static String calculateReplacementExpression(PsiMethodCallExpression expression, CommentTracker commentTracker) {
static String calculateReplacementExpression(PsiMethodCallExpression expression,
CommentTracker commentTracker,
boolean isFullyQualified) {
final PsiReferenceExpression methodExpression = expression.getMethodExpression();
final PsiNewExpression qualifier = ObjectUtils.tryCast(methodExpression.getQualifierExpression(), PsiNewExpression.class);
if (qualifier == null) return null;
@@ -87,24 +75,18 @@ public class UnnecessaryTemporaryOnConversionFromStringInspection
if (type == null) return null;
final String qualifierType = type.getPresentableText();
final String canonicalType = type.getCanonicalText();
final String conversionName = s_conversionMap.get(canonicalType);
if (TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_BOOLEAN, type)) {
if (!PsiUtil.isLanguageLevel5OrHigher(expression)) {
return qualifierType + '.' + conversionName + '(' + commentTracker.text(arg) + ").booleanValue()";
}
else {
return qualifierType + ".parseBoolean(" + commentTracker.text(arg) + ')';
}
}
else {
return qualifierType + '.' + conversionName + '(' + commentTracker.text(arg) + ')';
final String name = isFullyQualified ? canonicalType : qualifierType;
final String conversionName = JavaPsiBoxingUtils.getParseMethod(canonicalType);
if (TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_BOOLEAN, type) && !PsiUtil.isLanguageLevel5OrHigher(expression)) {
return name + '.' + "valueOf" + '(' + commentTracker.text(arg) + ").booleanValue()";
}
return name + '.' + conversionName + '(' + commentTracker.text(arg) + ')';
}
@Override
@Nullable
public InspectionGadgetsFix buildFix(Object... infos) {
final String replacementExpression = calculateReplacementExpression((PsiMethodCallExpression)infos[0], new CommentTracker());
final String replacementExpression = calculateReplacementExpression((PsiMethodCallExpression)infos[0], new CommentTracker(), false);
if (replacementExpression == null) return null;
final String name = CommonQuickFixBundle.message("fix.replace.with.x", replacementExpression);
return new UnnecessaryTemporaryObjectFix(name);
@@ -134,8 +116,8 @@ public class UnnecessaryTemporaryOnConversionFromStringInspection
@Override
public void doFix(Project project, ProblemDescriptor descriptor) {
final PsiMethodCallExpression expression = (PsiMethodCallExpression)descriptor.getPsiElement();
CommentTracker commentTracker = new CommentTracker();
final String newExpression = calculateReplacementExpression(expression, commentTracker);
final CommentTracker commentTracker = new CommentTracker();
final String newExpression = calculateReplacementExpression(expression, commentTracker, true);
if (newExpression == null) return;
PsiReplacementUtil.replaceExpression(expression, newExpression, commentTracker);
}
@@ -0,0 +1,31 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.siyeh.ig.psiutils;
import com.intellij.psi.CommonClassNames;
import com.intellij.util.containers.hash.HashMap;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
public class JavaPsiBoxingUtils {
private static final Map<String, String> parseMethodsMap = new HashMap<>();
static {
parseMethodsMap.put(CommonClassNames.JAVA_LANG_INTEGER, "parseInt");
parseMethodsMap.put(CommonClassNames.JAVA_LANG_LONG, "parseLong");
parseMethodsMap.put(CommonClassNames.JAVA_LANG_FLOAT, "parseFloat");
parseMethodsMap.put(CommonClassNames.JAVA_LANG_BOOLEAN, "parseBoolean");
parseMethodsMap.put(CommonClassNames.JAVA_LANG_DOUBLE, "parseDouble");
parseMethodsMap.put(CommonClassNames.JAVA_LANG_SHORT, "parseShort");
parseMethodsMap.put(CommonClassNames.JAVA_LANG_BYTE, "parseByte");
}
/**
* Get parse method name without qualifier for given full class name.
*/
@Nullable
public static String getParseMethod(@Nullable String className) {
return parseMethodsMap.get(className);
}
}
@@ -0,0 +1,13 @@
package foo.bar;
class Test {
void test(String str) {
int i = Integer.value<caret>Of(str);
}
}
class Integer {
static int valueOf(String str) {
return 42;
}
}
@@ -0,0 +1,7 @@
import static java.lang.Integer.valueOf;
class Test {
void test(String str) {
int i = Integer.parseInt(str);
}
}
@@ -0,0 +1,7 @@
import static java.lang.Integer.valueOf;
class Test {
void test(String str) {
int i = valueO<caret>f(str);
}
}
@@ -1,6 +1,6 @@
class Test {
void test(String s) {
/*hello*/
int x = Integer.parseInt(s +/*double-s*/s<caret>);
int x = Integer.parseInt(s +/*double-s*/s);
}
}
@@ -1,6 +1,7 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.siyeh.ig.fixes.migration;
import com.intellij.codeInspection.CommonQuickFixBundle;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.IGQuickFixesTestCase;
import com.siyeh.ig.migration.UnnecessaryBoxingInspection;
@@ -89,7 +90,15 @@ public class UnnecessaryBoxingFixTest extends IGQuickFixesTestCase {
}
public void testParseInt() {
doTest(getTestName(false), InspectionGadgetsBundle.message("unnecessary.boxing.use.parse.quickfix", "Integer.parseInt"));
doTest(CommonQuickFixBundle.message("fix.replace.with.x", "parseInt"));
}
public void testStaticImport() {
doTest(CommonQuickFixBundle.message("fix.replace.with.x", "parseInt"));
}
public void testShadowImport() {
assertQuickfixNotAvailable("Fix all 'Unnecessary boxing' problems in file");
}
private void doFixTest() {