diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertEnumToSealedClassIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertEnumToSealedClassIntention.kt index 75592e4079e..45c14cd0a9a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertEnumToSealedClassIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ConvertEnumToSealedClassIntention.kt @@ -20,6 +20,9 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.codeStyle.CodeStyleManager +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.idea.refactoring.withExpectedActuals import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.allChildren @@ -35,40 +38,56 @@ class ConvertEnumToSealedClassIntention : SelfTargetingRangeIntention(K } override fun applyTo(element: KtClass, editor: Editor?) { - element.removeModifier(KtTokens.ENUM_KEYWORD) - element.addModifier(KtTokens.SEALED_KEYWORD) + val name = element.name ?: return + if (name.isEmpty()) return - val psiFactory = KtPsiFactory(element) + for (klass in element.withExpectedActuals()) { + klass as? KtClass ?: continue - for (member in element.declarations) { - if (member !is KtEnumEntry) continue + val classDescriptor = klass.resolveToDescriptorIfAny() as? ClassDescriptor ?: continue + val isExpect = classDescriptor.isExpect + val isActual = classDescriptor.isActual - val obj = psiFactory.createDeclaration("object ${member.name}") + klass.removeModifier(KtTokens.ENUM_KEYWORD) + klass.addModifier(KtTokens.SEALED_KEYWORD) - val initializers = member.initializerList?.initializers ?: emptyList() - if (initializers.isNotEmpty()) { - initializers.forEach { obj.addSuperTypeListEntry(psiFactory.createSuperTypeCallEntry("${element.name}${it.text}")) } - } - else { - obj.addSuperTypeListEntry(psiFactory.createSuperTypeCallEntry("${element.name}()")) + val psiFactory = KtPsiFactory(klass) + + for (member in klass.declarations) { + if (member !is KtEnumEntry) continue + + val obj = psiFactory.createDeclaration("object ${member.name}") + + val initializers = member.initializerList?.initializers ?: emptyList() + if (initializers.isNotEmpty()) { + initializers.forEach { obj.addSuperTypeListEntry(psiFactory.createSuperTypeCallEntry("${klass.name}${it.text}")) } + } + else { + val defaultEntry = if (isExpect) psiFactory.createSuperTypeEntry(name) else psiFactory.createSuperTypeCallEntry("$name()") + obj.addSuperTypeListEntry(defaultEntry) + } + + if (isActual) { + obj.addModifier(KtTokens.ACTUAL_KEYWORD) + } + + member.getBody()?.let { body -> obj.add(body) } + + member.delete() + klass.addDeclaration(obj) } - member.getBody()?.let { body -> obj.add(body) } - - member.delete() - element.addDeclaration(obj) - } - - element.getBody()?.let { body -> - val semicolon = body - .allChildren - .takeWhile { it !is KtDeclaration } - .firstOrNull { it.node.elementType == KtTokens.SEMICOLON } - if (semicolon != null) { - val nonWhiteSibling = semicolon.siblings(forward = true, withItself = false).firstOrNull { it !is PsiWhiteSpace } - body.deleteChildRange(semicolon, nonWhiteSibling?.prevSibling ?: semicolon) - if (nonWhiteSibling != null) { - CodeStyleManager.getInstance(element.project).reformat(nonWhiteSibling.firstChild ?: nonWhiteSibling) + klass.getBody()?.let { body -> + val semicolon = body + .allChildren + .takeWhile { it !is KtDeclaration } + .firstOrNull { it.node.elementType == KtTokens.SEMICOLON } + if (semicolon != null) { + val nonWhiteSibling = semicolon.siblings(forward = true, withItself = false).firstOrNull { it !is PsiWhiteSpace } + body.deleteChildRange(semicolon, nonWhiteSibling?.prevSibling ?: semicolon) + if (nonWhiteSibling != null) { + CodeStyleManager.getInstance(klass.project).reformat(nonWhiteSibling.firstChild ?: nonWhiteSibling) + } } } } diff --git a/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/header/header.kt b/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/header/header.kt new file mode 100644 index 00000000000..b4dad8dfa19 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/header/header.kt @@ -0,0 +1,3 @@ +expect enum class E { + A, B, C +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/header/header.kt.after b/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/header/header.kt.after new file mode 100644 index 00000000000..b9ba6ae1551 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/header/header.kt.after @@ -0,0 +1,5 @@ +expect sealed class E { + object A : E + object B : E + object C : E +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/js/impl.kt b/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/js/impl.kt new file mode 100644 index 00000000000..76bc57577d6 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/js/impl.kt @@ -0,0 +1,5 @@ +// "Convert to sealed class" "true" + +actual enum class E { + A, B, C +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/js/impl.kt.after b/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/js/impl.kt.after new file mode 100644 index 00000000000..26049c6b56f --- /dev/null +++ b/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/js/impl.kt.after @@ -0,0 +1,7 @@ +// "Convert to sealed class" "true" + +actual sealed class E { + actual object A : E() + actual object B : E() + actual object C : E() +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/jvm/impl.kt b/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/jvm/impl.kt new file mode 100644 index 00000000000..1d96930e795 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/jvm/impl.kt @@ -0,0 +1,3 @@ +actual enum class E { + A, B, C +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/jvm/impl.kt.after b/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/jvm/impl.kt.after new file mode 100644 index 00000000000..e48b1294659 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/convertActualEnumToSealedClass/jvm/impl.kt.after @@ -0,0 +1,5 @@ +actual sealed class E { + actual object A : E() + actual object B : E() + actual object C : E() +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/header/header.kt b/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/header/header.kt new file mode 100644 index 00000000000..9c7e57d2fc2 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/header/header.kt @@ -0,0 +1,5 @@ +// "Convert to sealed class" "true" + +expect enum class E { + A, B, C +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/header/header.kt.after b/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/header/header.kt.after new file mode 100644 index 00000000000..105dc72c58b --- /dev/null +++ b/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/header/header.kt.after @@ -0,0 +1,7 @@ +// "Convert to sealed class" "true" + +expect sealed class E { + object A : E + object B : E + object C : E +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/js/impl.kt b/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/js/impl.kt new file mode 100644 index 00000000000..1d96930e795 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/js/impl.kt @@ -0,0 +1,3 @@ +actual enum class E { + A, B, C +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/js/impl.kt.after b/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/js/impl.kt.after new file mode 100644 index 00000000000..d0df8206bdd --- /dev/null +++ b/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/js/impl.kt.after @@ -0,0 +1,5 @@ +actual sealed class E { + actual object A : E() + actual object B : E() + actual object C : E() +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/jvm/impl.kt b/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/jvm/impl.kt new file mode 100644 index 00000000000..1d96930e795 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/jvm/impl.kt @@ -0,0 +1,3 @@ +actual enum class E { + A, B, C +} \ No newline at end of file diff --git a/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/jvm/impl.kt.after b/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/jvm/impl.kt.after new file mode 100644 index 00000000000..e48b1294659 --- /dev/null +++ b/idea/testData/multiModuleQuickFix/convertExpectEnumToSealedClass/jvm/impl.kt.after @@ -0,0 +1,5 @@ +actual sealed class E { + actual object A : E() + actual object B : E() + actual object C : E() +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTest.kt b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTest.kt index 02df3dc5d0b..ee3d7872c3a 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixMultiModuleTest.kt @@ -264,4 +264,10 @@ class QuickFixMultiModuleTest : AbstractQuickFixMultiModuleTest() { @Test fun testConvertActualSealedClassToEnum() = doTestHeaderWithJvmAndJs("js") + + @Test + fun testConvertExpectEnumToSealedClass() = doTestHeaderWithJvmAndJs("header") + + @Test + fun testConvertActualEnumToSealedClass() = doTestHeaderWithJvmAndJs("js") }