diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/RecursiveEqualsCallInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/RecursiveEqualsCallInspection.kt index 9d87c53eda3..e18311c7ccf 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/RecursiveEqualsCallInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/RecursiveEqualsCallInspection.kt @@ -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)) } } diff --git a/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirectSafe.kt b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirectSafe.kt new file mode 100644 index 00000000000..2f0c9d08ede --- /dev/null +++ b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirectSafe.kt @@ -0,0 +1,6 @@ +class Test { + override fun equals(other: Any?): Boolean { + if (this?.equals(other)) return true + return false + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirectSafe.kt.after b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirectSafe.kt.after new file mode 100644 index 00000000000..03d3e0d76a4 --- /dev/null +++ b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveDirectSafe.kt.after @@ -0,0 +1,6 @@ +class Test { + override fun equals(other: Any?): Boolean { + if (this === other) return true + return false + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveFakeNested.kt b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveFakeNested.kt new file mode 100644 index 00000000000..e1bda8283b0 --- /dev/null +++ b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveFakeNested.kt @@ -0,0 +1,9 @@ +// PROBLEM: none +open class Outer { + inner class Nested : Outer() { + override fun equals(other: Any?): Boolean { + if (this@Outer == other) return true + return false + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveNested.kt b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveNested.kt new file mode 100644 index 00000000000..fbbbbcb61d5 --- /dev/null +++ b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveNested.kt @@ -0,0 +1,8 @@ +open class Outer { + inner class Nested : Outer() { + override fun equals(other: Any?): Boolean { + if (this@Nested == other) return true + return false + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveNested.kt.after b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveNested.kt.after new file mode 100644 index 00000000000..f5f114e2ffa --- /dev/null +++ b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveNested.kt.after @@ -0,0 +1,8 @@ +open class Outer { + inner class Nested : Outer() { + override fun equals(other: Any?): Boolean { + if (this === other) return true + return false + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveNot.kt b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveNot.kt new file mode 100644 index 00000000000..b8d4fae003b --- /dev/null +++ b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveNot.kt @@ -0,0 +1,6 @@ +class Test { + override fun equals(other: Any?): Boolean { + if (this != other) return false + return true + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveNot.kt.after b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveNot.kt.after new file mode 100644 index 00000000000..e6658f3d9d9 --- /dev/null +++ b/idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveNot.kt.after @@ -0,0 +1,6 @@ +class Test { + override fun equals(other: Any?): Boolean { + if (this !== other) return false + return true + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index e9eed83d265..3edf3197512 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -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")