From b412edf2a38e736c66e07affd5c6d422b6015bba Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Thu, 22 Sep 2016 14:29:03 +0300 Subject: [PATCH] Extract Class: Implement 'Extract Interface' refactoring #KT-11017 Fixed --- ChangeLog.md | 1 + .../kotlin/generators/tests/GenerateTests.kt | 1 + .../KotlinRefactoringSupportProvider.kt | 3 + ...actoring.kt => ExtractSuperRefactoring.kt} | 56 ++++--- .../KotlinExtractInterfaceHandler.kt | 45 +++++ .../KotlinExtractSuperHandlerBase.kt | 85 ++++++++++ .../KotlinExtractSuperclassHandler.kt | 65 +------- .../ui/KotlinExtractInterfaceDialog.kt | 85 ++++++++++ .../ui/KotlinExtractSuperDialogBase.kt | 144 ++++++++++++++++ .../ui/KotlinExtractSuperclassDialog.kt | 155 ++---------------- .../extractInterface/addInterface.kt | 13 ++ .../extractInterface/addInterface.kt.after | 16 ++ .../addInterfaceWithAbstract.kt | 13 ++ .../addInterfaceWithAbstract.kt.after | 18 ++ .../extractInterface/addTypeParameters.kt | 14 ++ .../addTypeParameters.kt.after | 17 ++ .../addTypeParametersWithAbstract.kt | 14 ++ .../addTypeParametersWithAbstract.kt.after | 19 +++ .../extractInterface/annotation.kt | 3 + .../extractInterface/annotation.kt.conflicts | 1 + .../introduce/AbstractExtractionTest.kt | 17 +- .../introduce/ExtractionTestGenerated.java | 39 +++++ 22 files changed, 592 insertions(+), 232 deletions(-) rename idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/{ExtractSuperclassRefactoring.kt => ExtractSuperRefactoring.kt} (87%) create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/KotlinExtractInterfaceHandler.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/KotlinExtractSuperHandlerBase.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractInterfaceDialog.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractSuperDialogBase.kt create mode 100644 idea/testData/refactoring/extractInterface/addInterface.kt create mode 100644 idea/testData/refactoring/extractInterface/addInterface.kt.after create mode 100644 idea/testData/refactoring/extractInterface/addInterfaceWithAbstract.kt create mode 100644 idea/testData/refactoring/extractInterface/addInterfaceWithAbstract.kt.after create mode 100644 idea/testData/refactoring/extractInterface/addTypeParameters.kt create mode 100644 idea/testData/refactoring/extractInterface/addTypeParameters.kt.after create mode 100644 idea/testData/refactoring/extractInterface/addTypeParametersWithAbstract.kt create mode 100644 idea/testData/refactoring/extractInterface/addTypeParametersWithAbstract.kt.after create mode 100644 idea/testData/refactoring/extractInterface/annotation.kt create mode 100644 idea/testData/refactoring/extractInterface/annotation.kt.conflicts diff --git a/ChangeLog.md b/ChangeLog.md index 095310b8551..e6dbe11159b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -144,6 +144,7 @@ These artifacts include extensions for the types available in the latter JDKs, s - [`KT-13155`](https://youtrack.jetbrains.com/issue/KT-13155) Implement "Introduce Type Parameter" refactoring - [`KT-11017`](https://youtrack.jetbrains.com/issue/KT-11017) Implement "Extract Superclass" refactoring +- [`KT-11017`](https://youtrack.jetbrains.com/issue/KT-11017) Implement "Extract Interface" refactoring #### Android Lint diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 5452cca7c4f..6a978864f0c 100755 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -799,6 +799,7 @@ fun main(args: Array) { model("refactoring/introduceTypeParameter", pattern = KT_OR_KTS, testMethod = "doIntroduceTypeParameterTest") model("refactoring/introduceTypeAlias", pattern = KT_OR_KTS, testMethod = "doIntroduceTypeAliasTest") model("refactoring/extractSuperclass", pattern = KT_OR_KTS, testMethod = "doExtractSuperclassTest") + model("refactoring/extractInterface", pattern = KT_OR_KTS, testMethod = "doExtractInterfaceTest") } testClass() { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringSupportProvider.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringSupportProvider.kt index 468f1a1f8f8..e7a7c2915b4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringSupportProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringSupportProvider.kt @@ -22,6 +22,7 @@ import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.refactoring.RefactoringActionHandler import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinChangeSignatureHandler +import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.KotlinExtractInterfaceHandler import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.KotlinExtractSuperclassHandler import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.KotlinIntroduceLambdaParameterHandler @@ -81,6 +82,8 @@ class KotlinRefactoringSupportProvider : RefactoringSupportProvider() { override fun getPushDownHandler() = KotlinPushDownHandler() override fun getExtractSuperClassHandler() = KotlinExtractSuperclassHandler + + override fun getExtractInterfaceHandler() = KotlinExtractInterfaceHandler } class KotlinVetoRenameCondition: Condition { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ExtractSuperclassRefactoring.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ExtractSuperRefactoring.kt similarity index 87% rename from idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ExtractSuperclassRefactoring.kt rename to idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ExtractSuperRefactoring.kt index 1f72c9c370e..5163799f796 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ExtractSuperclassRefactoring.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ExtractSuperRefactoring.kt @@ -68,22 +68,24 @@ import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier import java.util.* -data class ExtractSuperclassInfo( +data class ExtractSuperInfo( val originalClass: KtClassOrObject, val memberInfos: Collection, val targetParent: PsiElement, val targetFileName: String, val newClassName: String, + val isInterface: Boolean, val docPolicy: DocCommentPolicy<*> ) -class ExtractSuperclassRefactoring( - private var extractInfo: ExtractSuperclassInfo +class ExtractSuperRefactoring( + private var extractInfo: ExtractSuperInfo ) { companion object { private fun getElementsToMove( memberInfos: Collection, - originalClass: KtClassOrObject + originalClass: KtClassOrObject, + isExtractInterface: Boolean ): Map { val project = originalClass.project val elementsToMove = LinkedHashMap() @@ -106,7 +108,7 @@ class ExtractSuperclassRefactoring( ?: continue val superClassDescriptor = superType.constructor.declarationDescriptor ?: continue val superClass = DescriptorToSourceUtilsIde.getAnyDeclaration(project, superClassDescriptor) as? KtClass ?: continue - if (!superClass.isInterface() || superClass in superInterfacesToMove) { + if ((!isExtractInterface && !superClass.isInterface()) || superClass in superInterfacesToMove) { elementsToMove[superTypeListEntry] = null } } @@ -119,7 +121,8 @@ class ExtractSuperclassRefactoring( originalClass: KtClassOrObject, memberInfos: List, targetParent: PsiElement, - newClassName: String + newClassName: String, + isExtractInterface: Boolean ): MultiMap { val conflicts = MultiMap() @@ -133,7 +136,7 @@ class ExtractSuperclassRefactoring( ?.let { conflicts.putValue(it, "Class $newClassName already exists in the target scope") } } - val elementsToMove = getElementsToMove(memberInfos, originalClass).keys + val elementsToMove = getElementsToMove(memberInfos, originalClass, isExtractInterface).keys val moveTarget = if (targetParent is PsiDirectory) { val targetPackage = targetParent.getPackage() ?: return conflicts @@ -192,7 +195,7 @@ class ExtractSuperclassRefactoring( collectTypeParameters(refTarget) } } - getElementsToMove(extractInfo.memberInfos, extractInfo.originalClass) + getElementsToMove(extractInfo.memberInfos, extractInfo.originalClass, extractInfo.isInterface) .asSequence() .flatMap { val (element, info) = it @@ -206,18 +209,22 @@ class ExtractSuperclassRefactoring( val newClassName = extractInfo.newClassName val originalClass = extractInfo.originalClass + val kind = if (extractInfo.isInterface) "interface" else "class" + val prototype = psiFactory.createClass("$kind $newClassName") val newClass = if (targetParent is PsiDirectory) { val template = FileTemplateManager.getInstance(project).getInternalTemplate("Kotlin File") val newFile = NewKotlinFileAction.createFileFromTemplate(extractInfo.targetFileName, template, targetParent) as KtFile - newFile.add(psiFactory.createClass("class $newClassName")) as KtClass + newFile.add(prototype) as KtClass } else { val targetSibling = originalClass.parentsWithSelf.first { it.parent == targetParent } - insertDeclaration(psiFactory.createClass("class $newClassName"), targetSibling) + insertDeclaration(prototype, targetSibling) } val shouldBeAbstract = extractInfo.memberInfos.any { it.isToAbstract } - newClass.addModifier(if (shouldBeAbstract) KtTokens.ABSTRACT_KEYWORD else KtTokens.OPEN_KEYWORD) + if (!extractInfo.isInterface) { + newClass.addModifier(if (shouldBeAbstract) KtTokens.ABSTRACT_KEYWORD else KtTokens.OPEN_KEYWORD) + } if (typeParameters.isNotEmpty()) { val typeParameterListText = typeParameters.sortedBy { it.startOffset }.map { it.text }.joinToString(prefix = "<", postfix = ">") @@ -235,10 +242,11 @@ class ExtractSuperclassRefactoring( append(typeParameters.sortedBy { it.startOffset }.map { it.name }.joinToString(prefix = "<", postfix = ">")) } } - val needSuperCall = superClassEntry is KtSuperTypeCallEntry + val needSuperCall = !extractInfo.isInterface + && (superClassEntry is KtSuperTypeCallEntry || originalClass.hasPrimaryConstructor() - || originalClass.getSecondaryConstructors().isEmpty() - val newSuperTypeCallEntry = if (needSuperCall) { + || originalClass.getSecondaryConstructors().isEmpty()) + val newSuperTypeListEntry = if (needSuperCall) { psiFactory.createSuperTypeCallEntry("$superTypeText()") } else { @@ -253,10 +261,10 @@ class ExtractSuperclassRefactoring( } else superClassEntry newClass.addSuperTypeListEntry(superClassEntryToAdd) - ShortenReferences.DEFAULT.process(superClassEntry.replaced(newSuperTypeCallEntry)) + ShortenReferences.DEFAULT.process(superClassEntry.replaced(newSuperTypeListEntry)) } else { - ShortenReferences.DEFAULT.process(originalClass.addSuperTypeListEntry(newSuperTypeCallEntry)) + ShortenReferences.DEFAULT.process(originalClass.addSuperTypeListEntry(newSuperTypeListEntry)) } ShortenReferences.DEFAULT.process(newClass) @@ -267,15 +275,17 @@ class ExtractSuperclassRefactoring( fun performRefactoring() { val originalClass = extractInfo.originalClass - KotlinExtractSuperclassHandler.getErrorMessage(originalClass)?.let { - throw CommonRefactoringUtil.RefactoringErrorHintException(it) - } + val handler = if (extractInfo.isInterface) KotlinExtractInterfaceHandler else KotlinExtractSuperclassHandler + handler.getErrorMessage(originalClass)?.let { throw CommonRefactoringUtil.RefactoringErrorHintException(it) } - val originalClassDescriptor = originalClass.resolveToDescriptor() as ClassDescriptor - val superClassDescriptor = originalClassDescriptor.getSuperClassNotAny() - val superClassEntry = originalClass.getSuperTypeListEntries().firstOrNull { - bindingContext[BindingContext.TYPE, it.typeReference]?.constructor?.declarationDescriptor == superClassDescriptor + val superClassEntry = if (!extractInfo.isInterface) { + val originalClassDescriptor = originalClass.resolveToDescriptor() as ClassDescriptor + val superClassDescriptor = originalClassDescriptor.getSuperClassNotAny() + originalClass.getSuperTypeListEntries().firstOrNull { + bindingContext[BindingContext.TYPE, it.typeReference]?.constructor?.declarationDescriptor == superClassDescriptor + } } + else null project.runSynchronouslyWithProgress(RefactoringBundle.message("progress.text"), true) { runReadAction { analyzeContext() } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/KotlinExtractInterfaceHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/KotlinExtractInterfaceHandler.kt new file mode 100644 index 00000000000..2900d9b445a --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/KotlinExtractInterfaceHandler.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.introduce.extractClass + +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.refactoring.util.CommonRefactoringUtil +import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ui.KotlinExtractInterfaceDialog +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtClassOrObject + +object KotlinExtractInterfaceHandler : KotlinExtractSuperHandlerBase(true) { + val REFACTORING_NAME = "Extract Interface" + + override fun getErrorMessage(klass: KtClassOrObject): String? { + if (klass is KtClass && klass.isAnnotation()) return "Interface cannot be extracted from an annotation class" + return null + } + + override fun doInvoke(klass: KtClassOrObject, targetParent: PsiElement, project: Project, editor: Editor?) { + if (!CommonRefactoringUtil.checkReadOnlyStatus(project, klass)) return + + KotlinExtractInterfaceDialog( + originalClass = klass, + targetParent = targetParent, + conflictChecker = { checkConflicts(klass, it) }, + refactoring = { ExtractSuperRefactoring(it).performRefactoring() } + ).show() + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/KotlinExtractSuperHandlerBase.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/KotlinExtractSuperHandlerBase.kt new file mode 100644 index 00000000000..af4b4e19dcb --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/KotlinExtractSuperHandlerBase.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.introduce.extractClass + +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.ScrollType +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.refactoring.RefactoringActionHandler +import com.intellij.refactoring.extractSuperclass.ExtractSuperClassUtil +import com.intellij.refactoring.lang.ElementsHandler +import org.jetbrains.kotlin.idea.refactoring.SeparateFileWrapper +import org.jetbrains.kotlin.idea.refactoring.chooseContainerElementIfNecessary +import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers +import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ui.KotlinExtractSuperDialogBase +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType + +abstract class KotlinExtractSuperHandlerBase(private val isExtractInterface: Boolean) : RefactoringActionHandler, ElementsHandler { + override fun isEnabledOnElements(elements: Array) = elements.singleOrNull() is KtClassOrObject + + override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) { + val offset = editor.caretModel.offset + val element = file.findElementAt(offset) ?: return + val klass = element.getNonStrictParentOfType() ?: return + editor.scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE) + selectElements(klass, project, editor) + } + + override fun invoke(project: Project, elements: Array, dataContext: DataContext?) { + if (dataContext == null) return + val editor = CommonDataKeys.EDITOR.getData(dataContext) + val klass = PsiTreeUtil.findCommonParent(*elements)?.getNonStrictParentOfType() ?: return + selectElements(klass, project, editor) + } + + fun selectElements(klass: KtClassOrObject, project: Project, editor: Editor?) { + val containers = klass.getExtractionContainers(strict = true, includeAll = true) + SeparateFileWrapper(klass.manager) + + if (editor == null) return doInvoke(klass, containers.first(), project, editor) + + chooseContainerElementIfNecessary( + containers, + editor, + if (containers.first() is KtFile) "Select target file" else "Select target code block / file", + true, + { it }, + { doInvoke(klass, if (it is SeparateFileWrapper) klass.containingFile.parent!! else it, project, editor) } + ) + } + + protected fun checkConflicts(originalClass: KtClassOrObject, dialog: KotlinExtractSuperDialogBase): Boolean { + val conflicts = ExtractSuperRefactoring.collectConflicts( + originalClass, + dialog.selectedMembers, + dialog.selectedTargetParent, + dialog.extractedSuperName, + isExtractInterface + ) + return ExtractSuperClassUtil.showConflicts(dialog, conflicts, originalClass.project) + } + + internal abstract fun getErrorMessage(klass: KtClassOrObject): String? + + protected abstract fun doInvoke(klass: KtClassOrObject, targetParent: PsiElement, project: Project, editor: Editor?) +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/KotlinExtractSuperclassHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/KotlinExtractSuperclassHandler.kt index 7ba723ab223..f4dae1b2d0f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/KotlinExtractSuperclassHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/KotlinExtractSuperclassHandler.kt @@ -16,65 +16,20 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.extractClass -import com.intellij.openapi.actionSystem.CommonDataKeys -import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.editor.Editor -import com.intellij.openapi.editor.ScrollType import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile -import com.intellij.psi.util.PsiTreeUtil import com.intellij.refactoring.HelpID -import com.intellij.refactoring.RefactoringActionHandler import com.intellij.refactoring.RefactoringBundle -import com.intellij.refactoring.extractSuperclass.ExtractSuperClassUtil -import com.intellij.refactoring.lang.ElementsHandler import com.intellij.refactoring.util.CommonRefactoringUtil -import org.jetbrains.kotlin.idea.refactoring.SeparateFileWrapper -import org.jetbrains.kotlin.idea.refactoring.chooseContainerElementIfNecessary -import org.jetbrains.kotlin.idea.refactoring.getExtractionContainers import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ui.KotlinExtractSuperclassDialog import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClassOrObject -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType -object KotlinExtractSuperclassHandler : RefactoringActionHandler, ElementsHandler { +object KotlinExtractSuperclassHandler : KotlinExtractSuperHandlerBase(false) { val REFACTORING_NAME = "Extract Superclass" - override fun isEnabledOnElements(elements: Array) = elements.singleOrNull() is KtClassOrObject - - override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) { - val offset = editor.caretModel.offset - val element = file.findElementAt(offset) ?: return - val klass = element.getNonStrictParentOfType() ?: return - editor.scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE) - selectElements(klass, project, editor) - } - - override fun invoke(project: Project, elements: Array, dataContext: DataContext?) { - if (dataContext == null) return - val editor = CommonDataKeys.EDITOR.getData(dataContext) - val klass = PsiTreeUtil.findCommonParent(*elements)?.getNonStrictParentOfType() ?: return - selectElements(klass, project, editor) - } - - fun selectElements(klass: KtClassOrObject, project: Project, editor: Editor?) { - val containers = klass.getExtractionContainers(strict = true, includeAll = true) + SeparateFileWrapper(klass.manager) - - if (editor == null) return doInvoke(klass, containers.first(), project, editor) - - chooseContainerElementIfNecessary( - containers, - editor, - if (containers.first() is KtFile) "Select target file" else "Select target code block / file", - true, - { it }, - { doInvoke(klass, it, project, editor) } - ) - } - - fun getErrorMessage(klass: KtClassOrObject): String? { + override fun getErrorMessage(klass: KtClassOrObject): String? { if (klass is KtClass) { if (klass.isInterface()) return RefactoringBundle.message("superclass.cannot.be.extracted.from.an.interface") if (klass.isEnum()) return RefactoringBundle.message("superclass.cannot.be.extracted.from.an.enum") @@ -83,30 +38,18 @@ object KotlinExtractSuperclassHandler : RefactoringActionHandler, ElementsHandle return null } - private fun checkConflicts(originalClass: KtClassOrObject, dialog: KotlinExtractSuperclassDialog): Boolean { - val conflicts = ExtractSuperclassRefactoring.collectConflicts( - originalClass, - dialog.selectedMembers, - dialog.selectedTargetParent, - dialog.extractedSuperName - ) - return ExtractSuperClassUtil.showConflicts(dialog, conflicts, originalClass.project) - } - - private fun doInvoke(klass: KtClassOrObject, container: PsiElement, project: Project, editor: Editor?) { + override fun doInvoke(klass: KtClassOrObject, targetParent: PsiElement, project: Project, editor: Editor?) { if (!CommonRefactoringUtil.checkReadOnlyStatus(project, klass)) return getErrorMessage(klass)?.let { CommonRefactoringUtil.showErrorHint(project, editor, RefactoringBundle.getCannotRefactorMessage(it), REFACTORING_NAME, HelpID.EXTRACT_SUPERCLASS) } - val targetParent = (if (container is SeparateFileWrapper) klass.containingFile.parent else container) ?: return - KotlinExtractSuperclassDialog( originalClass = klass, targetParent = targetParent, conflictChecker = { checkConflicts(klass, it) }, - refactoring = { ExtractSuperclassRefactoring(it).performRefactoring() } + refactoring = { ExtractSuperRefactoring(it).performRefactoring() } ).show() } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractInterfaceDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractInterfaceDialog.kt new file mode 100644 index 00000000000..981484445dd --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractInterfaceDialog.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ui + +import com.intellij.psi.PsiElement +import com.intellij.refactoring.HelpID +import com.intellij.refactoring.JavaRefactoringSettings +import com.intellij.refactoring.RefactoringBundle +import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperInfo +import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.KotlinExtractInterfaceHandler +import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo +import org.jetbrains.kotlin.idea.refactoring.memberInfo.extractClassMembers +import org.jetbrains.kotlin.idea.refactoring.pullUp.mustBeAbstractInInterface +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtProperty + +class KotlinExtractInterfaceDialog( + originalClass: KtClassOrObject, + targetParent: PsiElement, + conflictChecker: (KotlinExtractSuperDialogBase) -> Boolean, + refactoring: (ExtractSuperInfo) -> Unit +) : KotlinExtractSuperDialogBase(originalClass, targetParent, conflictChecker, true, KotlinExtractInterfaceHandler.REFACTORING_NAME, refactoring) { + companion object { + private val DESTINATION_PACKAGE_RECENT_KEY = "KotlinExtractInterfaceDialog.RECENT_KEYS" + } + + init { + init() + } + + override fun createMemberInfoModel(): MemberInfoModelBase { + val extractableMemberInfos = extractClassMembers(originalClass).filterNot { + val member = it.member + member is KtClass && member.hasModifier(KtTokens.INNER_KEYWORD) + } + extractableMemberInfos.forEach { it.isToAbstract = true } + return object : MemberInfoModelBase(extractableMemberInfos) { + override fun isAbstractEnabled(memberInfo: KotlinMemberInfo): Boolean { + val member = memberInfo.member + return member is KtNamedFunction || (member is KtProperty && !member.mustBeAbstractInInterface()) + } + + override fun isAbstractWhenDisabled(member: KotlinMemberInfo) = member.member is KtProperty + } + } + + override fun getDestinationPackageRecentKey() = DESTINATION_PACKAGE_RECENT_KEY + + override fun getClassNameLabelText() = RefactoringBundle.message("interface.name.prompt")!! + + override fun getPackageNameLabelText() = RefactoringBundle.message("package.for.new.interface")!! + + override fun getEntityName() = RefactoringBundle.message("extractSuperInterface.interface")!! + + override fun getTopLabelText() = RefactoringBundle.message("extract.interface.from")!! + + override fun getDocCommentPolicySetting() = JavaRefactoringSettings.getInstance().EXTRACT_INTERFACE_JAVADOC + + override fun setDocCommentPolicySetting(policy: Int) { + JavaRefactoringSettings.getInstance().EXTRACT_INTERFACE_JAVADOC = policy + } + + override fun getExtractedSuperNameNotSpecifiedMessage() = RefactoringBundle.message("no.interface.name.specified")!! + + override fun getHelpId() = HelpID.EXTRACT_INTERFACE + + override fun createExtractedSuperNameField() = super.createExtractedSuperNameField()!!.apply { text = "I${originalClass.name}" } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractSuperDialogBase.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractSuperDialogBase.kt new file mode 100644 index 00000000000..23114aec48e --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractSuperDialogBase.kt @@ -0,0 +1,144 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ui + +import com.intellij.psi.PsiComment +import com.intellij.psi.PsiDirectory +import com.intellij.psi.PsiElement +import com.intellij.refactoring.RefactoringBundle +import com.intellij.refactoring.classMembers.AbstractMemberInfoModel +import com.intellij.refactoring.classMembers.MemberInfoChange +import com.intellij.refactoring.extractSuperclass.JavaExtractSuperBaseDialog +import com.intellij.refactoring.util.DocCommentPolicy +import com.intellij.refactoring.util.RefactoringMessageUtil +import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.asJava.unwrapped +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.core.KotlinNameSuggester +import org.jetbrains.kotlin.idea.core.quoteIfNeeded +import org.jetbrains.kotlin.idea.core.unquote +import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperInfo +import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo +import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionPanel +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import java.awt.BorderLayout +import javax.swing.Box +import javax.swing.JComponent +import javax.swing.JPanel +import javax.swing.JTextField + +abstract class KotlinExtractSuperDialogBase( + protected val originalClass: KtClassOrObject, + protected val targetParent: PsiElement, + private val conflictChecker: (KotlinExtractSuperDialogBase) -> Boolean, + private val isExtractInterface: Boolean, + refactoringName: String, + private val refactoring: (ExtractSuperInfo) -> Unit +) : JavaExtractSuperBaseDialog(originalClass.project, originalClass.toLightClass()!!, emptyList(), refactoringName) { + private lateinit var memberInfoModel: MemberInfoModelBase + + val selectedMembers: List + get() = memberInfoModel.memberInfos.filter { it.isChecked } + + private val fileNameField = JTextField() + + open class MemberInfoModelBase( + val memberInfos: List + ) : AbstractMemberInfoModel() { + override fun isFixedAbstract(memberInfo: KotlinMemberInfo?) = true + } + + val selectedTargetParent: PsiElement + get() = if (targetParent is PsiDirectory) targetDirectory else targetParent + + val targetFileName: String + get() = fileNameField.text + + protected abstract fun createMemberInfoModel(): MemberInfoModelBase + + override fun getDocCommentPanelName() = "KDoc for abstracts" + + override fun checkConflicts() = conflictChecker(this) + + override fun createActionComponent() = Box.createHorizontalBox()!! + + override fun createDestinationRootPanel() = if (targetParent is PsiDirectory) super.createDestinationRootPanel() else null + + override fun createNorthPanel(): JComponent? { + return super.createNorthPanel().apply { + if (targetParent !is PsiDirectory) { + myPackageNameLabel.parent.remove(myPackageNameLabel) + myPackageNameField.parent.remove(myPackageNameField) + } + } + } + + override fun createCenterPanel(): JComponent? { + memberInfoModel = createMemberInfoModel().apply { + memberInfoChanged(MemberInfoChange(memberInfos)) + } + + return JPanel(BorderLayout()).apply { + val memberSelectionPanel = KotlinMemberSelectionPanel( + RefactoringBundle.message(if (isExtractInterface) "members.to.form.interface" else "members.to.form.superclass"), + memberInfoModel.memberInfos, + RefactoringBundle.message("make.abstract") + ) + memberSelectionPanel.table.memberInfoModel = memberInfoModel + memberSelectionPanel.table.addMemberInfoChangeListener(memberInfoModel) + add(memberSelectionPanel, BorderLayout.CENTER) + + add(myDocCommentPanel, BorderLayout.EAST) + } + } + + override fun init() { + super.init() + + fileNameField.text = "$extractedSuperName.${KotlinFileType.EXTENSION}" + } + + override fun preparePackage() { + if (targetParent is PsiDirectory) super.preparePackage() + } + + override fun isExtractSuperclass() = true + + override fun validateName(name: String): String? { + return when { + !KotlinNameSuggester.isIdentifier(name.quoteIfNeeded()) -> RefactoringMessageUtil.getIncorrectIdentifierMessage(name) + name.unquote() == mySourceClass.name -> "Different name expected" + else -> null + } + } + + override fun createProcessor() = null + + override fun executeRefactoring() { + val extractInfo = ExtractSuperInfo( + mySourceClass.unwrapped as KtClassOrObject, + selectedMembers, + if (targetParent is PsiDirectory) targetDirectory else targetParent, + targetFileName, + extractedSuperName.quoteIfNeeded(), + isExtractInterface, + DocCommentPolicy(docCommentPolicy) + ) + refactoring(extractInfo) + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractSuperclassDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractSuperclassDialog.kt index a9e95e44c46..71487a98ec8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractSuperclassDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractSuperclassDialog.kt @@ -16,81 +16,39 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ui -import com.intellij.psi.PsiComment -import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiElement import com.intellij.refactoring.HelpID import com.intellij.refactoring.JavaRefactoringSettings import com.intellij.refactoring.RefactoringBundle -import com.intellij.refactoring.classMembers.AbstractMemberInfoModel -import com.intellij.refactoring.classMembers.MemberInfoChange -import com.intellij.refactoring.extractSuperclass.ExtractSuperBaseDialog -import com.intellij.refactoring.extractSuperclass.JavaExtractSuperBaseDialog -import com.intellij.refactoring.util.DocCommentPolicy -import com.intellij.refactoring.util.RefactoringMessageUtil -import com.intellij.ui.components.JBLabel -import com.intellij.util.ui.FormBuilder -import org.jetbrains.kotlin.asJava.toLightClass -import org.jetbrains.kotlin.asJava.unwrapped -import org.jetbrains.kotlin.idea.KotlinFileType -import org.jetbrains.kotlin.idea.core.KotlinNameSuggester -import org.jetbrains.kotlin.idea.core.quoteIfNeeded -import org.jetbrains.kotlin.idea.core.unquote -import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperclassInfo +import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperInfo import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.KotlinExtractSuperclassHandler import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo -import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionPanel import org.jetbrains.kotlin.idea.refactoring.memberInfo.extractClassMembers import org.jetbrains.kotlin.psi.KtClassOrObject -import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtProperty -import java.awt.BorderLayout -import javax.swing.* class KotlinExtractSuperclassDialog( originalClass: KtClassOrObject, - private val targetParent: PsiElement, - private val conflictChecker: (KotlinExtractSuperclassDialog) -> Boolean, - private val refactoring: (ExtractSuperclassInfo) -> Unit -) : JavaExtractSuperBaseDialog( - originalClass.project, - originalClass.toLightClass()!!, - emptyList(), - KotlinExtractSuperclassHandler.REFACTORING_NAME -) { + targetParent: PsiElement, + conflictChecker: (KotlinExtractSuperDialogBase) -> Boolean, + refactoring: (ExtractSuperInfo) -> Unit +) : KotlinExtractSuperDialogBase(originalClass, targetParent, conflictChecker, false, KotlinExtractSuperclassHandler.REFACTORING_NAME, refactoring) { companion object { private val DESTINATION_PACKAGE_RECENT_KEY = "KotlinExtractSuperclassDialog.RECENT_KEYS" } - val kotlinMemberInfos = extractClassMembers(originalClass) - - val selectedMembers: List - get() = kotlinMemberInfos.filter { it.isChecked } - - private val fileNameField = JTextField() - - private val memberInfoModel = object : AbstractMemberInfoModel() { - override fun isFixedAbstract(memberInfo: KotlinMemberInfo?) = true - - override fun isAbstractEnabled(memberInfo: KotlinMemberInfo): Boolean { - val member = memberInfo.member - return member is KtNamedFunction || member is KtProperty - } - }.apply { - memberInfoChanged(MemberInfoChange(kotlinMemberInfos)) - } - - val selectedTargetParent: PsiElement - get() = if (targetParent is PsiDirectory) targetDirectory else targetParent - - val targetFileName: String - get() = fileNameField.text - init { init() + } - fileNameField.text = "$extractedSuperName.${KotlinFileType.EXTENSION}" + override fun createMemberInfoModel(): MemberInfoModelBase { + return object : MemberInfoModelBase(extractClassMembers(originalClass)) { + override fun isAbstractEnabled(memberInfo: KotlinMemberInfo): Boolean { + val member = memberInfo.member + return member is KtNamedFunction || member is KtProperty + } + } } override fun getDestinationPackageRecentKey() = DESTINATION_PACKAGE_RECENT_KEY @@ -109,94 +67,7 @@ class KotlinExtractSuperclassDialog( JavaRefactoringSettings.getInstance().EXTRACT_SUPERCLASS_JAVADOC = policy } - override fun getDocCommentPanelName() = "KDoc for abstracts" - override fun getExtractedSuperNameNotSpecifiedMessage() = RefactoringBundle.message("no.superclass.name.specified")!! override fun getHelpId() = HelpID.EXTRACT_SUPERCLASS - - override fun validateName(name: String): String? { - return when { - !KotlinNameSuggester.isIdentifier(name.quoteIfNeeded()) -> RefactoringMessageUtil.getIncorrectIdentifierMessage(name) - name.unquote() == mySourceClass.name -> "Different name expected" - else -> null - } - } - - override fun checkConflicts() = conflictChecker(this) - - override fun createActionComponent() = Box.createHorizontalBox()!! - - override fun createDestinationRootPanel(): JPanel? { - if (targetParent !is PsiDirectory) return null - - val targetDirectoryPanel = super.createDestinationRootPanel() - val targetFileNamePanel = JPanel(BorderLayout()).apply { - border = BorderFactory.createEmptyBorder(10, 0, 0, 0) - val label = JBLabel("Target file name:") - add(label, BorderLayout.NORTH) - label.labelFor = fileNameField - add(fileNameField, BorderLayout.CENTER) - } - - return FormBuilder - .createFormBuilder() - .addComponent(targetDirectoryPanel) - .addComponent(targetFileNamePanel) - .panel - } - - override fun createNorthPanel(): JComponent? { - return super.createNorthPanel().apply { - if (targetParent !is PsiDirectory) { - myPackageNameLabel.parent.remove(myPackageNameLabel) - myPackageNameField.parent.remove(myPackageNameField) - } - } - } - - override fun createCenterPanel(): JComponent? { - return JPanel(BorderLayout()).apply { - val memberSelectionPanel = KotlinMemberSelectionPanel( - RefactoringBundle.message("members.to.form.superclass"), - kotlinMemberInfos, - RefactoringBundle.message("make.abstract") - ) - memberSelectionPanel.table.memberInfoModel = memberInfoModel - memberSelectionPanel.table.addMemberInfoChangeListener(memberInfoModel) - add(memberSelectionPanel, BorderLayout.CENTER) - - add(myDocCommentPanel, BorderLayout.EAST) - } - } - - override fun isExtractSuperclass() = true - - override fun preparePackage() { - if (targetParent !is PsiDirectory) return - - super.preparePackage() - - val fileName = targetFileName - if (!fileName.endsWith(".${KotlinFileType.EXTENSION}")) { - throw ExtractSuperBaseDialog.OperationFailedException("Invalid Kotlin file name: $fileName") - } - RefactoringMessageUtil.checkCanCreateFile(myTargetDirectory, fileName)?.let { - throw ExtractSuperBaseDialog.OperationFailedException(it) - } - } - - override fun createProcessor() = null - - override fun executeRefactoring() { - val extractInfo = ExtractSuperclassInfo( - mySourceClass.unwrapped as KtClassOrObject, - selectedMembers, - if (targetParent is PsiDirectory) targetDirectory else targetParent, - targetFileName, - extractedSuperName.quoteIfNeeded(), - DocCommentPolicy(docCommentPolicy) - ) - refactoring(extractInfo) - } } \ No newline at end of file diff --git a/idea/testData/refactoring/extractInterface/addInterface.kt b/idea/testData/refactoring/extractInterface/addInterface.kt new file mode 100644 index 00000000000..ce87666f240 --- /dev/null +++ b/idea/testData/refactoring/extractInterface/addInterface.kt @@ -0,0 +1,13 @@ +// NAME: X +// INFO: {checked: "true"} +interface T {} + +open class A + +// SIBLING: +class B : A(), T { + // INFO: {checked: "true"} + fun foo() { + + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractInterface/addInterface.kt.after b/idea/testData/refactoring/extractInterface/addInterface.kt.after new file mode 100644 index 00000000000..359c296f3e5 --- /dev/null +++ b/idea/testData/refactoring/extractInterface/addInterface.kt.after @@ -0,0 +1,16 @@ +// NAME: X +// INFO: {checked: "true"} +interface T {} + +open class A + +interface X : T { + // INFO: {checked: "true"} + fun foo() { + + } +} + +// SIBLING: +class B : A(), X { +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractInterface/addInterfaceWithAbstract.kt b/idea/testData/refactoring/extractInterface/addInterfaceWithAbstract.kt new file mode 100644 index 00000000000..9ba900fd114 --- /dev/null +++ b/idea/testData/refactoring/extractInterface/addInterfaceWithAbstract.kt @@ -0,0 +1,13 @@ +// NAME: X +// INFO: {checked: "true"} +interface T {} + +open class A + +// SIBLING: +class B : A(), T { + // INFO: {checked: "true", toAbstract: "true"} + fun foo() { + + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractInterface/addInterfaceWithAbstract.kt.after b/idea/testData/refactoring/extractInterface/addInterfaceWithAbstract.kt.after new file mode 100644 index 00000000000..95946975f4f --- /dev/null +++ b/idea/testData/refactoring/extractInterface/addInterfaceWithAbstract.kt.after @@ -0,0 +1,18 @@ +// NAME: X +// INFO: {checked: "true"} +interface T {} + +open class A + +interface X : T { + // INFO: {checked: "true", toAbstract: "true"} + fun foo() +} + +// SIBLING: +class B : A(), X { + // INFO: {checked: "true", toAbstract: "true"} + override fun foo() { + + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractInterface/addTypeParameters.kt b/idea/testData/refactoring/extractInterface/addTypeParameters.kt new file mode 100644 index 00000000000..91d56c698e3 --- /dev/null +++ b/idea/testData/refactoring/extractInterface/addTypeParameters.kt @@ -0,0 +1,14 @@ +// NAME: B + +// INFO: {checked: "true"} +interface I + +open class J + +// SIBLING: +class A, V, W, X> : J(), I { + // INFO: {checked: "true"} + fun foo(u: U) { + + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractInterface/addTypeParameters.kt.after b/idea/testData/refactoring/extractInterface/addTypeParameters.kt.after new file mode 100644 index 00000000000..501eba42ad5 --- /dev/null +++ b/idea/testData/refactoring/extractInterface/addTypeParameters.kt.after @@ -0,0 +1,17 @@ +// NAME: B + +// INFO: {checked: "true"} +interface I + +open class J + +interface B, W> : I { + // INFO: {checked: "true"} + fun foo(u: U) { + + } +} + +// SIBLING: +class A, V, W, X> : J(), B { +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractInterface/addTypeParametersWithAbstract.kt b/idea/testData/refactoring/extractInterface/addTypeParametersWithAbstract.kt new file mode 100644 index 00000000000..34758d2e4c7 --- /dev/null +++ b/idea/testData/refactoring/extractInterface/addTypeParametersWithAbstract.kt @@ -0,0 +1,14 @@ +// NAME: B + +// INFO: {checked: "true"} +interface I + +open class J + +// SIBLING: +class A, V, W, X> : J(), I { + // INFO: {checked: "true", toAbstract: "true"} + fun foo() { + val u: U + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractInterface/addTypeParametersWithAbstract.kt.after b/idea/testData/refactoring/extractInterface/addTypeParametersWithAbstract.kt.after new file mode 100644 index 00000000000..8c3b1fe6576 --- /dev/null +++ b/idea/testData/refactoring/extractInterface/addTypeParametersWithAbstract.kt.after @@ -0,0 +1,19 @@ +// NAME: B + +// INFO: {checked: "true"} +interface I + +open class J + +interface B : I { + // INFO: {checked: "true", toAbstract: "true"} + fun foo() +} + +// SIBLING: +class A, V, W, X> : J(), B { + // INFO: {checked: "true", toAbstract: "true"} + override fun foo() { + val u: U + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/extractInterface/annotation.kt b/idea/testData/refactoring/extractInterface/annotation.kt new file mode 100644 index 00000000000..0e3439dc8b1 --- /dev/null +++ b/idea/testData/refactoring/extractInterface/annotation.kt @@ -0,0 +1,3 @@ +// NAME: B +// SIBLING: +annotation class A \ No newline at end of file diff --git a/idea/testData/refactoring/extractInterface/annotation.kt.conflicts b/idea/testData/refactoring/extractInterface/annotation.kt.conflicts new file mode 100644 index 00000000000..d1ee1b2ed3b --- /dev/null +++ b/idea/testData/refactoring/extractInterface/annotation.kt.conflicts @@ -0,0 +1 @@ +Interface cannot be extracted from an annotation class \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt index 60ee6111245..39f528cc6e5 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractExtractionTest.kt @@ -42,8 +42,8 @@ import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively import org.jetbrains.kotlin.idea.refactoring.chooseMembers -import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperclassInfo -import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperclassRefactoring +import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperInfo +import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperRefactoring import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.EXTRACT_FUNCTION import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.* @@ -354,7 +354,7 @@ abstract class AbstractExtractionTest() : KotlinLightCodeInsightFixtureTestCase( } } - protected fun doExtractSuperclassTest(path: String) { + protected fun doExtractSuperTest(path: String, isInterface: Boolean) { doTest(path) { file -> file as KtFile @@ -366,21 +366,26 @@ abstract class AbstractExtractionTest() : KotlinLightCodeInsightFixtureTestCase( val editor = fixture.editor val originalClass = file.findElementAt(editor.caretModel.offset)?.getStrictParentOfType()!! val memberInfos = chooseMembers(extractClassMembers(originalClass)) - val conflicts = ExtractSuperclassRefactoring.collectConflicts(originalClass, memberInfos, targetParent, className) + val conflicts = ExtractSuperRefactoring.collectConflicts(originalClass, memberInfos, targetParent, className, isInterface) project.checkConflictsInteractively(conflicts) { - val extractInfo = ExtractSuperclassInfo( + val extractInfo = ExtractSuperInfo( originalClass, memberInfos, targetParent, "$className.${KotlinFileType.EXTENSION}", className, + isInterface, DocCommentPolicy(DocCommentPolicy.ASIS) ) - ExtractSuperclassRefactoring(extractInfo).performRefactoring() + ExtractSuperRefactoring(extractInfo).performRefactoring() } } } + protected fun doExtractSuperclassTest(path: String) = doExtractSuperTest(path, false) + + protected fun doExtractInterfaceTest(path: String) = doExtractSuperTest(path, true) + protected fun doTest(path: String, checkAdditionalAfterdata: Boolean = false, action: (PsiFile) -> Unit) { val mainFile = File(path) val afterFile = File("$path.after") diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java index bd2980a5b16..092f25de0c0 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java @@ -4258,4 +4258,43 @@ public class ExtractionTestGenerated extends AbstractExtractionTest { doExtractSuperclassTest(fileName); } } + + @TestMetadata("idea/testData/refactoring/extractInterface") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ExtractInterface extends AbstractExtractionTest { + @TestMetadata("addInterface.kt") + public void testAddInterface() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractInterface/addInterface.kt"); + doExtractInterfaceTest(fileName); + } + + @TestMetadata("addInterfaceWithAbstract.kt") + public void testAddInterfaceWithAbstract() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractInterface/addInterfaceWithAbstract.kt"); + doExtractInterfaceTest(fileName); + } + + @TestMetadata("addTypeParameters.kt") + public void testAddTypeParameters() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractInterface/addTypeParameters.kt"); + doExtractInterfaceTest(fileName); + } + + @TestMetadata("addTypeParametersWithAbstract.kt") + public void testAddTypeParametersWithAbstract() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractInterface/addTypeParametersWithAbstract.kt"); + doExtractInterfaceTest(fileName); + } + + public void testAllFilesPresentInExtractInterface() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/extractInterface"), Pattern.compile("^(.+)\\.(kt|kts)$"), true); + } + + @TestMetadata("annotation.kt") + public void testAnnotation() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractInterface/annotation.kt"); + doExtractInterfaceTest(fileName); + } + } }