Recursive equals: case with recursive not equals + extra tests

This commit is contained in:
Mikhail Glukhikh
2017-11-28 16:45:24 +03:00
parent 13e0ba0836
commit 0d65df4d3e
9 changed files with 88 additions and 18 deletions
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelectorOrThis
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
@@ -51,26 +52,25 @@ class RecursiveEqualsCallInspection : AbstractKotlinInspection() {
argumentDescriptor == containingFunctionDescriptor.valueParameters.singleOrNull()
}
private fun KtExpression.reportRecursiveEquals() {
private fun KtExpression.reportRecursiveEquals(invert: Boolean = false) {
holder.registerProblem(this,
"Recursive equals call",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
ReplaceWithReferentialEqualityFix())
ReplaceWithReferentialEqualityFix(invert))
}
override fun visitBinaryExpression(expr: KtBinaryExpression) {
super.visitBinaryExpression(expr)
if (expr.operationToken != KtTokens.EQEQ) return
if (expr.operationToken != KtTokens.EQEQ && expr.operationToken != KtTokens.EXCLEQ) return
if (!expr.isRecursiveEquals(expr.right)) return
expr.reportRecursiveEquals()
expr.reportRecursiveEquals(invert = expr.operationToken == KtTokens.EXCLEQ)
}
override fun visitCallExpression(expr: KtCallExpression) {
super.visitCallExpression(expr)
val calleeExpression = expr.calleeExpression as? KtSimpleNameExpression ?: return
if (calleeExpression.getReferencedNameAsName() != OperatorNameConventions.EQUALS) return
if (expr.parent is KtSafeQualifiedExpression) return
if (!expr.isRecursiveEquals(expr.valueArguments.singleOrNull()?.getArgumentExpression())) return
expr.reportRecursiveEquals()
@@ -79,29 +79,26 @@ class RecursiveEqualsCallInspection : AbstractKotlinInspection() {
}
}
private class ReplaceWithReferentialEqualityFix : LocalQuickFix {
override fun getName() = "Replace with '==='"
private class ReplaceWithReferentialEqualityFix(invert: Boolean) : LocalQuickFix {
private val operator = if (invert) "!==" else "==="
override fun getName() = "Replace with '$operator'"
override fun getFamilyName() = name
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val element = descriptor.psiElement
val (right, target) = when (element) {
is KtBinaryExpression -> element.right to element
is KtCallExpression -> {
val parent = element.parent
val argument = element.valueArguments.first().getArgumentExpression()
if (parent is KtDotQualifiedExpression) {
argument to parent as KtExpression
}
else {
argument to element
}
is KtBinaryExpression -> {
element.right to element
}
is KtCallExpression -> with (element ){
valueArguments.first().getArgumentExpression() to getQualifiedExpressionForSelectorOrThis()
}
else -> return
}
if (right == null) return
target.replace(KtPsiFactory(project).createExpressionByPattern("this $0 $1", "===", right))
target.replace(KtPsiFactory(project).createExpressionByPattern("this $0 $1", operator, right))
}
}
@@ -0,0 +1,6 @@
class Test {
override fun equals(other: Any?): Boolean {
if (this?.equals<caret>(other)) return true
return false
}
}
@@ -0,0 +1,6 @@
class Test {
override fun equals(other: Any?): Boolean {
if (this === other) return true
return false
}
}
@@ -0,0 +1,9 @@
// PROBLEM: none
open class Outer {
inner class Nested : Outer() {
override fun equals(other: Any?): Boolean {
if (this@Outer <caret>== other) return true
return false
}
}
}
@@ -0,0 +1,8 @@
open class Outer {
inner class Nested : Outer() {
override fun equals(other: Any?): Boolean {
if (this@Nested <caret>== other) return true
return false
}
}
}
@@ -0,0 +1,8 @@
open class Outer {
inner class Nested : Outer() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
return false
}
}
}
@@ -0,0 +1,6 @@
class Test {
override fun equals(other: Any?): Boolean {
if (<caret>this != other) return false
return true
}
}
@@ -0,0 +1,6 @@
class Test {
override fun equals(other: Any?): Boolean {
if (this !== other) return false
return true
}
}
@@ -1526,12 +1526,24 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
doTest(fileName);
}
@TestMetadata("recursiveDirectSafe.kt")
public void testRecursiveDirectSafe() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirectSafe.kt");
doTest(fileName);
}
@TestMetadata("recursiveFake.kt")
public void testRecursiveFake() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveFake.kt");
doTest(fileName);
}
@TestMetadata("recursiveFakeNested.kt")
public void testRecursiveFakeNested() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveFakeNested.kt");
doTest(fileName);
}
@TestMetadata("recursiveFakeNoOther.kt")
public void testRecursiveFakeNoOther() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveFakeNoOther.kt");
@@ -1543,6 +1555,18 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveImplicit.kt");
doTest(fileName);
}
@TestMetadata("recursiveNested.kt")
public void testRecursiveNested() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveNested.kt");
doTest(fileName);
}
@TestMetadata("recursiveNot.kt")
public void testRecursiveNot() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveNot.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/inspectionsLocal/redundantExplicitType")