IDEA-182004 Collection.containsAll() can be simplified in certain cases

RedundantCollectionOperation: strings externalized, description fixed
This commit is contained in:
Tagir Valeev
2017-11-24 10:40:51 +07:00
parent c0b3ec0590
commit e71ffcdb58
8 changed files with 166 additions and 66 deletions
@@ -2,6 +2,6 @@
<body>
Reports unnecessarily complex collection operations which have simpler alternatives.
<!-- tooltip end -->
<small>New in 2018.1</small>
<p><small>New in 2018.1</small></p>
</body>
</html>
@@ -0,0 +1,8 @@
// "Replace with 'contains'" "true"
import java.util.*;
class Test {
void test(List<String> list) {
list.contains("foo");
}
}
@@ -0,0 +1,8 @@
// "Replace with 'Objects.equals'" "true"
import java.util.*;
class Test {
void test() {
final boolean ff = Objects.equals("foo", "bar");
}
}
@@ -0,0 +1,8 @@
// "Replace with 'contains'" "true"
import java.util.*;
class Test {
void test(List<String> list) {
list.con<caret>tainsAll(Collections.singleton("foo"));
}
}
@@ -0,0 +1,8 @@
// "Replace with 'Objects.equals'" "true"
import java.util.*;
class Test {
void test() {
final boolean ff = Collections.singleton("foo").contai<caret>ns("bar");
}
}
@@ -2030,8 +2030,8 @@
<localInspection groupPath="Java" language="JAVA" shortName="RedundantStringOperation" bundle="com.siyeh.InspectionGadgetsBundle"
key="inspection.redundant.string.operation.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.verbose.or.redundant.code.constructs"
enabledByDefault="true" level="WARNING" cleanupTool="true" implementationClass="com.siyeh.ig.redundancy.RedundantStringOperationInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="RedundantCollectionOperation" displayName="Redundant Collection operation"
groupBundle="messages.InspectionsBundle" groupKey="group.names.verbose.or.redundant.code.constructs"
<localInspection groupPath="Java" language="JAVA" shortName="RedundantCollectionOperation" bundle="com.siyeh.InspectionGadgetsBundle"
key="inspection.redundant.collection.operation.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.verbose.or.redundant.code.constructs"
enabledByDefault="true" level="WARNING" cleanupTool="true" implementationClass="com.siyeh.ig.redundancy.RedundantCollectionOperationInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="TailRecursion" bundle="com.siyeh.InspectionGadgetsBundle" key="tail.recursion.display.name"
groupBundle="messages.InspectionsBundle" groupKey="group.names.performance.issues" enabledByDefault="true"
@@ -2242,3 +2242,7 @@ inspection.catch.ignores.exception.used.message='catch' parameter named <code>#r
inspection.catch.ignores.exception.empty.message=Empty <code>#ref</code> block #loc
inspection.catch.ignores.exception.unused.message=Unused 'catch' parameter <code>#ref</code> #loc
inspection.catch.ignores.exception.vm.ignored.message=Some important exceptions might be ignored in a <code>#ref</code> block #loc
inspection.redundant.collection.operation.display.name=Redundant Collection operation
inspection.redundant.collection.operation.fix.family.name=Simplify collection operation
inspection.redundant.collection.operation.problem.arraycopy=Unnecessary collection created to copy an array
@@ -5,9 +5,11 @@ import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.codeInspection.*;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.callMatcher.CallMapper;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.CommentTracker;
@@ -17,8 +19,6 @@ import com.siyeh.ig.psiutils.ParenthesesUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
import static com.intellij.util.ObjectUtils.tryCast;
import static com.siyeh.ig.callMatcher.CallMatcher.*;
@@ -31,10 +31,21 @@ public class RedundantCollectionOperationInspection extends AbstractBaseJavaLoca
instanceCall(CommonClassNames.JAVA_UTIL_LIST, "subList").parameterTypes("int", "int");
private static final CallMatcher AS_LIST =
staticCall(CommonClassNames.JAVA_UTIL_ARRAYS, "asList").parameterCount(1);
private static final CallMatcher SINGLETON =
anyOf(
staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "singleton", "singletonList").parameterCount(1),
staticCall(CommonClassNames.JAVA_UTIL_LIST, "of").parameterTypes("E"),
staticCall(CommonClassNames.JAVA_UTIL_SET, "of").parameterTypes("E"));
private static final CallMatcher CONTAINS_ALL =
instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, "containsAll").parameterTypes(CommonClassNames.JAVA_UTIL_COLLECTION);
private static final CallMatcher CONTAINS =
instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, "contains").parameterTypes(CommonClassNames.JAVA_LANG_OBJECT);
private static final CallMapper<RedundantCollectionOperationHandler> HANDLERS =
new CallMapper<RedundantCollectionOperationHandler>()
.register(TO_ARRAY, SimplifyToArrayHandler.handler());
.register(TO_ARRAY, AsListToArrayHandler::handler)
.register(CONTAINS_ALL, ContainsAllSingletonHandler::handler)
.register(CONTAINS, SingletonContainsHandler::handler);
@NotNull
@Override
@@ -55,15 +66,16 @@ public class RedundantCollectionOperationInspection extends AbstractBaseJavaLoca
}
interface RedundantCollectionOperationHandler {
default String getProblemName() {
return InspectionGadgetsBundle.message("expression.can.be.replaced.problem.descriptor", getReplacement());
}
String getProblemName();
String getFixName();
String getReplacement();
void performFix(@NotNull Project project, @NotNull PsiMethodCallExpression call);
}
private static class SimplifyToArrayHandler implements RedundantCollectionOperationHandler {
private static class AsListToArrayHandler implements RedundantCollectionOperationHandler {
private final String myReplacementMethod;
@NotNull private final SmartPsiElementPointer<PsiExpression> myArrayPtr;
private final SmartPsiElementPointer<PsiExpression> myFromPtr;
@@ -71,11 +83,11 @@ public class RedundantCollectionOperationInspection extends AbstractBaseJavaLoca
@NotNull private final String mySourceComponentType;
@NotNull private final String myTargetComponentType;
private SimplifyToArrayHandler(PsiExpression from,
PsiExpression to,
@NotNull PsiExpression array,
@NotNull String sourceComponentType,
@NotNull String targetComponentType) {
private AsListToArrayHandler(PsiExpression from,
PsiExpression to,
@NotNull PsiExpression array,
@NotNull String sourceComponentType,
@NotNull String targetComponentType) {
SmartPointerManager manager = SmartPointerManager.getInstance(array.getProject());
myArrayPtr = manager.createSmartPsiElementPointer(array);
myFromPtr = from == null ? null : manager.createSmartPsiElementPointer(from);
@@ -95,12 +107,12 @@ public class RedundantCollectionOperationInspection extends AbstractBaseJavaLoca
@Override
public String getProblemName() {
return "Unnecessary collection created to copy an array";
return InspectionGadgetsBundle.message("inspection.redundant.collection.operation.problem.arraycopy");
}
@Override
public String getFixName() {
return "Replace with '" + myReplacementMethod + "'";
public String getReplacement() {
return myReplacementMethod;
}
@Override
@@ -129,54 +141,106 @@ public class RedundantCollectionOperationInspection extends AbstractBaseJavaLoca
ct.replaceAndRestoreComments(call, replacement);
}
public static Function<PsiMethodCallExpression, RedundantCollectionOperationHandler> handler() {
return call -> {
PsiExpression arg = ArrayUtil.getFirstElement(call.getArgumentList().getExpressions());
PsiExpression arrayLength = null;
String targetComponentType;
if (arg != null) {
if (!(arg instanceof PsiNewExpression)) return null;
PsiJavaCodeReferenceElement classRef = ((PsiNewExpression)arg).getClassReference();
if (classRef == null) return null;
targetComponentType = classRef.getQualifiedName();
PsiExpression[] dimensions = ((PsiNewExpression)arg).getArrayDimensions();
if (dimensions.length != 1) return null;
if (!ExpressionUtils.isZero(dimensions[0])) {
arrayLength = dimensions[0];
}
public static RedundantCollectionOperationHandler handler(PsiMethodCallExpression call) {
PsiExpression arg = ArrayUtil.getFirstElement(call.getArgumentList().getExpressions());
PsiExpression arrayLength = null;
String targetComponentType;
if (arg != null) {
if (!(arg instanceof PsiNewExpression)) return null;
PsiJavaCodeReferenceElement classRef = ((PsiNewExpression)arg).getClassReference();
if (classRef == null) return null;
targetComponentType = classRef.getQualifiedName();
PsiExpression[] dimensions = ((PsiNewExpression)arg).getArrayDimensions();
if (dimensions.length != 1) return null;
if (!ExpressionUtils.isZero(dimensions[0])) {
arrayLength = dimensions[0];
}
else {
targetComponentType = CommonClassNames.JAVA_LANG_OBJECT;
}
else {
targetComponentType = CommonClassNames.JAVA_LANG_OBJECT;
}
PsiExpression from = null;
PsiExpression to = null;
PsiMethodCallExpression qualifier = MethodCallUtils.getQualifierMethodCall(call);
if (SUBLIST.test(qualifier)) {
PsiExpression[] subListArgs = qualifier.getArgumentList().getExpressions();
from = subListArgs[0];
to = subListArgs[1];
qualifier = MethodCallUtils.getQualifierMethodCall(qualifier);
}
if (!AS_LIST.test(qualifier) || MethodCallUtils.isVarArgCall(qualifier)) return null;
PsiExpression array = qualifier.getArgumentList().getExpressions()[0];
PsiArrayType sourceArrayType = tryCast(array.getType(), PsiArrayType.class);
if (sourceArrayType == null) return null;
PsiClass componentClass = PsiUtil.resolveClassInClassTypeOnly(sourceArrayType.getComponentType());
if (componentClass == null) return null;
String sourceComponentType = componentClass.getQualifiedName();
if (sourceComponentType == null) return null;
if (from != null && to != null) {
if (arrayLength != null && !ExpressionUtils.isDifference(from, to, arrayLength)) return null;
}
else {
if (!sourceComponentType.equals(targetComponentType)) return null;
if (arrayLength != null) {
PsiExpression arrayFromLength = ExpressionUtils.getArrayFromLengthExpression(arrayLength);
if (arrayFromLength == null || !PsiEquivalenceUtil.areElementsEquivalent(array, arrayFromLength)) return null;
}
PsiExpression from = null;
PsiExpression to = null;
PsiMethodCallExpression qualifier = MethodCallUtils.getQualifierMethodCall(call);
if (SUBLIST.test(qualifier)) {
PsiExpression[] subListArgs = qualifier.getArgumentList().getExpressions();
from = subListArgs[0];
to = subListArgs[1];
qualifier = MethodCallUtils.getQualifierMethodCall(qualifier);
}
if (!AS_LIST.test(qualifier) || MethodCallUtils.isVarArgCall(qualifier)) return null;
PsiExpression array = qualifier.getArgumentList().getExpressions()[0];
PsiArrayType sourceArrayType = tryCast(array.getType(), PsiArrayType.class);
if (sourceArrayType == null) return null;
PsiClass componentClass = PsiUtil.resolveClassInClassTypeOnly(sourceArrayType.getComponentType());
if (componentClass == null) return null;
String sourceComponentType = componentClass.getQualifiedName();
if (sourceComponentType == null) return null;
if (from != null && to != null) {
if (arrayLength != null && !ExpressionUtils.isDifference(from, to, arrayLength)) return null;
}
else {
if (!sourceComponentType.equals(targetComponentType)) return null;
if (arrayLength != null) {
PsiExpression arrayFromLength = ExpressionUtils.getArrayFromLengthExpression(arrayLength);
if (arrayFromLength == null || !PsiEquivalenceUtil.areElementsEquivalent(array, arrayFromLength)) return null;
}
}
return new SimplifyToArrayHandler(from, to, array, sourceComponentType, targetComponentType);
};
}
return new AsListToArrayHandler(from, to, array, sourceComponentType, targetComponentType);
}
}
private static class ContainsAllSingletonHandler implements RedundantCollectionOperationHandler {
@Override
public String getReplacement() {
return "contains";
}
@Override
public void performFix(@NotNull Project project, @NotNull PsiMethodCallExpression call) {
PsiExpression arg = ArrayUtil.getFirstElement(call.getArgumentList().getExpressions());
if (arg == null) return;
PsiMethodCallExpression singleton = tryCast(PsiUtil.skipParenthesizedExprDown(arg), PsiMethodCallExpression.class);
if (singleton == null) return;
PsiExpression singletonArg = ArrayUtil.getFirstElement(singleton.getArgumentList().getExpressions());
if (singletonArg == null) return;
ExpressionUtils.bindCallTo(call, "contains");
CommentTracker ct = new CommentTracker();
ct.replaceAndRestoreComments(arg, ct.markUnchanged(singletonArg));
}
public static RedundantCollectionOperationHandler handler(PsiMethodCallExpression call) {
PsiExpression containsAllArg = call.getArgumentList().getExpressions()[0];
PsiMethodCallExpression maybeSingleton = tryCast(PsiUtil.skipParenthesizedExprDown(containsAllArg), PsiMethodCallExpression.class);
if (!SINGLETON.test(maybeSingleton)) return null;
return new ContainsAllSingletonHandler();
}
}
private static class SingletonContainsHandler implements RedundantCollectionOperationHandler {
@Override
public String getReplacement() {
return "Objects.equals";
}
@Override
public void performFix(@NotNull Project project, @NotNull PsiMethodCallExpression call) {
PsiMethodCallExpression qualifierCall = MethodCallUtils.getQualifierMethodCall(call);
if (qualifierCall == null) return;
PsiExpression left = ArrayUtil.getFirstElement(qualifierCall.getArgumentList().getExpressions());
PsiExpression right = ArrayUtil.getFirstElement(call.getArgumentList().getExpressions());
if (left == null || right == null) return;
CommentTracker ct = new CommentTracker();
PsiElement element =
ct.replaceAndRestoreComments(call, CommonClassNames.JAVA_UTIL_OBJECTS + ".equals(" + ct.text(left) + "," + ct.text(right) + ")");
JavaCodeStyleManager.getInstance(project).shortenClassReferences(element);
}
static RedundantCollectionOperationHandler handler(PsiMethodCallExpression call) {
if(!PsiUtil.isLanguageLevel7OrHigher(call)) return null;
PsiMethodCallExpression qualifierCall = MethodCallUtils.getQualifierMethodCall(call);
if (!SINGLETON.test(qualifierCall)) return null;
return new SingletonContainsHandler();
}
}
@@ -190,14 +254,14 @@ public class RedundantCollectionOperationInspection extends AbstractBaseJavaLoca
@NotNull
@Override
public String getName() {
return myHandler.getFixName();
return InspectionGadgetsBundle.message("replace.with", myHandler.getReplacement());
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return "Simplify collection operation";
return InspectionGadgetsBundle.message("inspection.redundant.collection.operation.fix.family.name");
}
@Override