diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml index afd0b80d0cc4..21ba183afedc 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml @@ -1283,6 +1283,10 @@ key="bigdecimal.legacy.method.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.language.level.specific.issues.and.migration.aids" enabledByDefault="false" level="WARNING" implementationClass="com.siyeh.ig.migration.BigDecimalLegacyMethodInspection"/> + #ref dangling.javadoc.display.name=Dangling Javadoc comment dangling.javadoc.problem.descriptor=Dangling Javadoc comment #loc dangling.javadoc.convert.quickfix=Replace with block comment -dangling.javadoc.delete.quickfix=Remove dangling comment \ No newline at end of file +dangling.javadoc.delete.quickfix=Remove dangling comment +equals.replaceable.by.objects.call.display.name='equals()' expression replaceable by 'Objects.equals()' expression +equals.replaceable.by.objects.call.problem.descriptor=#ref replaceable by 'Objects.equals()' expression #loc +equals.replaceable.by.objects.call.quickfix=Replace with 'Objects.equals()' expression \ No newline at end of file diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/migration/EqualsReplaceableByObjectsCallInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/migration/EqualsReplaceableByObjectsCallInspection.java new file mode 100644 index 000000000000..75b59118fee7 --- /dev/null +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/migration/EqualsReplaceableByObjectsCallInspection.java @@ -0,0 +1,209 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * 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.migration; + +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.siyeh.HardcodedMethodConstants; +import com.siyeh.InspectionGadgetsBundle; +import com.siyeh.ig.BaseInspection; +import com.siyeh.ig.BaseInspectionVisitor; +import com.siyeh.ig.InspectionGadgetsFix; +import com.siyeh.ig.PsiReplacementUtil; +import com.siyeh.ig.psiutils.ExpressionUtils; +import com.siyeh.ig.psiutils.ParenthesesUtils; +import com.siyeh.ig.psiutils.VariableAccessUtils; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Bas Leijdekkers + */ +public class EqualsReplaceableByObjectsCallInspection extends BaseInspection { + + @Nls + @NotNull + @Override + public String getDisplayName() { + return InspectionGadgetsBundle.message("equals.replaceable.by.objects.call.display.name"); + } + + @NotNull + @Override + protected String buildErrorString(Object... infos) { + return InspectionGadgetsBundle.message("equals.replaceable.by.objects.call.problem.descriptor"); + } + + @Nullable + @Override + protected InspectionGadgetsFix buildFix(Object... infos) { + return new EqualsReplaceableByObjectsCallFix((String)infos[0], (String)infos[1], (Boolean)infos[2]); + } + + private static class EqualsReplaceableByObjectsCallFix extends InspectionGadgetsFix { + + private final String myName1; + private final String myName2; + private final Boolean myEquals; + + public EqualsReplaceableByObjectsCallFix(String name1, String name2, Boolean equals) { + myName1 = name1; + myName2 = name2; + myEquals = equals; + } + + @Nls + @NotNull + @Override + public String getName() { + return InspectionGadgetsBundle.message("equals.replaceable.by.objects.call.quickfix"); + } + + @NotNull + @Override + public String getFamilyName() { + return getName(); + } + + @Override + protected void doFix(Project project, ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + if (!(element instanceof PsiBinaryExpression)) { + return; + } + final PsiBinaryExpression expression = (PsiBinaryExpression)element; + if (myEquals) { + PsiReplacementUtil.replaceExpressionAndShorten(expression, "java.util.Objects.equals(" + myName1 + "," + myName2 + ")"); + } + else { + PsiReplacementUtil.replaceExpressionAndShorten(expression, "!java.util.Objects.equals(" + myName1 + "," + myName2 + ")"); + } + } + } + + @Override + public boolean shouldInspect(PsiFile file) { + return PsiUtil.isLanguageLevel7OrHigher(file); + } + + @Override + public BaseInspectionVisitor buildVisitor() { + return new EqualsReplaceableByObjectsCallVisitor(); + } + + private static class EqualsReplaceableByObjectsCallVisitor extends BaseInspectionVisitor { + + @Override + public void visitBinaryExpression(PsiBinaryExpression expression) { + final IElementType tokenType = expression.getOperationTokenType(); + if (JavaTokenType.ANDAND.equals(tokenType)) { + final PsiVariable variable = ExpressionUtils.getVariableFromNullComparison(expression.getLOperand(), false); + if (variable == null) { + return; + } + final PsiVariable otherVariable = getArgumentFromEqualsCallOn(expression.getROperand(), variable); + if (otherVariable == null) { + return; + } + checkEqualityBefore(expression, true, variable, otherVariable); + } + else if (JavaTokenType.OROR.equals(tokenType)) { + final PsiVariable variable = ExpressionUtils.getVariableFromNullComparison(expression.getLOperand(), true); + if (variable == null) { + return; + } + final PsiExpression rhs = ParenthesesUtils.stripParentheses(expression.getROperand()); + if (!(rhs instanceof PsiPrefixExpression)) { + return; + } + final PsiPrefixExpression prefixExpression = (PsiPrefixExpression)rhs; + if (!JavaTokenType.EXCL.equals(prefixExpression.getOperationTokenType())) { + return; + } + final PsiVariable otherVariable = getArgumentFromEqualsCallOn(prefixExpression.getOperand(), variable); + if (otherVariable == null) { + return; + } + checkEqualityBefore(expression, false, variable, otherVariable); + } + } + + private void checkEqualityBefore(PsiExpression expression, boolean equals, PsiVariable variable1, PsiVariable variable2) { + final PsiElement parent = PsiTreeUtil.skipParentsOfType(expression, PsiParenthesizedExpression.class); + if (parent instanceof PsiBinaryExpression) { + final PsiBinaryExpression binaryExpression = (PsiBinaryExpression)parent; + if (PsiTreeUtil.isAncestor(binaryExpression.getROperand(), expression, false)) { + final PsiExpression lhs = binaryExpression.getLOperand(); + if (isEquality(lhs, equals, variable1, variable2)) { + registerError(binaryExpression, variable1.getName(), variable2.getName(), Boolean.valueOf(equals)); + return; + } + } + } + registerError(expression, variable1.getName(), variable2.getName(), Boolean.valueOf(equals)); + } + + private static boolean isEquality(PsiExpression expression, boolean equals, PsiVariable variable1, PsiVariable variable2) { + expression = ParenthesesUtils.stripParentheses(expression); + if (!(expression instanceof PsiBinaryExpression)) { + return false; + } + final PsiBinaryExpression binaryExpression = (PsiBinaryExpression)expression; + if (equals) { + if (!JavaTokenType.EQEQ.equals(binaryExpression.getOperationTokenType())) { + return false; + } + } + else { + if (!JavaTokenType.NE.equals(binaryExpression.getOperationTokenType())) { + return false; + } + } + final PsiExpression lhs = binaryExpression.getLOperand(); + final PsiExpression rhs = binaryExpression.getROperand(); + return (VariableAccessUtils.evaluatesToVariable(lhs, variable1) && VariableAccessUtils.evaluatesToVariable(rhs, variable2)) || + (VariableAccessUtils.evaluatesToVariable(lhs, variable2) && VariableAccessUtils.evaluatesToVariable(rhs, variable1)); + } + + private static PsiVariable getArgumentFromEqualsCallOn(PsiExpression expression, @NotNull PsiVariable variable) { + expression = ParenthesesUtils.stripParentheses(expression); + if (!(expression instanceof PsiMethodCallExpression)) { + return null; + } + final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression; + final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression(); + final String methodName = methodExpression.getReferenceName(); + if (!HardcodedMethodConstants.EQUALS.equals(methodName)) { + return null; + } + final PsiExpression qualifier = methodExpression.getQualifierExpression(); + if (!VariableAccessUtils.evaluatesToVariable(qualifier, variable)) { + return null; + } + final PsiExpressionList argumentList = methodCallExpression.getArgumentList(); + final PsiExpression[] expressions = argumentList.getExpressions(); + if (expressions.length != 1) { + return null; + } + return ExpressionUtils.getVariable(expressions[0]); + } + } +} diff --git a/plugins/InspectionGadgets/src/inspectionDescriptions/EqualsReplaceableByObjectsCall.html b/plugins/InspectionGadgets/src/inspectionDescriptions/EqualsReplaceableByObjectsCall.html new file mode 100644 index 000000000000..2bb4e0d4c175 --- /dev/null +++ b/plugins/InspectionGadgets/src/inspectionDescriptions/EqualsReplaceableByObjectsCall.html @@ -0,0 +1,12 @@ + + +Reports expressions that can be replaced with a call to java.util.Objects.equals(), which is available since JDK 1.7. +For example: +
a != null && a.equals(b)
+ +

