Recursive equals: case with recursive not equals + extra tests
This commit is contained in:
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
|
|||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
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.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
@@ -51,26 +52,25 @@ class RecursiveEqualsCallInspection : AbstractKotlinInspection() {
|
|||||||
argumentDescriptor == containingFunctionDescriptor.valueParameters.singleOrNull()
|
argumentDescriptor == containingFunctionDescriptor.valueParameters.singleOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KtExpression.reportRecursiveEquals() {
|
private fun KtExpression.reportRecursiveEquals(invert: Boolean = false) {
|
||||||
holder.registerProblem(this,
|
holder.registerProblem(this,
|
||||||
"Recursive equals call",
|
"Recursive equals call",
|
||||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
|
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
|
||||||
ReplaceWithReferentialEqualityFix())
|
ReplaceWithReferentialEqualityFix(invert))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitBinaryExpression(expr: KtBinaryExpression) {
|
override fun visitBinaryExpression(expr: KtBinaryExpression) {
|
||||||
super.visitBinaryExpression(expr)
|
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
|
if (!expr.isRecursiveEquals(expr.right)) return
|
||||||
expr.reportRecursiveEquals()
|
expr.reportRecursiveEquals(invert = expr.operationToken == KtTokens.EXCLEQ)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitCallExpression(expr: KtCallExpression) {
|
override fun visitCallExpression(expr: KtCallExpression) {
|
||||||
super.visitCallExpression(expr)
|
super.visitCallExpression(expr)
|
||||||
val calleeExpression = expr.calleeExpression as? KtSimpleNameExpression ?: return
|
val calleeExpression = expr.calleeExpression as? KtSimpleNameExpression ?: return
|
||||||
if (calleeExpression.getReferencedNameAsName() != OperatorNameConventions.EQUALS) return
|
if (calleeExpression.getReferencedNameAsName() != OperatorNameConventions.EQUALS) return
|
||||||
if (expr.parent is KtSafeQualifiedExpression) return
|
|
||||||
|
|
||||||
if (!expr.isRecursiveEquals(expr.valueArguments.singleOrNull()?.getArgumentExpression())) return
|
if (!expr.isRecursiveEquals(expr.valueArguments.singleOrNull()?.getArgumentExpression())) return
|
||||||
expr.reportRecursiveEquals()
|
expr.reportRecursiveEquals()
|
||||||
@@ -79,29 +79,26 @@ class RecursiveEqualsCallInspection : AbstractKotlinInspection() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ReplaceWithReferentialEqualityFix : LocalQuickFix {
|
private class ReplaceWithReferentialEqualityFix(invert: Boolean) : LocalQuickFix {
|
||||||
override fun getName() = "Replace with '==='"
|
private val operator = if (invert) "!==" else "==="
|
||||||
|
|
||||||
|
override fun getName() = "Replace with '$operator'"
|
||||||
|
|
||||||
override fun getFamilyName() = name
|
override fun getFamilyName() = name
|
||||||
|
|
||||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||||
val element = descriptor.psiElement
|
val element = descriptor.psiElement
|
||||||
val (right, target) = when (element) {
|
val (right, target) = when (element) {
|
||||||
is KtBinaryExpression -> element.right to element
|
is KtBinaryExpression -> {
|
||||||
is KtCallExpression -> {
|
element.right to element
|
||||||
val parent = element.parent
|
}
|
||||||
val argument = element.valueArguments.first().getArgumentExpression()
|
is KtCallExpression -> with (element ){
|
||||||
if (parent is KtDotQualifiedExpression) {
|
valueArguments.first().getArgumentExpression() to getQualifiedExpressionForSelectorOrThis()
|
||||||
argument to parent as KtExpression
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
argument to element
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else -> return
|
else -> return
|
||||||
}
|
}
|
||||||
if (right == null) 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
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -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);
|
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")
|
@TestMetadata("recursiveFake.kt")
|
||||||
public void testRecursiveFake() throws Exception {
|
public void testRecursiveFake() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveFake.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveFake.kt");
|
||||||
doTest(fileName);
|
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")
|
@TestMetadata("recursiveFakeNoOther.kt")
|
||||||
public void testRecursiveFakeNoOther() throws Exception {
|
public void testRecursiveFakeNoOther() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveFakeNoOther.kt");
|
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");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/recursiveEqualsCall/recursiveImplicit.kt");
|
||||||
doTest(fileName);
|
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")
|
@TestMetadata("idea/testData/inspectionsLocal/redundantExplicitType")
|
||||||
|
|||||||
Reference in New Issue
Block a user