From df7968678aab8a08f6bc8bf1174101603011063a Mon Sep 17 00:00:00 2001 From: Gabryel Monteiro Date: Mon, 12 Feb 2018 20:50:36 +0300 Subject: [PATCH] Add inspection: Refactor sealed sub-class to object #KT-20305 Fixed --- .../CanSealedSubClassBeObject.html | 5 + idea/src/META-INF/plugin.xml | 9 ++ .../CanSealedSubClassBeObjectInspection.kt | 88 +++++++++++++++ .../ConvertSealedSubClassToObjectFix.kt | 105 ++++++++++++++++++ .../inspectionData/expected.xml | 50 +++++++++ .../inspectionData/inspections.test | 1 + .../sealedSubClassCanBeObject/sealed.kt | 47 ++++++++ .../convertSealedSubClassToObject/.inspection | 1 + .../convertSubClassWithModifiers.kt | 3 + .../convertSubClassWithModifiers.kt.after | 3 + .../convertSubClassWithParentheses.kt | 3 + .../convertSubClassWithParentheses.kt.after | 3 + .../convertSubClassWithoutParentheses.kt | 3 + ...convertSubClassWithoutParentheses.kt.after | 3 + .../after/JavaSealedTest.java | 11 ++ .../after/KotlinSealedTest.kt | 16 +++ .../after/Seal.kt | 9 ++ .../before/JavaSealedTest.java | 11 ++ .../before/KotlinSealedTest.kt | 16 +++ .../before/Seal.kt | 9 ++ .../convertCallableReferenceUsages.test | 5 + .../after/JavaSealedTest.java | 13 +++ .../after/KotlinSealedTest.kt | 13 +++ .../convertInOtherFiles/after/Seal.kt | 5 + .../before/JavaSealedTest.java | 14 +++ .../before/KotlinSealedTest.kt | 13 +++ .../convertInOtherFiles/before/Seal.kt | 5 + .../convertInOtherFiles.test | 5 + .../codeInsight/InspectionTestGenerated.java | 6 + .../LocalInspectionTestGenerated.java | 27 +++++ ...MultiFileLocalInspectionTestGenerated.java | 12 ++ 31 files changed, 514 insertions(+) create mode 100644 idea/resources/inspectionDescriptions/CanSealedSubClassBeObject.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/CanSealedSubClassBeObjectInspection.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/quickfix/sealedSubClassToObject/ConvertSealedSubClassToObjectFix.kt create mode 100644 idea/testData/inspections/sealedSubClassCanBeObject/inspectionData/expected.xml create mode 100644 idea/testData/inspections/sealedSubClassCanBeObject/inspectionData/inspections.test create mode 100644 idea/testData/inspections/sealedSubClassCanBeObject/sealed.kt create mode 100644 idea/testData/inspectionsLocal/convertSealedSubClassToObject/.inspection create mode 100644 idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithModifiers.kt create mode 100644 idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithModifiers.kt.after create mode 100644 idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithParentheses.kt create mode 100644 idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithParentheses.kt.after create mode 100644 idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithoutParentheses.kt create mode 100644 idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithoutParentheses.kt.after create mode 100644 idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/after/JavaSealedTest.java create mode 100644 idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/after/KotlinSealedTest.kt create mode 100644 idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/after/Seal.kt create mode 100644 idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/before/JavaSealedTest.java create mode 100644 idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/before/KotlinSealedTest.kt create mode 100644 idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/before/Seal.kt create mode 100644 idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/convertCallableReferenceUsages.test create mode 100644 idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/after/JavaSealedTest.java create mode 100644 idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/after/KotlinSealedTest.kt create mode 100644 idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/after/Seal.kt create mode 100644 idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/before/JavaSealedTest.java create mode 100644 idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/before/KotlinSealedTest.kt create mode 100644 idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/before/Seal.kt create mode 100644 idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/convertInOtherFiles.test diff --git a/idea/resources/inspectionDescriptions/CanSealedSubClassBeObject.html b/idea/resources/inspectionDescriptions/CanSealedSubClassBeObject.html new file mode 100644 index 00000000000..4a8662c3ede --- /dev/null +++ b/idea/resources/inspectionDescriptions/CanSealedSubClassBeObject.html @@ -0,0 +1,5 @@ + + +This inspection reports direct inheritors of sealed class that have default constructors only and thus can be objects. + + \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 7ec629bff6a..6d93620712d 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2765,6 +2765,15 @@ language="kotlin" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/CanSealedSubClassBeObjectInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/CanSealedSubClassBeObjectInspection.kt new file mode 100644 index 00000000000..4cac7bf5755 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/CanSealedSubClassBeObjectInspection.kt @@ -0,0 +1,88 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.kotlin.idea.inspections + +import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.psi.PsiElementVisitor +import org.jetbrains.kotlin.asJava.classes.KtLightClassImpl +import org.jetbrains.kotlin.idea.core.getModalityFromDescriptor +import org.jetbrains.kotlin.idea.quickfix.sealedSubClassToObject.ConvertSealedSubClassToObjectFix +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.KtVisitorVoid + +class CanSealedSubClassBeObjectInspection : AbstractKotlinInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { + return object : KtVisitorVoid() { + override fun visitClass(klass: KtClass) { + if (!klass.hasModifier(KtTokens.SEALED_KEYWORD)) return + if (klass.getModalityFromDescriptor() != KtTokens.SEALED_KEYWORD) return + + klass.getSubclasses() + .withEmptyConstructors() + .thatAreFinal() + .thatHasNoInnerClasses() + .thatHasNoCompanionObjects() + .forEach { reportPossibleObject(it) } + } + + private fun reportPossibleObject(klass: KtClass) { + val keyword = klass.getClassOrInterfaceKeyword() ?: return + holder.registerProblem( + keyword, + "Sealed Sub-class should be changed To Object", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + ConvertSealedSubClassToObjectFix() + ) + } + } + } + + private fun KtClass.getSubclasses(): List { + return HierarchySearchRequest(this, this.useScope, false) + .searchInheritors().filterIsInstance() + } + + private fun List.withEmptyConstructors(): List { + return map { it.kotlinOrigin }.filterIsInstance() + .filter { it.primaryConstructorParameters.isEmpty() } + .filter { klass -> klass.secondaryConstructors.all { cons -> cons.valueParameters.isEmpty() } } + } + + private fun List.thatHasNoCompanionObjects(): List { + return filter { klass -> klass.companionObjects.isEmpty() } + } + + private fun List.thatAreFinal(): List { + return filter { klass -> klass.getModalityFromDescriptor() == KtTokens.FINAL_KEYWORD } + } + + private fun List.thatHasNoInnerClasses(): List { + return filter { klass -> klass.hasNoInnerClass() } + } + + private fun KtClass.hasNoInnerClass(): Boolean { + val internalClasses = getBody() + ?.declarations + ?.filterIsInstance() ?: return true + + return internalClasses.none { klass -> klass.isInner() } + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/sealedSubClassToObject/ConvertSealedSubClassToObjectFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/sealedSubClassToObject/ConvertSealedSubClassToObjectFix.kt new file mode 100644 index 00000000000..641453103a7 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/sealedSubClassToObject/ConvertSealedSubClassToObjectFix.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2000-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.lang.Language +import com.intellij.openapi.project.Project +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiElement +import com.intellij.psi.impl.source.tree.JavaElementType +import com.intellij.psi.search.searches.ReferencesSearch +import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.buildExpression +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType + +class ConvertSealedSubClassToObjectFix : LocalQuickFix { + + override fun getFamilyName() = "Convert Sealed Sub-class to Object" + + companion object { + val JAVA_LANG = Language.findLanguageByID("JAVA") + val KOTLIN_LANG = Language.findLanguageByID("kotlin") + } + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val klass = descriptor.psiElement.getParentOfType(false) ?: return + + changeInstances(klass) + changeDeclaration(klass) + } + + /** + * Changes declaration of class to object. + */ + private fun changeDeclaration(element: KtClass) { + val factory = KtPsiFactory(element) + + element.changeToObject(factory) + element.transformToObject(factory) + } + + private fun KtClass.changeToObject(factory: KtPsiFactory) { + getClassOrInterfaceKeyword()?.replace(factory.createExpression(KtTokens.OBJECT_KEYWORD.value)) + secondaryConstructors.forEach { delete() } + primaryConstructor?.delete() + } + + private fun KtClass.transformToObject(factory: KtPsiFactory) { + replace(factory.createObject(text)) + } + + /** + * Replace instantiations of the class with links to the singleton instance of the object. + */ + private fun changeInstances(klass: KtClass) { + mapReferencesByLanguage(klass) + .apply { + replaceKotlin(klass) + replaceJava(klass) + } + } + + /** + * Map references to this class by language + */ + private fun mapReferencesByLanguage(klass: KtClass) = ReferencesSearch.search(klass) + .groupBy({ it.element.language }, { it.element.parent }) + + /** + * Replace Kotlin instantiations to a straightforward call to the singleton. + */ + private fun Map>.replaceKotlin(klass: KtClass) { + val list = this[KOTLIN_LANG] ?: return + val singletonCall = KtPsiFactory(klass).buildExpression { appendName(klass.nameAsSafeName) } + + list.filter { it.node.elementType == KtNodeTypes.CALL_EXPRESSION } + .forEach { it.replace(singletonCall) } + } + + /** + * Replace Java instantiations to an instance of the object, unless it is the only thing + * done in the statement, in which IDEA will consider wrong, so I delete the line. + */ + private fun Map>.replaceJava(klass: KtClass) { + val list = this[JAVA_LANG] ?: return + val first = list.firstOrNull() ?: return + val elementFactory = JavaPsiFacade.getElementFactory(klass.project) + val javaSingletonCall = elementFactory.createExpressionFromText("${klass.name}.INSTANCE", first) + + list.filter { it.node.elementType == JavaElementType.NEW_EXPRESSION } + .forEach { + when (it.parent.node.elementType) { + JavaElementType.EXPRESSION_STATEMENT -> it.delete() + else -> it.replace(javaSingletonCall) + } + } + } +} \ No newline at end of file diff --git a/idea/testData/inspections/sealedSubClassCanBeObject/inspectionData/expected.xml b/idea/testData/inspections/sealedSubClassCanBeObject/inspectionData/expected.xml new file mode 100644 index 00000000000..da5d2ab5187 --- /dev/null +++ b/idea/testData/inspections/sealedSubClassCanBeObject/inspectionData/expected.xml @@ -0,0 +1,50 @@ + + + sealed.kt + 8 + light_idea_test_case + + Sealed Sub-class should be changed To Object + Sealed Sub-class should be changed To Object + + + sealed.kt + 10 + light_idea_test_case + + Sealed Sub-class should be changed To Object + Sealed Sub-class should be changed To Object + + + sealed.kt + 13 + light_idea_test_case + + Sealed Sub-class should be changed To Object + Sealed Sub-class should be changed To Object + + + sealed.kt + 15 + light_idea_test_case + + Sealed Sub-class should be changed To Object + Sealed Sub-class should be changed To Object + + + sealed.kt + 41 + light_idea_test_case + + Sealed Sub-class should be changed To Object + Sealed Sub-class should be changed To Object + + + sealed.kt + 45 + light_idea_test_case + + Sealed Sub-class should be changed To Object + Sealed Sub-class should be changed To Object + + \ No newline at end of file diff --git a/idea/testData/inspections/sealedSubClassCanBeObject/inspectionData/inspections.test b/idea/testData/inspections/sealedSubClassCanBeObject/inspectionData/inspections.test new file mode 100644 index 00000000000..9eed2b5b197 --- /dev/null +++ b/idea/testData/inspections/sealedSubClassCanBeObject/inspectionData/inspections.test @@ -0,0 +1 @@ +// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.CanSealedSubClassBeObjectInspection diff --git a/idea/testData/inspections/sealedSubClassCanBeObject/sealed.kt b/idea/testData/inspections/sealedSubClassCanBeObject/sealed.kt new file mode 100644 index 00000000000..12e45003967 --- /dev/null +++ b/idea/testData/inspections/sealedSubClassCanBeObject/sealed.kt @@ -0,0 +1,47 @@ +sealed class Sealed { + open class OpenSubSealedNestedNoChild() : Sealed() + + open class SubSealedNestedWithChild : Sealed() + + class SubSealedNestedChild() : SubSealedNestedWithChild() + + final class FinalSubSealedNested() : Sealed() + + class SubSealedNested() : Sealed() +} + +class SubSealedParentheses() : Sealed() + +class SubSealedNoParentheses : Sealed() + +open class SubSealedWithChild : Sealed() + +class SubSealedChild : SubSealedWithChild() + +class SubSealedParameters(val value : String) : Sealed() + +object SubSealedObject() : Sealed() + +class SubSealedObjectWithPrimaryConstructor(parameter: String) : Sealed() + +class SubSealedObjectWithSecondaryConstructor() : Sealed() { + constructor(parameter: String): this() +} + +object AlreadyObject: Sealed() + +class SubSealedWithCompanion: Sealed() { + companion object +} + +class SubSealedWithInner: Sealed() { + inner class Inner +} + +class SubSealedWithNested: Sealed() { + class Nested +} + +class SubSealedWithFunction: Sealed() { + fun internalFunction() { } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/.inspection b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/.inspection new file mode 100644 index 00000000000..59b482c36d0 --- /dev/null +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.CanSealedSubClassBeObjectInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithModifiers.kt b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithModifiers.kt new file mode 100644 index 00000000000..89ae91c31d3 --- /dev/null +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithModifiers.kt @@ -0,0 +1,3 @@ +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 new file mode 100644 index 00000000000..80fb84760f6 --- /dev/null +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithModifiers.kt.after @@ -0,0 +1,3 @@ +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 new file mode 100644 index 00000000000..578fff8216d --- /dev/null +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithParentheses.kt @@ -0,0 +1,3 @@ +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 new file mode 100644 index 00000000000..9f4a9df9787 --- /dev/null +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithParentheses.kt.after @@ -0,0 +1,3 @@ +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 new file mode 100644 index 00000000000..eac01055aba --- /dev/null +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithoutParentheses.kt @@ -0,0 +1,3 @@ +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 new file mode 100644 index 00000000000..9f4a9df9787 --- /dev/null +++ b/idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithoutParentheses.kt.after @@ -0,0 +1,3 @@ +sealed class Sealed + +object SubSealed : Sealed() \ No newline at end of file diff --git a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/after/JavaSealedTest.java b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/after/JavaSealedTest.java new file mode 100644 index 00000000000..5d76aca2f85 --- /dev/null +++ b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/after/JavaSealedTest.java @@ -0,0 +1,11 @@ +import seal.* + +class JavaSealedTest { + public void testNesting() { + new SubSealed.Nested(); + Supplier nestedSupplier = SubSealed.Nested::new; + + SubSealed.INSTANCE.internalFunction(); + Runnable noArgFunction = SubSealed.INSTANCE::internalFunction; + } +} \ No newline at end of file diff --git a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/after/KotlinSealedTest.kt b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/after/KotlinSealedTest.kt new file mode 100644 index 00000000000..be1502b3233 --- /dev/null +++ b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/after/KotlinSealedTest.kt @@ -0,0 +1,16 @@ +import seal.* + +class KotlinSealedTest { + fun testSeal() { + val internalFunction = SubSealed::internalFunction + + val nestedClass = SubSealed.Nested() + + SubSealed.Nested() + SubSealed::Nested + + SubSealed.internalFunction() + SubSealed::internalFunction + SubSealed::internalFunction + } +} \ No newline at end of file diff --git a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/after/Seal.kt b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/after/Seal.kt new file mode 100644 index 00000000000..e0b9734ad13 --- /dev/null +++ b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/after/Seal.kt @@ -0,0 +1,9 @@ +package seal + +sealed class Sealed + +object SubSealed : Sealed() { + class Nested + + fun internalFunction() {} +} \ No newline at end of file diff --git a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/before/JavaSealedTest.java b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/before/JavaSealedTest.java new file mode 100644 index 00000000000..511545700fa --- /dev/null +++ b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/before/JavaSealedTest.java @@ -0,0 +1,11 @@ +import seal.* + +class JavaSealedTest { + public void testNesting() { + new SubSealed.Nested(); + Supplier nestedSupplier = SubSealed.Nested::new; + + new SubSealed().internalFunction(); + Runnable noArgFunction = new SubSealed()::internalFunction; + } +} \ No newline at end of file diff --git a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/before/KotlinSealedTest.kt b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/before/KotlinSealedTest.kt new file mode 100644 index 00000000000..d70b96dd53a --- /dev/null +++ b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/before/KotlinSealedTest.kt @@ -0,0 +1,16 @@ +import seal.* + +class KotlinSealedTest { + fun testSeal() { + val internalFunction = SubSealed()::internalFunction + + val nestedClass = SubSealed.Nested() + + SubSealed.Nested() + SubSealed::Nested + + SubSealed().internalFunction() + SubSealed()::internalFunction + SubSealed::internalFunction + } +} \ No newline at end of file diff --git a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/before/Seal.kt b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/before/Seal.kt new file mode 100644 index 00000000000..1daa4520144 --- /dev/null +++ b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/before/Seal.kt @@ -0,0 +1,9 @@ +package seal + +sealed class Sealed + +class SubSealed : Sealed() { + class Nested + + fun internalFunction() {} +} \ No newline at end of file diff --git a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/convertCallableReferenceUsages.test b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/convertCallableReferenceUsages.test new file mode 100644 index 00000000000..67c88f77340 --- /dev/null +++ b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/convertCallableReferenceUsages.test @@ -0,0 +1,5 @@ +{ + "mainFile": "Seal.kt", + "inspectionClass": "org.jetbrains.kotlin.idea.inspections.CanSealedSubClassBeObjectInspection", + "fix": "Convert Sealed Sub-class to Object" +} \ No newline at end of file diff --git a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/after/JavaSealedTest.java b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/after/JavaSealedTest.java new file mode 100644 index 00000000000..7f565d3f8cf --- /dev/null +++ b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/after/JavaSealedTest.java @@ -0,0 +1,13 @@ +import seal.* + +class JavaSealedTest { + Sealed sealedInsideClass = SubSealed.INSTANCE; + + public void testSeal() { + Sealed sealedInsideMethod = SubSealed.INSTANCE; + + SubSealed.INSTANCE.toString(); + + // Will be deleted because Java doesn't allow a expression to be used as a statement + } +} \ No newline at end of file diff --git a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/after/KotlinSealedTest.kt b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/after/KotlinSealedTest.kt new file mode 100644 index 00000000000..1283112599b --- /dev/null +++ b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/after/KotlinSealedTest.kt @@ -0,0 +1,13 @@ +import seal.* + +val sealedOutsideClass = SubSealed + +class KotlinSealedTest { + val sealedInsideClass = SubSealed + + fun testSeal() { + val sealedInsideMethod = SubSealed + + SubSealed + } +} \ No newline at end of file diff --git a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/after/Seal.kt b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/after/Seal.kt new file mode 100644 index 00000000000..b0f627a9d2c --- /dev/null +++ b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/after/Seal.kt @@ -0,0 +1,5 @@ +package seal + +sealed class Sealed + +object SubSealed : Sealed() \ No newline at end of file diff --git a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/before/JavaSealedTest.java b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/before/JavaSealedTest.java new file mode 100644 index 00000000000..39bbbcd0705 --- /dev/null +++ b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/before/JavaSealedTest.java @@ -0,0 +1,14 @@ +import seal.* + +class JavaSealedTest { + Sealed sealedInsideClass = new SubSealed(); + + public void testSeal() { + Sealed sealedInsideMethod = new SubSealed(); + + new SubSealed().toString(); + + // Will be deleted because Java doesn't allow a expression to be used as a statement + new SubSealed(); + } +} \ No newline at end of file diff --git a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/before/KotlinSealedTest.kt b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/before/KotlinSealedTest.kt new file mode 100644 index 00000000000..7e713eec17c --- /dev/null +++ b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/before/KotlinSealedTest.kt @@ -0,0 +1,13 @@ +import seal.* + +val sealedOutsideClass = SubSealed() + +class KotlinSealedTest { + val sealedInsideClass = SubSealed() + + fun testSeal() { + val sealedInsideMethod = SubSealed() + + SubSealed() + } +} \ No newline at end of file diff --git a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/before/Seal.kt b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/before/Seal.kt new file mode 100644 index 00000000000..f2e29658a7b --- /dev/null +++ b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/before/Seal.kt @@ -0,0 +1,5 @@ +package seal + +sealed class Sealed + +class SubSealed : Sealed() \ No newline at end of file diff --git a/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/convertInOtherFiles.test b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/convertInOtherFiles.test new file mode 100644 index 00000000000..67c88f77340 --- /dev/null +++ b/idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/convertInOtherFiles.test @@ -0,0 +1,5 @@ +{ + "mainFile": "Seal.kt", + "inspectionClass": "org.jetbrains.kotlin.idea.inspections.CanSealedSubClassBeObjectInspection", + "fix": "Convert Sealed Sub-class to Object" +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java index a4d4ae43a50..ae6f9a5b4c9 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/InspectionTestGenerated.java @@ -372,6 +372,12 @@ public class InspectionTestGenerated extends AbstractInspectionTest { doTest(fileName); } + @TestMetadata("sealedSubClassCanBeObject/inspectionData/inspections.test") + public void testSealedSubClassCanBeObject_inspectionData_Inspections_test() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/sealedSubClassCanBeObject/inspectionData/inspections.test"); + doTest(fileName); + } + @TestMetadata("sortModifiers/inspectionData/inspections.test") public void testSortModifiers_inspectionData_Inspections_test() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/sortModifiers/inspectionData/inspections.test"); diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 656465d667a..aba22cfc6b5 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -1234,6 +1234,33 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/convertSealedSubClassToObject") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ConvertSealedSubClassToObject extends AbstractLocalInspectionTest { + public void testAllFilesPresentInConvertSealedSubClassToObject() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/convertSealedSubClassToObject"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("convertSubClassWithModifiers.kt") + public void testConvertSubClassWithModifiers() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithModifiers.kt"); + doTest(fileName); + } + + @TestMetadata("convertSubClassWithParentheses.kt") + public void testConvertSubClassWithParentheses() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithParentheses.kt"); + doTest(fileName); + } + + @TestMetadata("convertSubClassWithoutParentheses.kt") + public void testConvertSubClassWithoutParentheses() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/convertSealedSubClassToObject/convertSubClassWithoutParentheses.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/inspectionsLocal/copyWithoutNamedArguments") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/MultiFileLocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/MultiFileLocalInspectionTestGenerated.java index cf03fee249c..24277d292ee 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/MultiFileLocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/MultiFileLocalInspectionTestGenerated.java @@ -25,6 +25,18 @@ public class MultiFileLocalInspectionTestGenerated extends AbstractMultiFileLoca KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/multiFileLocalInspections"), Pattern.compile("^(.+)\\.test$"), TargetBackend.ANY); } + @TestMetadata("convertSealedSubClassToObject/convertCallableReferenceUsages/convertCallableReferenceUsages.test") + public void testConvertSealedSubClassToObject_convertCallableReferenceUsages_ConvertCallableReferenceUsages() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertCallableReferenceUsages/convertCallableReferenceUsages.test"); + doTest(fileName); + } + + @TestMetadata("convertSealedSubClassToObject/convertInOtherFiles/convertInOtherFiles.test") + public void testConvertSealedSubClassToObject_convertInOtherFiles_ConvertInOtherFiles() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileLocalInspections/convertSealedSubClassToObject/convertInOtherFiles/convertInOtherFiles.test"); + doTest(fileName); + } + @TestMetadata("moveFileToPackageMatchingDirectory/moveToDefaultDirectory/moveToDefaultDirectory.test") public void testMoveFileToPackageMatchingDirectory_moveToDefaultDirectory_MoveToDefaultDirectory() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileLocalInspections/moveFileToPackageMatchingDirectory/moveToDefaultDirectory/moveToDefaultDirectory.test");