+This inspection only reports if the project or module is configured to use a language level of 7 or higher. +

+New in 15 + + \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/migration/equals_replaceable_by_objects_call/EqualsReplaceableByObjectsCall.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/migration/equals_replaceable_by_objects_call/EqualsReplaceableByObjectsCall.java new file mode 100644 index 000000000000..fa187d730513 --- /dev/null +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/migration/equals_replaceable_by_objects_call/EqualsReplaceableByObjectsCall.java @@ -0,0 +1,7 @@ +class EqualsReplaceableByObjectsCall { + void yyy(Object a, Object b) { + boolean c = (a != null) && a.equals(b); + boolean d = (a != b) && (a == null || !a.equals(b)); + boolean e = ((a) == (b)) || ((a) != (null) && (a).equals((b))); + } +} \ No newline at end of file diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/migration/EqualsReplaceableByObjectsCallInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/migration/EqualsReplaceableByObjectsCallInspectionTest.java new file mode 100644 index 000000000000..38e5b432ddbc --- /dev/null +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/migration/EqualsReplaceableByObjectsCallInspectionTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * 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.migration; + +import com.intellij.codeInspection.InspectionProfileEntry; +import com.siyeh.ig.LightInspectionTestCase; +import junit.framework.TestCase; +import org.jetbrains.annotations.Nullable; + +/** + * @author Bas Leijdekkers + */ +public class EqualsReplaceableByObjectsCallInspectionTest extends LightInspectionTestCase { + + public void testEqualsReplaceableByObjectsCall() { + doTest(); + } + + @Nullable + @Override + protected InspectionProfileEntry getInspection() { + return new EqualsReplaceableByObjectsCallInspection(); + } +} \ No newline at end of file