KT-43941 [Sealed interfaces]: subclass intention
Restriction for sealed inheritors was relaxed. Instead of being nested class members now they can be the members of the same module and package.
This commit is contained in:
@@ -14,12 +14,14 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.JavaDirectoryService
|
||||
import com.intellij.refactoring.rename.PsiElementRenameHandler
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
|
||||
import org.jetbrains.kotlin.descriptors.DescriptorVisibility
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.ImplementMembersHandler
|
||||
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
||||
import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile
|
||||
import org.jetbrains.kotlin.idea.refactoring.ui.CreateKotlinClassDialog
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
@@ -73,8 +75,8 @@ class CreateKotlinSubClassIntention : SelfTargetingRangeIntention<KtClass>(
|
||||
if (editor == null) throw IllegalArgumentException("This intention requires an editor")
|
||||
|
||||
val name = element.name ?: throw IllegalStateException("This intention should not be applied to anonymous classes")
|
||||
if (element.isSealed()) {
|
||||
createSealedSubclass(element, name, editor)
|
||||
if (element.isSealed() && !element.languageVersionSettings.supportsFeature(LanguageFeature.SealedInterfaces)) {
|
||||
createNestedSubclass(element, name, editor)
|
||||
} else {
|
||||
createExternalSubclass(element, name, editor)
|
||||
}
|
||||
@@ -87,7 +89,7 @@ class CreateKotlinSubClassIntention : SelfTargetingRangeIntention<KtClass>(
|
||||
private fun targetNameWithoutConflicts(baseName: String, container: KtClassOrObject?) =
|
||||
KotlinNameSuggester.suggestNameByName(defaultTargetName(baseName)) { container?.hasSameDeclaration(it) != true }
|
||||
|
||||
private fun createSealedSubclass(sealedClass: KtClass, sealedName: String, editor: Editor) {
|
||||
private fun createNestedSubclass(sealedClass: KtClass, sealedName: String, editor: Editor) {
|
||||
val project = sealedClass.project
|
||||
val klass = runWriteAction {
|
||||
val builder = buildClassHeader(targetNameWithoutConflicts(sealedName, sealedClass), sealedClass, sealedName)
|
||||
@@ -162,7 +164,8 @@ class CreateKotlinSubClassIntention : SelfTargetingRangeIntention<KtClass>(
|
||||
targetNameWithoutConflicts(baseName, baseClass.containingClassOrObject),
|
||||
aPackage?.qualifiedName ?: "",
|
||||
CreateClassKind.CLASS, true,
|
||||
ModuleUtilCore.findModuleForPsiElement(baseClass)
|
||||
ModuleUtilCore.findModuleForPsiElement(baseClass),
|
||||
baseClass.isSealed()
|
||||
) {
|
||||
override fun getBaseDir(packageName: String?) = sourceDir
|
||||
|
||||
|
||||
@@ -53,7 +53,8 @@ open class CreateKotlinClassDialog(
|
||||
targetPackageName: String,
|
||||
kind: ClassKind,
|
||||
private val myClassNameEditable: Boolean,
|
||||
private val myModule: Module?
|
||||
private val myModule: Module?,
|
||||
val isSealed: Boolean = false
|
||||
) : DialogWrapper(myProject, true) {
|
||||
|
||||
private val myInformationLabel = JLabel("#")
|
||||
@@ -63,7 +64,9 @@ open class CreateKotlinClassDialog(
|
||||
myProject,
|
||||
RECENTS_KEY,
|
||||
CodeInsightBundle.message("dialog.create.class.package.chooser.title")
|
||||
)
|
||||
).also {
|
||||
if (isSealed) it.isEnabled = false
|
||||
}
|
||||
private val myTfClassName: JTextField = MyTextField()
|
||||
var targetDirectory: PsiDirectory? = null
|
||||
private set
|
||||
@@ -239,6 +242,6 @@ open class CreateKotlinClassDialog(
|
||||
override fun pass(s: String?) {
|
||||
setErrorText(s, myDestinationCB)
|
||||
}
|
||||
}, myPackageComponent.getChildComponent())
|
||||
}, myPackageComponent.childComponent, isSealed)
|
||||
}
|
||||
}
|
||||
+26
-9
@@ -28,6 +28,7 @@ import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDest
|
||||
import com.intellij.ui.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle;
|
||||
import org.jetbrains.kotlin.idea.roots.ProjectRootUtilsKt;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
@@ -72,12 +73,25 @@ public abstract class KotlinDestinationFolderComboBox extends ComboboxWithBrowse
|
||||
}
|
||||
|
||||
public void setData(
|
||||
Project project,
|
||||
PsiDirectory initialTargetDirectory,
|
||||
Pass<String> errorMessageUpdater, EditorComboBox editorComboBox
|
||||
Project project,
|
||||
PsiDirectory initialTargetDirectory,
|
||||
Pass<String> errorMessageUpdater, EditorComboBox editorComboBox
|
||||
) {
|
||||
setData(project, initialTargetDirectory, errorMessageUpdater, editorComboBox, false);
|
||||
}
|
||||
|
||||
public void setData(Project project, PsiDirectory initialTargetDirectory, Pass<String> errorMessageUpdater,
|
||||
EditorComboBox editorComboBox, boolean sourceRootsInTargetDirOnly
|
||||
) {
|
||||
myInitialTargetDirectory = initialTargetDirectory;
|
||||
mySourceRoots = getKotlinAwareDestinationSourceRoots(project);
|
||||
|
||||
if (sourceRootsInTargetDirOnly) {
|
||||
Module module = ModuleUtilCore.findModuleForFile(myInitialTargetDirectory.getVirtualFile(), project);
|
||||
mySourceRoots = ProjectRootUtilsKt.collectKotlinAwareDestinationSourceRoots(module);
|
||||
} else {
|
||||
mySourceRoots = getKotlinAwareDestinationSourceRoots(project);
|
||||
}
|
||||
|
||||
new ComboboxSpeedSearch(getComboBox()) {
|
||||
@Override
|
||||
protected String getElementText(Object element) {
|
||||
@@ -130,7 +144,8 @@ public abstract class KotlinDestinationFolderComboBox extends ComboboxWithBrowse
|
||||
return;
|
||||
}
|
||||
}
|
||||
setComboboxModel(getComboBox(), root, root, fileIndex, mySourceRoots, project, true, errorMessageUpdater);
|
||||
setComboboxModel(getComboBox(), root, root, fileIndex, mySourceRoots, project, true, errorMessageUpdater,
|
||||
sourceRootsInTargetDirOnly);
|
||||
});
|
||||
|
||||
editorComboBox.addDocumentListener(new DocumentListener() {
|
||||
@@ -140,10 +155,11 @@ public abstract class KotlinDestinationFolderComboBox extends ComboboxWithBrowse
|
||||
DirectoryChooser.ItemWrapper selectedItem = (DirectoryChooser.ItemWrapper) comboBox.getSelectedItem();
|
||||
setComboboxModel(comboBox, selectedItem != null && selectedItem != NULL_WRAPPER ? fileIndex
|
||||
.getSourceRootForFile(selectedItem.getDirectory().getVirtualFile()) : initialSourceRoot, selection[0], fileIndex,
|
||||
mySourceRoots, project, false, errorMessageUpdater);
|
||||
mySourceRoots, project, false, errorMessageUpdater, sourceRootsInTargetDirOnly);
|
||||
}
|
||||
});
|
||||
setComboboxModel(getComboBox(), initialSourceRoot, selection[0], fileIndex, mySourceRoots, project, false, errorMessageUpdater);
|
||||
setComboboxModel(getComboBox(), initialSourceRoot, selection[0], fileIndex, mySourceRoots, project, false, errorMessageUpdater,
|
||||
sourceRootsInTargetDirOnly);
|
||||
getComboBox().addActionListener(e -> {
|
||||
Object selectedItem = getComboBox().getSelectedItem();
|
||||
updateErrorMessage(errorMessageUpdater, fileIndex, selectedItem);
|
||||
@@ -199,7 +215,8 @@ public abstract class KotlinDestinationFolderComboBox extends ComboboxWithBrowse
|
||||
List<VirtualFile> sourceRoots,
|
||||
Project project,
|
||||
boolean forceIncludeAll,
|
||||
Pass<String> updateErrorMessage
|
||||
Pass<String> updateErrorMessage,
|
||||
boolean sourceRootsInTargetDirOnly
|
||||
) {
|
||||
LinkedHashSet<PsiDirectory> targetDirectories = new LinkedHashSet<>();
|
||||
HashMap<PsiDirectory, String> pathsToCreate = new HashMap<>();
|
||||
@@ -224,7 +241,7 @@ public abstract class KotlinDestinationFolderComboBox extends ComboboxWithBrowse
|
||||
oldOne = itemWrapper;
|
||||
}
|
||||
}
|
||||
if (oldSelection == null || !fileIndex.isInLibrarySource(oldSelection)) {
|
||||
if (!sourceRootsInTargetDirOnly && (oldSelection == null || !fileIndex.isInLibrarySource(oldSelection))) {
|
||||
items.add(NULL_WRAPPER);
|
||||
}
|
||||
DirectoryChooser.ItemWrapper selection = chooseSelection(initialTargetDirectorySourceRoot, fileIndex, items, initial, oldOne);
|
||||
|
||||
@@ -99,7 +99,7 @@ fun getKotlinAwareDestinationSourceRoots(project: Project): List<VirtualFile> {
|
||||
private val KOTLIN_AWARE_SOURCE_ROOT_TYPES: Set<JpsModuleSourceRootType<JavaSourceRootProperties>> =
|
||||
JavaModuleSourceRootTypes.SOURCES + ALL_KOTLIN_SOURCE_ROOT_TYPES
|
||||
|
||||
private fun Module.collectKotlinAwareDestinationSourceRoots(): List<VirtualFile> {
|
||||
fun Module.collectKotlinAwareDestinationSourceRoots(): List<VirtualFile> {
|
||||
return rootManager
|
||||
.contentEntries
|
||||
.asSequence()
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Implement sealed class" "true"
|
||||
// WITH_RUNTIME
|
||||
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
|
||||
// COMPILER_ARGUMENTS: -XXLanguage:+SealedInterfaces
|
||||
|
||||
sealed class <caret>Base {
|
||||
abstract fun foo(): Int
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// "Implement sealed class" "true"
|
||||
// WITH_RUNTIME
|
||||
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
|
||||
// COMPILER_ARGUMENTS: -XXLanguage:+SealedInterfaces
|
||||
|
||||
sealed class Base {
|
||||
abstract fun foo(): Int
|
||||
}
|
||||
|
||||
class BaseImpl : Base() {
|
||||
override fun foo(): Int {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Implement sealed class" "true"
|
||||
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
|
||||
// COMPILER_ARGUMENTS: -XXLanguage:+SealedInterfaces
|
||||
sealed class <caret>Sealed
|
||||
@@ -0,0 +1,5 @@
|
||||
// "Implement sealed class" "true"
|
||||
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
|
||||
// COMPILER_ARGUMENTS: -XXLanguage:+SealedInterfaces
|
||||
sealed class Sealed
|
||||
<caret>class SealedImpl : Sealed()
|
||||
@@ -0,0 +1,12 @@
|
||||
// "Implement sealed class" "true"
|
||||
// WITH_RUNTIME
|
||||
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
|
||||
// COMPILER_ARGUMENTS: -XXLanguage:+SealedInterfaces
|
||||
|
||||
sealed class <caret>Base {
|
||||
abstract fun foo(): Int
|
||||
|
||||
class BaseImpl : Base() {
|
||||
override fun foo() = throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// "Implement sealed class" "true"
|
||||
// WITH_RUNTIME
|
||||
// SHOULD_BE_AVAILABLE_AFTER_EXECUTION
|
||||
// COMPILER_ARGUMENTS: -XXLanguage:+SealedInterfaces
|
||||
|
||||
sealed class Base {
|
||||
abstract fun foo(): Int
|
||||
|
||||
class BaseImpl : Base() {
|
||||
override fun foo() = throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
|
||||
class BaseImpl : Base() {
|
||||
override fun foo(): Int {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
@@ -8204,16 +8204,31 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
runTest("idea/testData/quickfix/implement/sealed.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("sealedAfter15.kt")
|
||||
public void testSealedAfter15() throws Exception {
|
||||
runTest("idea/testData/quickfix/implement/sealedAfter15.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("sealedEmpty.kt")
|
||||
public void testSealedEmpty() throws Exception {
|
||||
runTest("idea/testData/quickfix/implement/sealedEmpty.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("sealedEmptyAfter15.kt")
|
||||
public void testSealedEmptyAfter15() throws Exception {
|
||||
runTest("idea/testData/quickfix/implement/sealedEmptyAfter15.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("sealedWithConflict.kt")
|
||||
public void testSealedWithConflict() throws Exception {
|
||||
runTest("idea/testData/quickfix/implement/sealedWithConflict.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("sealedWithConflictAfter15.kt")
|
||||
public void testSealedWithConflictAfter15() throws Exception {
|
||||
runTest("idea/testData/quickfix/implement/sealedWithConflictAfter15.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("typeParameter.kt")
|
||||
public void testTypeParameter() throws Exception {
|
||||
runTest("idea/testData/quickfix/implement/typeParameter.kt");
|
||||
|
||||
Reference in New Issue
Block a user