Add 'Covariant equals' inspection
#KT-29798 Fixed
This commit is contained in:
committed by
Dmitry Gridin
parent
3998e842f1
commit
61f3e776a7
@@ -3333,6 +3333,15 @@
|
||||
language="kotlin"
|
||||
/>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.KotlinCovariantEqualsInspection"
|
||||
displayName="Covariant 'equals()'"
|
||||
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,7 @@
|
||||
<html>
|
||||
<body>
|
||||
This inspection reports <b>equals()</b> methods taking an argument type other than <b>Any?</b>.
|
||||
Only reports if the containing class does not have another <b>equals()</b> method which does take <b>Any?</b> as its argument type.
|
||||
Normally, this is a mistake.
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
import org.jetbrains.kotlin.psi.KtObjectDeclaration
|
||||
import org.jetbrains.kotlin.psi.namedFunctionVisitor
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.types.typeUtil.isBoolean
|
||||
import org.jetbrains.kotlin.types.typeUtil.isNullableAny
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
|
||||
class KotlinCovariantEqualsInspection : AbstractKotlinInspection() {
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = namedFunctionVisitor(fun(function) {
|
||||
if (function.isTopLevel || function.isLocal) return
|
||||
if (function.nameAsName != OperatorNameConventions.EQUALS) return
|
||||
val nameIdentifier = function.nameIdentifier ?: return
|
||||
val classOrObject = function.containingClassOrObject ?: return
|
||||
if (classOrObject is KtObjectDeclaration && classOrObject.isCompanion()) return
|
||||
|
||||
val parameter = function.valueParameters.singleOrNull() ?: return
|
||||
val typeReference = parameter.typeReference ?: return
|
||||
val type = parameter.analyze(BodyResolveMode.PARTIAL)[BindingContext.TYPE, typeReference] ?: return
|
||||
if (KotlinBuiltIns.isNullableAny(type)) return
|
||||
|
||||
if (classOrObject.body?.children?.any { (it as? KtNamedFunction)?.isEquals() == true } == true) return
|
||||
|
||||
holder.registerProblem(nameIdentifier, "'equals' should take 'Any?' as its argument")
|
||||
})
|
||||
|
||||
private fun KtNamedFunction.isEquals(): Boolean {
|
||||
if (!hasModifier(KtTokens.OVERRIDE_KEYWORD)) return false
|
||||
if (nameAsName != OperatorNameConventions.EQUALS) return false
|
||||
val descriptor = this.descriptor as? FunctionDescriptor ?: return false
|
||||
if (descriptor.valueParameters.singleOrNull()?.type?.isNullableAny() != true) return false
|
||||
if (descriptor.returnType?.isBoolean() != true) return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.inspections.KotlinCovariantEqualsInspection
|
||||
@@ -0,0 +1,7 @@
|
||||
// PROBLEM: 'equals' should take 'Any?' as its argument
|
||||
// FIX: none
|
||||
class Foo {
|
||||
fun <caret>equals(other: Foo?): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// PROBLEM: none
|
||||
class Foo {
|
||||
fun <caret>equals(other: Foo?): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// PROBLEM: none
|
||||
class F {
|
||||
companion object {
|
||||
fun <caret>equals(other: F?): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// PROBLEM: 'equals' should take 'Any?' as its argument
|
||||
// FIX: none
|
||||
object F {
|
||||
fun <caret>equals(other: F?): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// PROBLEM: 'equals' should take 'Any?' as its argument
|
||||
// FIX: none
|
||||
interface F
|
||||
|
||||
val f = object : F {
|
||||
fun <caret>equals(other: F?): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// PROBLEM: none
|
||||
class Foo {
|
||||
fun test() {
|
||||
fun <caret>equals(other: Foo?): Boolean {
|
||||
return true
|
||||
}
|
||||
equals(null)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// PROBLEM: 'equals' should take 'Any?' as its argument
|
||||
// FIX: none
|
||||
open class Foo {
|
||||
open fun equals(other: Foo?): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class Bar : Foo() {
|
||||
override fun <caret>equals(other: Foo?): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// PROBLEM: none
|
||||
class Foo {
|
||||
override fun <caret>equals(other: Any?): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// PROBLEM: none
|
||||
fun <caret>equals(other: Any?): Boolean {
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// PROBLEM: none
|
||||
class Foo {
|
||||
fun <caret>equals(other: Foo?, other2: Foo?): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
+63
@@ -2459,6 +2459,69 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/inspectionsLocal/covariantEquals")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class CovariantEquals extends AbstractLocalInspectionTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInCovariantEquals() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/covariantEquals"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("basic.kt")
|
||||
public void testBasic() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/covariantEquals/basic.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("hasOverriddenAnyEquals.kt")
|
||||
public void testHasOverriddenAnyEquals() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/covariantEquals/hasOverriddenAnyEquals.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("inCompanion.kt")
|
||||
public void testInCompanion() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/covariantEquals/inCompanion.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("inObject.kt")
|
||||
public void testInObject() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/covariantEquals/inObject.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("inObjectLiteral.kt")
|
||||
public void testInObjectLiteral() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/covariantEquals/inObjectLiteral.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("local.kt")
|
||||
public void testLocal() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/covariantEquals/local.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("override.kt")
|
||||
public void testOverride() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/covariantEquals/override.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("overrideAnyEquals.kt")
|
||||
public void testOverrideAnyEquals() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/covariantEquals/overrideAnyEquals.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("topLevel.kt")
|
||||
public void testTopLevel() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/covariantEquals/topLevel.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("twoParameters.kt")
|
||||
public void testTwoParameters() throws Exception {
|
||||
runTest("idea/testData/inspectionsLocal/covariantEquals/twoParameters.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/inspectionsLocal/delegationToVarProperty")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user