Move: Check if sealed class is moved with its subclasses

#KT-22769 Fixed
This commit is contained in:
Alexey Sedunov
2018-02-13 19:31:13 +03:00
parent 27d4593fc4
commit 4537192e86
22 changed files with 115 additions and 0 deletions
@@ -59,6 +59,7 @@ import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
@@ -451,6 +452,38 @@ class MoveConflictChecker(
}
}
private fun checkSealedClassMove(conflicts: MultiMap<PsiElement, String>) {
val visited = HashSet<PsiElement>()
for (elementToMove in elementsToMove) {
if (!visited.add(elementToMove)) continue
if (elementToMove !is KtClassOrObject) continue
val rootClass: KtClass
val rootClassDescriptor: ClassDescriptor
if (elementToMove is KtClass && elementToMove.isSealed()) {
rootClass = elementToMove
rootClassDescriptor = rootClass.resolveToDescriptorIfAny() as? ClassDescriptor ?: return
}
else {
val classDescriptor = elementToMove.resolveToDescriptorIfAny() as? ClassDescriptor ?: return
val superClassDescriptor = classDescriptor.getSuperClassNotAny() ?: return
if (superClassDescriptor.modality != Modality.SEALED) return
rootClassDescriptor = superClassDescriptor
rootClass = rootClassDescriptor.source.getPsi() as? KtClass ?: return
}
val subclasses = rootClassDescriptor.sealedSubclasses.mapNotNull { it.source.getPsi() }
if (subclasses.isEmpty()) continue
visited.add(rootClass)
visited.addAll(subclasses)
if (isToBeMoved(rootClass) && subclasses.all { isToBeMoved(it) }) continue
conflicts.putValue(rootClass, "Sealed class '${rootClass.name}' must be moved with all its subclasses")
}
}
fun checkAllConflicts(
externalUsages: MutableSet<UsageInfo>,
internalUsages: MutableSet<UsageInfo>,
@@ -461,6 +494,7 @@ class MoveConflictChecker(
checkVisibilityInUsages(externalUsages, conflicts)
checkVisibilityInDeclarations(conflicts)
checkInternalMemberUsages(conflicts)
checkSealedClassMove(conflicts)
}
}
@@ -0,0 +1,6 @@
package target
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
@@ -0,0 +1,6 @@
package source
sealed class <caret>Expr
data class <caret>Const(val number: Double) : Expr()
data class <caret>Sum(val e1: Expr, val e2: Expr) : Expr()
object <caret>NotANumber : Expr()
@@ -0,0 +1,5 @@
{
"mainFile": "source/Foo.kt",
"type": "MOVE_KOTLIN_TOP_LEVEL_DECLARATIONS",
"targetPackage": "target"
}
@@ -0,0 +1,7 @@
package source
import target.Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
@@ -0,0 +1,3 @@
package target
sealed class Expr
@@ -0,0 +1,6 @@
package source
sealed class <caret>Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
@@ -0,0 +1 @@
Sealed class 'Expr' must be moved with all its subclasses
@@ -0,0 +1,5 @@
{
"mainFile": "source/Foo.kt",
"type": "MOVE_KOTLIN_TOP_LEVEL_DECLARATIONS",
"targetPackage": "target"
}
@@ -0,0 +1,5 @@
package source
sealed class Expr
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
@@ -0,0 +1,5 @@
package target
import source.Expr
data class Const(val number: Double) : Expr()
@@ -0,0 +1,6 @@
package source
sealed class Expr
data class <caret>Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
@@ -0,0 +1 @@
Sealed class 'Expr' must be moved with all its subclasses
@@ -0,0 +1,5 @@
{
"mainFile": "source/Foo.kt",
"type": "MOVE_KOTLIN_TOP_LEVEL_DECLARATIONS",
"targetPackage": "target"
}
@@ -613,6 +613,24 @@ public class MoveTestGenerated extends AbstractMoveTest {
doTest(fileName);
}
@TestMetadata("kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/sealedClassWithAllSubclasses.test")
public void testKotlin_moveTopLevelDeclarations_misc_sealedClassWithAllSubclasses_SealedClassWithAllSubclasses() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithAllSubclasses/sealedClassWithAllSubclasses.test");
doTest(fileName);
}
@TestMetadata("kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/sealedClassWithSkippedSubclasses.test")
public void testKotlin_moveTopLevelDeclarations_misc_sealedClassWithSkippedSubclasses_SealedClassWithSkippedSubclasses() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedClassWithSkippedSubclasses/sealedClassWithSkippedSubclasses.test");
doTest(fileName);
}
@TestMetadata("kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/sealedSubclassWithSkippedRoot.test")
public void testKotlin_moveTopLevelDeclarations_misc_sealedSubclassWithSkippedRoot_SealedSubclassWithSkippedRoot() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/sealedSubclassWithSkippedRoot/sealedSubclassWithSkippedRoot.test");
doTest(fileName);
}
@TestMetadata("kotlin/moveTopLevelDeclarations/misc/selfReferences/selfReferences.test")
public void testKotlin_moveTopLevelDeclarations_misc_selfReferences_SelfReferences() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveTopLevelDeclarations/misc/selfReferences/selfReferences.test");