diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveDeclarationToSeparateFileIntention.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveDeclarationToSeparateFileIntention.kt index 55fe22d1eb1..41625fa39d9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveDeclarationToSeparateFileIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/MoveDeclarationToSeparateFileIntention.kt @@ -24,6 +24,10 @@ import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.util.TextRange import com.intellij.refactoring.move.MoveCallback import com.intellij.refactoring.util.CommonRefactoringUtil +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.intentions.SelfTargetingRangeIntention import org.jetbrains.kotlin.idea.core.moveCaret import org.jetbrains.kotlin.idea.refactoring.createKotlinFile @@ -35,16 +39,24 @@ import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtObjectDeclaration import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode class MoveDeclarationToSeparateFileIntention : - SelfTargetingRangeIntention(KtClassOrObject::class.java, "Move declaration to separate file"), - LowPriorityAction { + SelfTargetingRangeIntention(KtClassOrObject::class.java, "Move declaration to separate file"), + LowPriorityAction { override fun applicabilityRange(element: KtClassOrObject): TextRange? { if (element.name == null) return null if (element.parent !is KtFile) return null if (element.hasModifier(KtTokens.PRIVATE_KEYWORD)) return null if (element.containingKtFile.declarations.size == 1) return null + if (element.resolveToDescriptorIfAny()?.sealedSubclasses?.isNotEmpty() == true) return null + if (element.superTypeListEntries.any { + val superType = it.analyze(BodyResolveMode.PARTIAL)[BindingContext.TYPE, it.typeReference] + (superType?.constructor?.declarationDescriptor as? ClassDescriptor)?.modality == Modality.SEALED + }) return null + val keyword = when (element) { is KtClass -> element.getClassOrInterfaceKeyword() is KtObjectDeclaration -> element.getObjectKeyword() @@ -76,15 +88,17 @@ class MoveDeclarationToSeparateFileIntention : // If automatic move is not possible, fall back to full-fledged Move Declarations refactoring ApplicationManager.getApplication().invokeLater { - MoveKotlinTopLevelDeclarationsDialog(project, - setOf(element), - packageName.asString(), - directory, - targetFile as? KtFile, - true, - true, - true, - null).show() + MoveKotlinTopLevelDeclarationsDialog( + project, + setOf(element), + packageName.asString(), + directory, + targetFile as? KtFile, + true, + true, + true, + null + ).show() } return } @@ -92,18 +106,18 @@ class MoveDeclarationToSeparateFileIntention : createKotlinFile(targetFileName, directory, packageName.asString()) } val descriptor = MoveDeclarationsDescriptor( - project = project, - elementsToMove = listOf(element), - moveTarget = moveTarget, - delegate = MoveDeclarationsDelegate.TopLevel, - searchInCommentsAndStrings = false, - searchInNonCode = false, - moveCallback = MoveCallback { - val newFile = directory.findFile(targetFileName) as KtFile - val newDeclaration = newFile.declarations.first() - NavigationUtil.activateFileWithPsiElement(newFile) - FileEditorManager.getInstance(project).selectedTextEditor?.moveCaret(newDeclaration.startOffset + originalOffset) - } + project = project, + elementsToMove = listOf(element), + moveTarget = moveTarget, + delegate = MoveDeclarationsDelegate.TopLevel, + searchInCommentsAndStrings = false, + searchInNonCode = false, + moveCallback = MoveCallback { + val newFile = directory.findFile(targetFileName) as KtFile + val newDeclaration = newFile.declarations.first() + NavigationUtil.activateFileWithPsiElement(newFile) + FileEditorManager.getInstance(project).selectedTextEditor?.moveCaret(newDeclaration.startOffset + originalOffset) + } ) MoveKotlinDeclarationsProcessor(descriptor).run() diff --git a/idea/testData/intentions/moveDeclarationToSeparateFile/.intention b/idea/testData/intentions/moveDeclarationToSeparateFile/.intention new file mode 100644 index 00000000000..86161fabce1 --- /dev/null +++ b/idea/testData/intentions/moveDeclarationToSeparateFile/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveDeclarationToSeparateFileIntention \ No newline at end of file diff --git a/idea/testData/intentions/moveDeclarationToSeparateFile/extendSealed.kt b/idea/testData/intentions/moveDeclarationToSeparateFile/extendSealed.kt new file mode 100644 index 00000000000..1dba2cc2eed --- /dev/null +++ b/idea/testData/intentions/moveDeclarationToSeparateFile/extendSealed.kt @@ -0,0 +1,3 @@ +// IS_APPLICABLE: false +sealed class A +class B : A() \ No newline at end of file diff --git a/idea/testData/intentions/moveDeclarationToSeparateFile/sealed.kt b/idea/testData/intentions/moveDeclarationToSeparateFile/sealed.kt new file mode 100644 index 00000000000..e20cef61e18 --- /dev/null +++ b/idea/testData/intentions/moveDeclarationToSeparateFile/sealed.kt @@ -0,0 +1,3 @@ +// IS_APPLICABLE: false +sealed class A +class B : A() \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 0b784622e3c..ce1271dc223 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -11824,6 +11824,27 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } } + @TestMetadata("idea/testData/intentions/moveDeclarationToSeparateFile") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class MoveDeclarationToSeparateFile extends AbstractIntentionTest { + public void testAllFilesPresentInMoveDeclarationToSeparateFile() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/moveDeclarationToSeparateFile"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("extendSealed.kt") + public void testExtendSealed() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveDeclarationToSeparateFile/extendSealed.kt"); + doTest(fileName); + } + + @TestMetadata("sealed.kt") + public void testSealed() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/moveDeclarationToSeparateFile/sealed.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/intentions/moveLambdaInsideParentheses") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)