Add "'equals()' between objects of inconvertible types" inspection
#KT-25006 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
a1979677d0
commit
a2205cfc98
@@ -3389,6 +3389,15 @@
|
||||
language="kotlin"
|
||||
/>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.KotlinEqualsBetweenInconvertibleTypesInspection"
|
||||
displayName="'equals()' between objects of inconvertible types"
|
||||
groupPath="Kotlin"
|
||||
groupName="Probable bugs"
|
||||
enabledByDefault="true"
|
||||
level="WARNING"
|
||||
language="kotlin"
|
||||
/>
|
||||
|
||||
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||
|
||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<html>
|
||||
<body>
|
||||
This inspection reports <b>equals()</b> between objects of inconvertible primitive / enum / string types.
|
||||
</body>
|
||||
</html>
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.inspections
|
||||
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||
import org.jetbrains.kotlin.psi.callExpressionVisitor
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.isEnum
|
||||
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
|
||||
class KotlinEqualsBetweenInconvertibleTypesInspection : AbstractKotlinInspection() {
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = callExpressionVisitor(fun(call) {
|
||||
val callee = call.calleeExpression as? KtSimpleNameExpression ?: return
|
||||
val identifier = callee.getReferencedNameAsName()
|
||||
if (identifier != OperatorNameConventions.EQUALS) return
|
||||
val receiver = call.getQualifiedExpressionForSelector()?.receiverExpression ?: return
|
||||
val argument = call.valueArguments.singleOrNull()?.getArgumentExpression() ?: return
|
||||
if (call.analyze(BodyResolveMode.PARTIAL).isInconvertibleTypes(receiver, argument)) {
|
||||
holder.registerProblem(callee, "'equals()' between objects of inconvertible types")
|
||||
}
|
||||
})
|
||||
|
||||
companion object {
|
||||
fun BindingContext.isInconvertibleTypes(expr1: KtExpression?, expr2: KtExpression?): Boolean {
|
||||
val type1 = expr1?.getTargetType(this) ?: return false
|
||||
val type2 = expr2?.getTargetType(this) ?: return false
|
||||
return type1 != type2
|
||||
}
|
||||
|
||||
private fun KtExpression.getTargetType(context: BindingContext): KotlinType? {
|
||||
return context.getType(this)?.takeIf {
|
||||
KotlinBuiltIns.isPrimitiveTypeOrNullablePrimitiveType(it) || KotlinBuiltIns.isStringOrNullableString(it) || it.isEnum()
|
||||
}?.makeNotNullable()
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.inspections.AbstractApplicabilityBasedInspection
|
||||
import org.jetbrains.kotlin.idea.inspections.KotlinEqualsBetweenInconvertibleTypesInspection
|
||||
import org.jetbrains.kotlin.idea.intentions.callExpression
|
||||
import org.jetbrains.kotlin.idea.intentions.conventionNameCalls.isAnyEquals
|
||||
import org.jetbrains.kotlin.idea.intentions.isOperatorOrCompatible
|
||||
@@ -155,6 +156,11 @@ class ReplaceCallWithBinaryOperatorInspection : AbstractApplicabilityBasedInspec
|
||||
return when (identifier) {
|
||||
OperatorNameConventions.EQUALS -> {
|
||||
if (!dotQualified.isAnyEquals()) return null
|
||||
with(KotlinEqualsBetweenInconvertibleTypesInspection) {
|
||||
val receiver = dotQualified.receiverExpression
|
||||
val argument = dotQualified.callExpression?.valueArguments?.singleOrNull()?.getArgumentExpression()
|
||||
if (dotQualified.analyze(BodyResolveMode.PARTIAL).isInconvertibleTypes(receiver, argument)) return null
|
||||
}
|
||||
val prefixExpression = dotQualified.getWrappingPrefixExpressionIfAny()
|
||||
if (prefixExpression != null && prefixExpression.operationToken == KtTokens.EXCL) KtTokens.EXCLEQ
|
||||
else KtTokens.EQEQ
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
// PROBLEM: none
|
||||
|
||||
val x = 2.<caret>equals("")
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.inspections.KotlinEqualsBetweenInconvertibleTypesInspection
|
||||
@@ -0,0 +1,8 @@
|
||||
// PROBLEM: none
|
||||
|
||||
fun test(x: E1, y: E1): Boolean {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
|
||||
enum class E1
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// FIX: none
|
||||
|
||||
fun test(x: E1, y: E2): Boolean {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
|
||||
enum class E1
|
||||
|
||||
enum class E2
|
||||
@@ -0,0 +1,7 @@
|
||||
// FIX: none
|
||||
|
||||
fun test(x: E1, y: Int): Boolean {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
|
||||
enum class E1
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// FIX: none
|
||||
|
||||
fun test(x: E1, y: String): Boolean {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
|
||||
enum class E1
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// PROBLEM: none
|
||||
|
||||
fun test(x: E1, y: Foo): Boolean {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
|
||||
class Foo
|
||||
|
||||
enum class E1
|
||||
@@ -0,0 +1,7 @@
|
||||
// FIX: none
|
||||
|
||||
fun test(x: Int, y: E1): Boolean {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
|
||||
enum class E1
|
||||
@@ -0,0 +1,5 @@
|
||||
// PROBLEM: none
|
||||
|
||||
fun test(x: Int, y: Int): Boolean {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// FIX: none
|
||||
|
||||
fun test(x: Int, y: Long): Boolean {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// FIX: none
|
||||
|
||||
fun test(x: Int, y: String): Boolean {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// PROBLEM: none
|
||||
|
||||
fun test(x: Int, y: Foo): Boolean {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
|
||||
class Foo
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// FIX: none
|
||||
enum class E1
|
||||
|
||||
fun test(x: E1?, y: String): Boolean? {
|
||||
return x?.<caret>equals(y)
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// FIX: none
|
||||
fun test(x: Int?, y: String): Boolean? {
|
||||
return x?.<caret>equals(y)
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// FIX: none
|
||||
fun test(x: String?, y: Int): Boolean? {
|
||||
return x?.<caret>equals(y)
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// PROBLEM: none
|
||||
fun test() {
|
||||
System.getProperty("some")?.<caret>equals("true")
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// FIX: none
|
||||
|
||||
fun test(x: String, y: E1): Boolean {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
|
||||
enum class E1
|
||||
@@ -0,0 +1,5 @@
|
||||
// FIX: none
|
||||
|
||||
fun test(x: String, y: Int): Boolean {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// FIX: none
|
||||
enum class E1
|
||||
|
||||
fun test(x: String, y: E1?): Boolean? {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// FIX: none
|
||||
fun test(x: String, y: Int?): Boolean? {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// PROBLEM: none
|
||||
fun test(x: String, y: String?): Boolean? {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// PROBLEM: none
|
||||
|
||||
fun test(x: String, y: String): Boolean {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// PROBLEM: none
|
||||
|
||||
fun test(x: String, y: Foo): Boolean {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
|
||||
class Foo
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// PROBLEM: none
|
||||
|
||||
fun test(x: Foo, y: Int): Boolean {
|
||||
return x.<caret>equals(y)
|
||||
}
|
||||
|
||||
class Foo
|
||||
+128
@@ -2492,6 +2492,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
||||
runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equals.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("equalsBetweenInconvertibleTypes.kt")
|
||||
public void testEqualsBetweenInconvertibleTypes() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsBetweenInconvertibleTypes.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("equalsCompareTo.kt")
|
||||
public void testEqualsCompareTo() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/conventionNameCalls/replaceCallWithBinaryOperator/equalsCompareTo.kt");
|
||||
@@ -3608,6 +3613,129 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class EqualsBetweenInconvertibleTypes extends AbstractLocalInspectionTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInEqualsBetweenInconvertibleTypes() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("enumEqEnum.kt")
|
||||
public void testEnumEqEnum() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/enumEqEnum.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("enumEqEnum2.kt")
|
||||
public void testEnumEqEnum2() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/enumEqEnum2.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("enumEqInt.kt")
|
||||
public void testEnumEqInt() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/enumEqInt.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("enumEqString.kt")
|
||||
public void testEnumEqString() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/enumEqString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("enumEqUserType.kt")
|
||||
public void testEnumEqUserType() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/enumEqUserType.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("intEqEnum.kt")
|
||||
public void testIntEqEnum() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/intEqEnum.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("intEqInt.kt")
|
||||
public void testIntEqInt() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/intEqInt.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("intEqLong.kt")
|
||||
public void testIntEqLong() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/intEqLong.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("intEqString.kt")
|
||||
public void testIntEqString() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/intEqString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("intEqUserType.kt")
|
||||
public void testIntEqUserType() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/intEqUserType.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("nullableEnumEqString.kt")
|
||||
public void testNullableEnumEqString() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/nullableEnumEqString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("nullableIntEqString.kt")
|
||||
public void testNullableIntEqString() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/nullableIntEqString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("nullableStringEqInt.kt")
|
||||
public void testNullableStringEqInt() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/nullableStringEqInt.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("nullableStringEqString.kt")
|
||||
public void testNullableStringEqString() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/nullableStringEqString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("stringEqEnum.kt")
|
||||
public void testStringEqEnum() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/stringEqEnum.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("stringEqInt.kt")
|
||||
public void testStringEqInt() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/stringEqInt.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("stringEqNullableEnum.kt")
|
||||
public void testStringEqNullableEnum() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/stringEqNullableEnum.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("stringEqNullableInt.kt")
|
||||
public void testStringEqNullableInt() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/stringEqNullableInt.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("stringEqNullableString.kt")
|
||||
public void testStringEqNullableString() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/stringEqNullableString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("stringEqString.kt")
|
||||
public void testStringEqString() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/stringEqString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("stringEqUserType.kt")
|
||||
public void testStringEqUserType() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/stringEqUserType.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("userTypeEqInt.kt")
|
||||
public void testUserTypeEqInt() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/equalsBetweenInconvertibleTypes/userTypeEqInt.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/inspectionsLocal/explicitThis")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user