From 9ef89447b30de47272c8395fe3984817aaa46172 Mon Sep 17 00:00:00 2001 From: Mikhail Glukhikh Date: Wed, 13 Jun 2018 18:05:46 +0300 Subject: [PATCH] Sealed sub-class -> object: handle equals, suggest "add equals" Before this commit, sealed sub-class without state was considered a style issue. After this commit, sealed sub-class without state AND custom equals is considered a probable bug, because comparison of its instances is very fragile. Alternative fix (generate equals & hashCode by identity) is added. --- .../CanSealedSubClassBeObject.html | 3 +- idea/src/META-INF/plugin.xml | 6 +-- idea/src/META-INF/plugin.xml.172 | 6 +-- idea/src/META-INF/plugin.xml.173 | 6 +-- idea/src/META-INF/plugin.xml.182 | 6 +-- idea/src/META-INF/plugin.xml.as31 | 6 +-- idea/src/META-INF/plugin.xml.as32 | 6 +-- .../CanSealedSubClassBeObjectInspection.kt | 49 ++++++++++++------- .../ConvertSealedSubClassToObjectFix.kt | 2 +- .../GenerateIdentityEqualsFix.kt | 48 ++++++++++++++++++ .../inspectionData/expected.xml | 24 ++++----- .../baseEquals.kt | 18 +++++++ .../convertSubClassWithModifiers.kt | 2 + .../convertSubClassWithModifiers.kt.after | 2 + .../convertSubClassWithParentheses.kt | 2 + .../convertSubClassWithParentheses.kt.after | 2 + .../convertSubClassWithoutParentheses.kt | 2 + ...convertSubClassWithoutParentheses.kt.after | 2 + .../fakeState.kt | 1 + .../fakeState.kt.after | 1 + .../convertSealedSubClassToObject/noEquals.kt | 13 +++++ .../noEquals.kt.after | 21 ++++++++ .../ownEquals.kt | 9 ++++ .../sealedEquals.kt | 12 +++++ .../convertCallableReferenceUsages.test | 2 +- .../convertInOtherFiles.test | 2 +- .../LocalInspectionTestGenerated.java | 20 ++++++++ 27 files changed, 222 insertions(+), 51 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/sealedSubClassToObject/GenerateIdentityEqualsFix.kt create mode 100644 idea/testData/inspectionsLocal/convertSealedSubClassToObject/baseEquals.kt create mode 100644 idea/testData/inspectionsLocal/convertSealedSubClassToObject/noEquals.kt create mode 100644 idea/testData/inspectionsLocal/convertSealedSubClassToObject/noEquals.kt.after create mode 100644 idea/testData/inspectionsLocal/convertSealedSubClassToObject/ownEquals.kt create mode 100644 idea/testData/inspectionsLocal/convertSealedSubClassToObject/sealedEquals.kt diff --git a/idea/resources/inspectionDescriptions/CanSealedSubClassBeObject.html b/idea/resources/inspectionDescriptions/CanSealedSubClassBeObject.html index af276fbf04a..6b85d805406 100644 --- a/idea/resources/inspectionDescriptions/CanSealedSubClassBeObject.html +++ b/idea/resources/inspectionDescriptions/CanSealedSubClassBeObject.html @@ -1,5 +1,6 @@ -This inspection reports direct inheritors of sealed class that have no state and thus can be objects. +This inspection reports direct inheritors of sealed class that have no state and no overridden equals. +It's highly recommended to override equals to provide comparison stability or convert class to object with the same effect. \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 5c58dc9f4a9..5fb736da19f 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2838,11 +2838,11 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. /> diff --git a/idea/src/META-INF/plugin.xml.172 b/idea/src/META-INF/plugin.xml.172 index 1b3cad96b58..5a22051e866 100644 --- a/idea/src/META-INF/plugin.xml.172 +++ b/idea/src/META-INF/plugin.xml.172 @@ -2836,11 +2836,11 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. /> diff --git a/idea/src/META-INF/plugin.xml.173 b/idea/src/META-INF/plugin.xml.173 index 2597ba523ad..15a7d7f6b7b 100644 --- a/idea/src/META-INF/plugin.xml.173 +++ b/idea/src/META-INF/plugin.xml.173 @@ -2836,11 +2836,11 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. /> diff --git a/idea/src/META-INF/plugin.xml.182 b/idea/src/META-INF/plugin.xml.182 index 1520d1f1aed..53f894c0453 100644 --- a/idea/src/META-INF/plugin.xml.182 +++ b/idea/src/META-INF/plugin.xml.182 @@ -2837,11 +2837,11 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. /> diff --git a/idea/src/META-INF/plugin.xml.as31 b/idea/src/META-INF/plugin.xml.as31 index 46445e5800f..d516619c07e 100644 --- a/idea/src/META-INF/plugin.xml.as31 +++ b/idea/src/META-INF/plugin.xml.as31 @@ -2836,11 +2836,11 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. /> diff --git a/idea/src/META-INF/plugin.xml.as32 b/idea/src/META-INF/plugin.xml.as32 index 0ab49fe0d1b..e5c9c867105 100644 --- a/idea/src/META-INF/plugin.xml.as32 +++ b/idea/src/META-INF/plugin.xml.as32 @@ -2836,11 +2836,11 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. /> diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/CanSealedSubClassBeObjectInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/CanSealedSubClassBeObjectInspection.kt index c951da6f93a..f36b6648272 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/CanSealedSubClassBeObjectInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/CanSealedSubClassBeObjectInspection.kt @@ -23,15 +23,18 @@ import org.jetbrains.kotlin.asJava.classes.KtLightClassImpl import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.core.getModalityFromDescriptor import org.jetbrains.kotlin.idea.quickfix.sealedSubClassToObject.ConvertSealedSubClassToObjectFix +import org.jetbrains.kotlin.idea.quickfix.sealedSubClassToObject.GenerateIdentityEqualsFix import org.jetbrains.kotlin.idea.refactoring.isAbstract import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.psi.KtVisitorVoid import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny +import org.jetbrains.kotlin.util.OperatorNameConventions class CanSealedSubClassBeObjectInspection : AbstractKotlinInspection() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { @@ -46,8 +49,8 @@ class CanSealedSubClassBeObjectInspection : AbstractKotlinInspection() { .thatHasNoTypeParameters() .thatHasNoInnerClasses() .thatHasNoCompanionObjects() - .thatHasNoState() - if (candidates.isEmpty() || !klass.hasNoState() || !klass.baseClassHasNoState()) return + .thatHasNoStateOrEquals() + if (candidates.isEmpty() || !klass.hasNoStateOrEquals() || !klass.baseClassHasNoStateOrEquals()) return candidates.forEach { reportPossibleObject(it) } } @@ -56,22 +59,15 @@ class CanSealedSubClassBeObjectInspection : AbstractKotlinInspection() { val keyword = klass.getClassOrInterfaceKeyword() ?: return holder.registerProblem( keyword, - "Sealed Sub-class should be changed To Object", + "Sealed sub-class has no state and no overridden equals", ProblemHighlightType.GENERIC_ERROR_OR_WARNING, - ConvertSealedSubClassToObjectFix() + ConvertSealedSubClassToObjectFix(), + GenerateIdentityEqualsFix() ) } } } - private tailrec fun KtClass.baseClassHasNoState(): Boolean { - val descriptor = resolveToDescriptorIfAny() ?: return false - val superDescriptor = descriptor.getSuperClassNotAny() ?: return true // No super class -- no state - val superClass = DescriptorToSourceUtils.descriptorToDeclaration(superDescriptor) as? KtClass ?: return false - if (!superClass.hasNoState()) return false - return superClass.baseClassHasNoState() - } - private fun KtClass.getSubclasses(): List { return HierarchySearchRequest(this, this.useScope, false) .searchInheritors().filterIsInstance() @@ -99,16 +95,24 @@ class CanSealedSubClassBeObjectInspection : AbstractKotlinInspection() { return filter { klass -> klass.hasNoInnerClass() } } - private fun List.thatHasNoState(): List { - return filter { it.hasNoState() } + private fun List.thatHasNoStateOrEquals(): List { + return filter { it.hasNoStateOrEquals() } } - private fun KtClass.hasNoState(): Boolean { + private tailrec fun KtClass.baseClassHasNoStateOrEquals(): Boolean { + val descriptor = resolveToDescriptorIfAny() ?: return false + val superDescriptor = descriptor.getSuperClassNotAny() ?: return true // No super class -- no state + val superClass = DescriptorToSourceUtils.descriptorToDeclaration(superDescriptor) as? KtClass ?: return false + if (!superClass.hasNoStateOrEquals()) return false + return superClass.baseClassHasNoStateOrEquals() + } + + private fun KtClass.hasNoStateOrEquals(): Boolean { if (primaryConstructor?.valueParameters?.isNotEmpty() == true) return false val body = getBody() return body == null || run { - val properties = body.declarations.filterIsInstance() - properties.none { property -> + val declarations = body.declarations + declarations.filterIsInstance().none { property -> // Simplified "backing field required" when { property.isAbstract() -> false @@ -117,6 +121,11 @@ class CanSealedSubClassBeObjectInspection : AbstractKotlinInspection() { !property.isVar -> property.getter == null else -> property.getter == null || property.setter == null } + } && declarations.filterIsInstance().none { function -> + val name = function.name + val valueParameters = function.valueParameters + val noTypeParameters = function.typeParameters.isEmpty() + noTypeParameters && (name == EQUALS && valueParameters.size == 1 || name == HASH_CODE && valueParameters.isEmpty()) } } } @@ -128,4 +137,10 @@ class CanSealedSubClassBeObjectInspection : AbstractKotlinInspection() { return internalClasses.none { klass -> klass.isInner() } } + + companion object { + val EQUALS = OperatorNameConventions.EQUALS.asString() + + const val HASH_CODE = "hashCode" + } } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/sealedSubClassToObject/ConvertSealedSubClassToObjectFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/sealedSubClassToObject/ConvertSealedSubClassToObjectFix.kt index 641453103a7..dcd7cbb5522 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/sealedSubClassToObject/ConvertSealedSubClassToObjectFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/sealedSubClassToObject/ConvertSealedSubClassToObjectFix.kt @@ -22,7 +22,7 @@ import org.jetbrains.kotlin.psi.psiUtil.getParentOfType class ConvertSealedSubClassToObjectFix : LocalQuickFix { - override fun getFamilyName() = "Convert Sealed Sub-class to Object" + override fun getFamilyName() = "Convert sealed sub-class to object" companion object { val JAVA_LANG = Language.findLanguageByID("JAVA") diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/sealedSubClassToObject/GenerateIdentityEqualsFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/sealedSubClassToObject/GenerateIdentityEqualsFix.kt new file mode 100644 index 00000000000..be0c54c4e4c --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/sealedSubClassToObject/GenerateIdentityEqualsFix.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2018 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.quickfix.sealedSubClassToObject + +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtPsiFactory.* +import org.jetbrains.kotlin.psi.KtPsiFactory.CallableBuilder.Target.* +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType + +class GenerateIdentityEqualsFix : LocalQuickFix { + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val klass = descriptor.psiElement.getParentOfType(false) ?: return + val factory = KtPsiFactory(klass) + + val equalsFunction = factory.createFunction( + CallableBuilder(FUNCTION).apply { + modifier(KtTokens.OVERRIDE_KEYWORD.value) + typeParams() + name("equals") + param("other", "Any?") + returnType("Boolean") + blockBody("return this === other") + }.asString() + ) + klass.addDeclaration(equalsFunction) + + val hashCodeFunction = factory.createFunction( + CallableBuilder(FUNCTION).apply { + modifier(KtTokens.OVERRIDE_KEYWORD.value) + typeParams() + name("hashCode") + returnType("Int") + blockBody("return System.identityHashCode(this)") + }.asString() + ) + klass.addDeclaration(hashCodeFunction) + } + + override fun getFamilyName() = "Generate equals & hashCode by identity" +} \ No newline at end of file diff --git a/idea/testData/inspections/sealedSubClassCanBeObject/inspectionData/expected.xml b/idea/testData/inspections/sealedSubClassCanBeObject/inspectionData/expected.xml index da5d2ab5187..db7bdf88453 100644 --- a/idea/testData/inspections/sealedSubClassCanBeObject/inspectionData/expected.xml +++ b/idea/testData/inspections/sealedSubClassCanBeObject/inspectionData/expected.xml @@ -4,47 +4,47 @@ 8 light_idea_test_case - Sealed Sub-class should be changed To Object - Sealed Sub-class should be changed To Object + Sealed sub-class without state and overridden equals + Sealed sub-class has no state and no overridden equals sealed.kt 10 light_idea_test_case - Sealed Sub-class should be changed To Object - Sealed Sub-class should be changed To Object + Sealed sub-class without state and overridden equals + Sealed sub-class has no state and no overridden equals sealed.kt 13 light_idea_test_case - Sealed Sub-class should be changed To Object - Sealed Sub-class should be changed To Object + Sealed sub-class without state and overridden equals + Sealed sub-class has no state and no overridden equals sealed.kt 15 light_idea_test_case - Sealed Sub-class should be changed To Object - Sealed Sub-class should be changed To Object + Sealed sub-class without state and overridden equals + Sealed sub-class has no state and no overridden equals sealed.kt 41 light_idea_test_case - Sealed Sub-class should be changed To Object - Sealed Sub-class should be changed To Object + Sealed sub-class without state and overridden equals + Sealed sub-class has no state and no overridden equals sealed.kt 45 light_idea_test_case - Sealed Sub-class should be changed To Object - Sealed Sub-class should be changed To Object + Sealed sub-class without state and overridden equals + Sealed sub-class has no state and no overridden equals \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/baseEquals.kt b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/baseEquals.kt new file mode 100644 index 00000000000..a14b7de38ea --- /dev/null +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/baseEquals.kt @@ -0,0 +1,18 @@ +// PROBLEM: none + +abstract class Base { + open val prop: Int + get() = 13 + + override fun equals(other: Any?): Boolean { + if (other !is Base) return false + return prop == other.prop + } +} + +sealed class SC : Base() { + class U : SC() + + override val prop: Int + get() = 42 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithModifiers.kt b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithModifiers.kt index 89ae91c31d3..2ea4aff1983 100644 --- a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithModifiers.kt +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithModifiers.kt @@ -1,3 +1,5 @@ +// FIX: Convert sealed sub-class to object + sealed class Sealed private class SubSealed : Sealed() \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithModifiers.kt.after b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithModifiers.kt.after index 80fb84760f6..3981efc5639 100644 --- a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithModifiers.kt.after +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithModifiers.kt.after @@ -1,3 +1,5 @@ +// FIX: Convert sealed sub-class to object + sealed class Sealed private object SubSealed : Sealed() \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithParentheses.kt b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithParentheses.kt index 578fff8216d..6206891f2f7 100644 --- a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithParentheses.kt +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithParentheses.kt @@ -1,3 +1,5 @@ +// FIX: Convert sealed sub-class to object + sealed class Sealed class SubSealed() : Sealed() \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithParentheses.kt.after b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithParentheses.kt.after index 9f4a9df9787..db51cae1906 100644 --- a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithParentheses.kt.after +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithParentheses.kt.after @@ -1,3 +1,5 @@ +// FIX: Convert sealed sub-class to object + sealed class Sealed object SubSealed : Sealed() \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithoutParentheses.kt b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithoutParentheses.kt index eac01055aba..acac3f0c1e9 100644 --- a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithoutParentheses.kt +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithoutParentheses.kt @@ -1,3 +1,5 @@ +// FIX: Convert sealed sub-class to object + sealed class Sealed class SubSealed : Sealed() \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithoutParentheses.kt.after b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithoutParentheses.kt.after index 9f4a9df9787..db51cae1906 100644 --- a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithoutParentheses.kt.after +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithoutParentheses.kt.after @@ -1,3 +1,5 @@ +// FIX: Convert sealed sub-class to object + sealed class Sealed object SubSealed : Sealed() \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/fakeState.kt b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/fakeState.kt index 9b4feeddb1f..f50d78a842a 100644 --- a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/fakeState.kt +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/fakeState.kt @@ -1,3 +1,4 @@ +// FIX: Convert sealed sub-class to object // WITH_RUNTIME abstract class Base { diff --git a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/fakeState.kt.after b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/fakeState.kt.after index 4472ccbd688..49782de405a 100644 --- a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/fakeState.kt.after +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/fakeState.kt.after @@ -1,3 +1,4 @@ +// FIX: Convert sealed sub-class to object // WITH_RUNTIME abstract class Base { diff --git a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/noEquals.kt b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/noEquals.kt new file mode 100644 index 00000000000..ed74968259b --- /dev/null +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/noEquals.kt @@ -0,0 +1,13 @@ +// FIX: Generate equals & hashCode by identity + +abstract class Base { + open val prop: Int + get() = 13 +} + +sealed class SC : Base() { + class U : SC() + + override val prop: Int + get() = 42 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/noEquals.kt.after b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/noEquals.kt.after new file mode 100644 index 00000000000..8481997df9a --- /dev/null +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/noEquals.kt.after @@ -0,0 +1,21 @@ +// FIX: Generate equals & hashCode by identity + +abstract class Base { + open val prop: Int + get() = 13 +} + +sealed class SC : Base() { + class U : SC() { + override fun equals(other: Any?): Boolean { + return this === other + } + + override fun hashCode(): Int { + return System.identityHashCode(this) + } + } + + override val prop: Int + get() = 42 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/ownEquals.kt b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/ownEquals.kt new file mode 100644 index 00000000000..0f6f2e525d2 --- /dev/null +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/ownEquals.kt @@ -0,0 +1,9 @@ +// PROBLEM: none + +sealed class SC { + class U : SC() { + override fun equals(other: Any?): Boolean { + return this === other + } + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/sealedEquals.kt b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/sealedEquals.kt new file mode 100644 index 00000000000..153d534e74b --- /dev/null +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/sealedEquals.kt @@ -0,0 +1,12 @@ +// PROBLEM: none + +sealed class SC { + class U : SC() + + fun foo() = 42 + + override fun equals(other: Any?): Boolean { + if (other !is SC) return false + return foo() == other.foo() + } +} \ No newline at end of file diff --git a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/convertCallableReferenceUsages.test b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/convertCallableReferenceUsages.test index 67c88f77340..60983da2e64 100644 --- a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/convertCallableReferenceUsages.test +++ b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/convertCallableReferenceUsages.test @@ -1,5 +1,5 @@ { "mainFile": "Seal.kt", "inspectionClass": "org.jetbrains.kotlin.idea.inspections.CanSealedSubClassBeObjectInspection", - "fix": "Convert Sealed Sub-class to Object" + "fix": "Convert sealed sub-class to object" } \ No newline at end of file diff --git a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/convertInOtherFiles.test b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/convertInOtherFiles.test index 67c88f77340..60983da2e64 100644 --- a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/convertInOtherFiles.test +++ b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/convertInOtherFiles.test @@ -1,5 +1,5 @@ { "mainFile": "Seal.kt", "inspectionClass": "org.jetbrains.kotlin.idea.inspections.CanSealedSubClassBeObjectInspection", - "fix": "Convert Sealed Sub-class to Object" + "fix": "Convert sealed sub-class to object" } \ 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 412f24ae908..df27eb3eeb3 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -1225,6 +1225,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/convertSealedSubClassToObject"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); } + @TestMetadata("baseEquals.kt") + public void testBaseEquals() throws Exception { + runTest("idea/testData/inspectionsLocal/convertSealedSubClassToObject/baseEquals.kt"); + } + @TestMetadata("baseState.kt") public void testBaseState() throws Exception { runTest("idea/testData/inspectionsLocal/convertSealedSubClassToObject/baseState.kt"); @@ -1255,6 +1260,11 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { runTest("idea/testData/inspectionsLocal/convertSealedSubClassToObject/generic.kt"); } + @TestMetadata("noEquals.kt") + public void testNoEquals() throws Exception { + runTest("idea/testData/inspectionsLocal/convertSealedSubClassToObject/noEquals.kt"); + } + @TestMetadata("nonEmptyConstructor.kt") public void testNonEmptyConstructor() throws Exception { runTest("idea/testData/inspectionsLocal/convertSealedSubClassToObject/nonEmptyConstructor.kt"); @@ -1265,11 +1275,21 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { runTest("idea/testData/inspectionsLocal/convertSealedSubClassToObject/openSubclass.kt"); } + @TestMetadata("ownEquals.kt") + public void testOwnEquals() throws Exception { + runTest("idea/testData/inspectionsLocal/convertSealedSubClassToObject/ownEquals.kt"); + } + @TestMetadata("ownState.kt") public void testOwnState() throws Exception { runTest("idea/testData/inspectionsLocal/convertSealedSubClassToObject/ownState.kt"); } + @TestMetadata("sealedEquals.kt") + public void testSealedEquals() throws Exception { + runTest("idea/testData/inspectionsLocal/convertSealedSubClassToObject/sealedEquals.kt"); + } + @TestMetadata("sealedState.kt") public void testSealedState() throws Exception { runTest("idea/testData/inspectionsLocal/convertSealedSubClassToObject/sealedState.kt");