Convert Enum to Sealed Class: Support expect/actual classes

This commit is contained in:
Alexey Sedunov
2017-10-20 14:15:22 +03:00
parent 9d5d85a1c5
commit 5892944bfc
14 changed files with 109 additions and 28 deletions
@@ -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<KtClass>(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<KtObjectDeclaration>("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<KtObjectDeclaration>("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)
}
}
}
}
@@ -0,0 +1,3 @@
expect enum class E {
A, B, C
}
@@ -0,0 +1,5 @@
<caret>expect sealed class E {
object A : E
object B : E
object C : E
}
@@ -0,0 +1,5 @@
// "Convert to sealed class" "true"
actual enum class <caret>E {
A, B, C
}
@@ -0,0 +1,7 @@
// "Convert to sealed class" "true"
actual sealed class <caret>E {
actual object A : E()
actual object B : E()
actual object C : E()
}
@@ -0,0 +1,3 @@
actual enum class E {
A, B, C
}
@@ -0,0 +1,5 @@
actual sealed class E {
actual object A : E()
actual object B : E()
actual object C : E()
}
@@ -0,0 +1,5 @@
// "Convert to sealed class" "true"
expect enum class <caret>E {
A, B, C
}
@@ -0,0 +1,7 @@
// "Convert to sealed class" "true"
expect sealed class <caret>E {
object A : E
object B : E
object C : E
}
@@ -0,0 +1,3 @@
actual enum class E {
A, B, C
}
@@ -0,0 +1,5 @@
<caret>actual sealed class E {
actual object A : E()
actual object B : E()
actual object C : E()
}
@@ -0,0 +1,3 @@
actual enum class E {
A, B, C
}
@@ -0,0 +1,5 @@
actual sealed class E {
actual object A : E()
actual object B : E()
actual object C : E()
}
@@ -264,4 +264,10 @@ class QuickFixMultiModuleTest : AbstractQuickFixMultiModuleTest() {
@Test
fun testConvertActualSealedClassToEnum() = doTestHeaderWithJvmAndJs("js")
@Test
fun testConvertExpectEnumToSealedClass() = doTestHeaderWithJvmAndJs("header")
@Test
fun testConvertActualEnumToSealedClass() = doTestHeaderWithJvmAndJs("js")
}