Extract Class: Implement 'Extract Interface' refactoring

#KT-11017 Fixed
This commit is contained in:
Alexey Sedunov
2016-09-22 14:29:03 +03:00
parent af2de09840
commit b412edf2a3
22 changed files with 592 additions and 232 deletions
+1
View File
@@ -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-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 Superclass" refactoring
- [`KT-11017`](https://youtrack.jetbrains.com/issue/KT-11017) Implement "Extract Interface" refactoring
#### Android Lint #### Android Lint
@@ -799,6 +799,7 @@ fun main(args: Array<String>) {
model("refactoring/introduceTypeParameter", pattern = KT_OR_KTS, testMethod = "doIntroduceTypeParameterTest") model("refactoring/introduceTypeParameter", pattern = KT_OR_KTS, testMethod = "doIntroduceTypeParameterTest")
model("refactoring/introduceTypeAlias", pattern = KT_OR_KTS, testMethod = "doIntroduceTypeAliasTest") model("refactoring/introduceTypeAlias", pattern = KT_OR_KTS, testMethod = "doIntroduceTypeAliasTest")
model("refactoring/extractSuperclass", pattern = KT_OR_KTS, testMethod = "doExtractSuperclassTest") model("refactoring/extractSuperclass", pattern = KT_OR_KTS, testMethod = "doExtractSuperclassTest")
model("refactoring/extractInterface", pattern = KT_OR_KTS, testMethod = "doExtractInterfaceTest")
} }
testClass<AbstractPullUpTest>() { testClass<AbstractPullUpTest>() {
@@ -22,6 +22,7 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.refactoring.RefactoringActionHandler import com.intellij.refactoring.RefactoringActionHandler
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinChangeSignatureHandler 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.extractFunction.ExtractKotlinFunctionHandler
import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.KotlinExtractSuperclassHandler import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.KotlinExtractSuperclassHandler
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.KotlinIntroduceLambdaParameterHandler import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.KotlinIntroduceLambdaParameterHandler
@@ -81,6 +82,8 @@ class KotlinRefactoringSupportProvider : RefactoringSupportProvider() {
override fun getPushDownHandler() = KotlinPushDownHandler() override fun getPushDownHandler() = KotlinPushDownHandler()
override fun getExtractSuperClassHandler() = KotlinExtractSuperclassHandler override fun getExtractSuperClassHandler() = KotlinExtractSuperclassHandler
override fun getExtractInterfaceHandler() = KotlinExtractInterfaceHandler
} }
class KotlinVetoRenameCondition: Condition<PsiElement> { class KotlinVetoRenameCondition: Condition<PsiElement> {
@@ -68,22 +68,24 @@ import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
import java.util.* import java.util.*
data class ExtractSuperclassInfo( data class ExtractSuperInfo(
val originalClass: KtClassOrObject, val originalClass: KtClassOrObject,
val memberInfos: Collection<KotlinMemberInfo>, val memberInfos: Collection<KotlinMemberInfo>,
val targetParent: PsiElement, val targetParent: PsiElement,
val targetFileName: String, val targetFileName: String,
val newClassName: String, val newClassName: String,
val isInterface: Boolean,
val docPolicy: DocCommentPolicy<*> val docPolicy: DocCommentPolicy<*>
) )
class ExtractSuperclassRefactoring( class ExtractSuperRefactoring(
private var extractInfo: ExtractSuperclassInfo private var extractInfo: ExtractSuperInfo
) { ) {
companion object { companion object {
private fun getElementsToMove( private fun getElementsToMove(
memberInfos: Collection<KotlinMemberInfo>, memberInfos: Collection<KotlinMemberInfo>,
originalClass: KtClassOrObject originalClass: KtClassOrObject,
isExtractInterface: Boolean
): Map<KtElement, KotlinMemberInfo?> { ): Map<KtElement, KotlinMemberInfo?> {
val project = originalClass.project val project = originalClass.project
val elementsToMove = LinkedHashMap<KtElement, KotlinMemberInfo?>() val elementsToMove = LinkedHashMap<KtElement, KotlinMemberInfo?>()
@@ -106,7 +108,7 @@ class ExtractSuperclassRefactoring(
?: continue ?: continue
val superClassDescriptor = superType.constructor.declarationDescriptor ?: continue val superClassDescriptor = superType.constructor.declarationDescriptor ?: continue
val superClass = DescriptorToSourceUtilsIde.getAnyDeclaration(project, superClassDescriptor) as? KtClass ?: 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 elementsToMove[superTypeListEntry] = null
} }
} }
@@ -119,7 +121,8 @@ class ExtractSuperclassRefactoring(
originalClass: KtClassOrObject, originalClass: KtClassOrObject,
memberInfos: List<KotlinMemberInfo>, memberInfos: List<KotlinMemberInfo>,
targetParent: PsiElement, targetParent: PsiElement,
newClassName: String newClassName: String,
isExtractInterface: Boolean
): MultiMap<PsiElement, String> { ): MultiMap<PsiElement, String> {
val conflicts = MultiMap<PsiElement, String>() val conflicts = MultiMap<PsiElement, String>()
@@ -133,7 +136,7 @@ class ExtractSuperclassRefactoring(
?.let { conflicts.putValue(it, "Class $newClassName already exists in the target scope") } ?.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 moveTarget = if (targetParent is PsiDirectory) {
val targetPackage = targetParent.getPackage() ?: return conflicts val targetPackage = targetParent.getPackage() ?: return conflicts
@@ -192,7 +195,7 @@ class ExtractSuperclassRefactoring(
collectTypeParameters(refTarget) collectTypeParameters(refTarget)
} }
} }
getElementsToMove(extractInfo.memberInfos, extractInfo.originalClass) getElementsToMove(extractInfo.memberInfos, extractInfo.originalClass, extractInfo.isInterface)
.asSequence() .asSequence()
.flatMap { .flatMap {
val (element, info) = it val (element, info) = it
@@ -206,18 +209,22 @@ class ExtractSuperclassRefactoring(
val newClassName = extractInfo.newClassName val newClassName = extractInfo.newClassName
val originalClass = extractInfo.originalClass 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 newClass = if (targetParent is PsiDirectory) {
val template = FileTemplateManager.getInstance(project).getInternalTemplate("Kotlin File") val template = FileTemplateManager.getInstance(project).getInternalTemplate("Kotlin File")
val newFile = NewKotlinFileAction.createFileFromTemplate(extractInfo.targetFileName, template, targetParent) as KtFile val newFile = NewKotlinFileAction.createFileFromTemplate(extractInfo.targetFileName, template, targetParent) as KtFile
newFile.add(psiFactory.createClass("class $newClassName")) as KtClass newFile.add(prototype) as KtClass
} }
else { else {
val targetSibling = originalClass.parentsWithSelf.first { it.parent == targetParent } val targetSibling = originalClass.parentsWithSelf.first { it.parent == targetParent }
insertDeclaration(psiFactory.createClass("class $newClassName"), targetSibling) insertDeclaration(prototype, targetSibling)
} }
val shouldBeAbstract = extractInfo.memberInfos.any { it.isToAbstract } 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()) { if (typeParameters.isNotEmpty()) {
val typeParameterListText = typeParameters.sortedBy { it.startOffset }.map { it.text }.joinToString(prefix = "<", postfix = ">") 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 = ">")) 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.hasPrimaryConstructor()
|| originalClass.getSecondaryConstructors().isEmpty() || originalClass.getSecondaryConstructors().isEmpty())
val newSuperTypeCallEntry = if (needSuperCall) { val newSuperTypeListEntry = if (needSuperCall) {
psiFactory.createSuperTypeCallEntry("$superTypeText()") psiFactory.createSuperTypeCallEntry("$superTypeText()")
} }
else { else {
@@ -253,10 +261,10 @@ class ExtractSuperclassRefactoring(
} }
else superClassEntry else superClassEntry
newClass.addSuperTypeListEntry(superClassEntryToAdd) newClass.addSuperTypeListEntry(superClassEntryToAdd)
ShortenReferences.DEFAULT.process(superClassEntry.replaced(newSuperTypeCallEntry)) ShortenReferences.DEFAULT.process(superClassEntry.replaced(newSuperTypeListEntry))
} }
else { else {
ShortenReferences.DEFAULT.process(originalClass.addSuperTypeListEntry(newSuperTypeCallEntry)) ShortenReferences.DEFAULT.process(originalClass.addSuperTypeListEntry(newSuperTypeListEntry))
} }
ShortenReferences.DEFAULT.process(newClass) ShortenReferences.DEFAULT.process(newClass)
@@ -267,15 +275,17 @@ class ExtractSuperclassRefactoring(
fun performRefactoring() { fun performRefactoring() {
val originalClass = extractInfo.originalClass val originalClass = extractInfo.originalClass
KotlinExtractSuperclassHandler.getErrorMessage(originalClass)?.let { val handler = if (extractInfo.isInterface) KotlinExtractInterfaceHandler else KotlinExtractSuperclassHandler
throw CommonRefactoringUtil.RefactoringErrorHintException(it) handler.getErrorMessage(originalClass)?.let { throw CommonRefactoringUtil.RefactoringErrorHintException(it) }
}
val originalClassDescriptor = originalClass.resolveToDescriptor() as ClassDescriptor val superClassEntry = if (!extractInfo.isInterface) {
val superClassDescriptor = originalClassDescriptor.getSuperClassNotAny() val originalClassDescriptor = originalClass.resolveToDescriptor() as ClassDescriptor
val superClassEntry = originalClass.getSuperTypeListEntries().firstOrNull { val superClassDescriptor = originalClassDescriptor.getSuperClassNotAny()
bindingContext[BindingContext.TYPE, it.typeReference]?.constructor?.declarationDescriptor == superClassDescriptor originalClass.getSuperTypeListEntries().firstOrNull {
bindingContext[BindingContext.TYPE, it.typeReference]?.constructor?.declarationDescriptor == superClassDescriptor
}
} }
else null
project.runSynchronouslyWithProgress(RefactoringBundle.message("progress.text"), true) { runReadAction { analyzeContext() } } project.runSynchronouslyWithProgress(RefactoringBundle.message("progress.text"), true) { runReadAction { analyzeContext() } }
@@ -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()
}
}
@@ -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<out PsiElement>) = 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<KtClassOrObject>() ?: return
editor.scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE)
selectElements(klass, project, editor)
}
override fun invoke(project: Project, elements: Array<out PsiElement>, dataContext: DataContext?) {
if (dataContext == null) return
val editor = CommonDataKeys.EDITOR.getData(dataContext)
val klass = PsiTreeUtil.findCommonParent(*elements)?.getNonStrictParentOfType<KtClassOrObject>() ?: 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?)
}
@@ -16,65 +16,20 @@
package org.jetbrains.kotlin.idea.refactoring.introduce.extractClass 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.Editor
import com.intellij.openapi.editor.ScrollType
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement 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.HelpID
import com.intellij.refactoring.RefactoringActionHandler
import com.intellij.refactoring.RefactoringBundle import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.extractSuperclass.ExtractSuperClassUtil
import com.intellij.refactoring.lang.ElementsHandler
import com.intellij.refactoring.util.CommonRefactoringUtil 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.idea.refactoring.introduce.extractClass.ui.KotlinExtractSuperclassDialog
import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassOrObject 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" val REFACTORING_NAME = "Extract Superclass"
override fun isEnabledOnElements(elements: Array<out PsiElement>) = elements.singleOrNull() is KtClassOrObject override fun getErrorMessage(klass: KtClassOrObject): String? {
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<KtClassOrObject>() ?: return
editor.scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE)
selectElements(klass, project, editor)
}
override fun invoke(project: Project, elements: Array<out PsiElement>, dataContext: DataContext?) {
if (dataContext == null) return
val editor = CommonDataKeys.EDITOR.getData(dataContext)
val klass = PsiTreeUtil.findCommonParent(*elements)?.getNonStrictParentOfType<KtClassOrObject>() ?: 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? {
if (klass is KtClass) { if (klass is KtClass) {
if (klass.isInterface()) return RefactoringBundle.message("superclass.cannot.be.extracted.from.an.interface") 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") if (klass.isEnum()) return RefactoringBundle.message("superclass.cannot.be.extracted.from.an.enum")
@@ -83,30 +38,18 @@ object KotlinExtractSuperclassHandler : RefactoringActionHandler, ElementsHandle
return null return null
} }
private fun checkConflicts(originalClass: KtClassOrObject, dialog: KotlinExtractSuperclassDialog): Boolean { override fun doInvoke(klass: KtClassOrObject, targetParent: PsiElement, project: Project, editor: Editor?) {
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?) {
if (!CommonRefactoringUtil.checkReadOnlyStatus(project, klass)) return if (!CommonRefactoringUtil.checkReadOnlyStatus(project, klass)) return
getErrorMessage(klass)?.let { getErrorMessage(klass)?.let {
CommonRefactoringUtil.showErrorHint(project, editor, RefactoringBundle.getCannotRefactorMessage(it), REFACTORING_NAME, HelpID.EXTRACT_SUPERCLASS) 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( KotlinExtractSuperclassDialog(
originalClass = klass, originalClass = klass,
targetParent = targetParent, targetParent = targetParent,
conflictChecker = { checkConflicts(klass, it) }, conflictChecker = { checkConflicts(klass, it) },
refactoring = { ExtractSuperclassRefactoring(it).performRefactoring() } refactoring = { ExtractSuperRefactoring(it).performRefactoring() }
).show() ).show()
} }
} }
@@ -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}" }
}
@@ -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<KotlinMemberInfo>
get() = memberInfoModel.memberInfos.filter { it.isChecked }
private val fileNameField = JTextField()
open class MemberInfoModelBase(
val memberInfos: List<KotlinMemberInfo>
) : AbstractMemberInfoModel<KtNamedDeclaration, KotlinMemberInfo>() {
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<PsiComment>(docCommentPolicy)
)
refactoring(extractInfo)
}
}
@@ -16,81 +16,39 @@
package org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ui 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.psi.PsiElement
import com.intellij.refactoring.HelpID import com.intellij.refactoring.HelpID
import com.intellij.refactoring.JavaRefactoringSettings import com.intellij.refactoring.JavaRefactoringSettings
import com.intellij.refactoring.RefactoringBundle import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.classMembers.AbstractMemberInfoModel import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperInfo
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.KotlinExtractSuperclassHandler import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.KotlinExtractSuperclassHandler
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo 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.idea.refactoring.memberInfo.extractClassMembers
import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.psi.KtProperty
import java.awt.BorderLayout
import javax.swing.*
class KotlinExtractSuperclassDialog( class KotlinExtractSuperclassDialog(
originalClass: KtClassOrObject, originalClass: KtClassOrObject,
private val targetParent: PsiElement, targetParent: PsiElement,
private val conflictChecker: (KotlinExtractSuperclassDialog) -> Boolean, conflictChecker: (KotlinExtractSuperDialogBase) -> Boolean,
private val refactoring: (ExtractSuperclassInfo) -> Unit refactoring: (ExtractSuperInfo) -> Unit
) : JavaExtractSuperBaseDialog( ) : KotlinExtractSuperDialogBase(originalClass, targetParent, conflictChecker, false, KotlinExtractSuperclassHandler.REFACTORING_NAME, refactoring) {
originalClass.project,
originalClass.toLightClass()!!,
emptyList(),
KotlinExtractSuperclassHandler.REFACTORING_NAME
) {
companion object { companion object {
private val DESTINATION_PACKAGE_RECENT_KEY = "KotlinExtractSuperclassDialog.RECENT_KEYS" private val DESTINATION_PACKAGE_RECENT_KEY = "KotlinExtractSuperclassDialog.RECENT_KEYS"
} }
val kotlinMemberInfos = extractClassMembers(originalClass)
val selectedMembers: List<KotlinMemberInfo>
get() = kotlinMemberInfos.filter { it.isChecked }
private val fileNameField = JTextField()
private val memberInfoModel = object : AbstractMemberInfoModel<KtNamedDeclaration, KotlinMemberInfo>() {
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 {
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 override fun getDestinationPackageRecentKey() = DESTINATION_PACKAGE_RECENT_KEY
@@ -109,94 +67,7 @@ class KotlinExtractSuperclassDialog(
JavaRefactoringSettings.getInstance().EXTRACT_SUPERCLASS_JAVADOC = policy JavaRefactoringSettings.getInstance().EXTRACT_SUPERCLASS_JAVADOC = policy
} }
override fun getDocCommentPanelName() = "KDoc for abstracts"
override fun getExtractedSuperNameNotSpecifiedMessage() = RefactoringBundle.message("no.superclass.name.specified")!! override fun getExtractedSuperNameNotSpecifiedMessage() = RefactoringBundle.message("no.superclass.name.specified")!!
override fun getHelpId() = HelpID.EXTRACT_SUPERCLASS 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<PsiComment>(docCommentPolicy)
)
refactoring(extractInfo)
}
} }
@@ -0,0 +1,13 @@
// NAME: X
// INFO: {checked: "true"}
interface T {}
open class A
// SIBLING:
class <caret>B : A(), T {
// INFO: {checked: "true"}
fun foo() {
}
}
@@ -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 {
}
@@ -0,0 +1,13 @@
// NAME: X
// INFO: {checked: "true"}
interface T {}
open class A
// SIBLING:
class <caret>B : A(), T {
// INFO: {checked: "true", toAbstract: "true"}
fun foo() {
}
}
@@ -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() {
}
}
@@ -0,0 +1,14 @@
// NAME: B
// INFO: {checked: "true"}
interface I<T>
open class J<T>
// SIBLING:
class <caret>A<T, U : List<T>, V, W, X> : J<X>(), I<W> {
// INFO: {checked: "true"}
fun foo(u: U) {
}
}
@@ -0,0 +1,17 @@
// NAME: B
// INFO: {checked: "true"}
interface I<T>
open class J<T>
interface B<T, U : List<T>, W> : I<W> {
// INFO: {checked: "true"}
fun foo(u: U) {
}
}
// SIBLING:
class A<T, U : List<T>, V, W, X> : J<X>(), B<T, U, W> {
}
@@ -0,0 +1,14 @@
// NAME: B
// INFO: {checked: "true"}
interface I<T>
open class J<T>
// SIBLING:
class <caret>A<T, U : List<T>, V, W, X> : J<X>(), I<W> {
// INFO: {checked: "true", toAbstract: "true"}
fun foo() {
val u: U
}
}
@@ -0,0 +1,19 @@
// NAME: B
// INFO: {checked: "true"}
interface I<T>
open class J<T>
interface B<W> : I<W> {
// INFO: {checked: "true", toAbstract: "true"}
fun foo()
}
// SIBLING:
class A<T, U : List<T>, V, W, X> : J<X>(), B<W> {
// INFO: {checked: "true", toAbstract: "true"}
override fun foo() {
val u: U
}
}
@@ -0,0 +1,3 @@
// NAME: B
// SIBLING:
annotation class <caret>A
@@ -0,0 +1 @@
Interface cannot be extracted from an annotation class
@@ -42,8 +42,8 @@ import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
import org.jetbrains.kotlin.idea.refactoring.chooseMembers import org.jetbrains.kotlin.idea.refactoring.chooseMembers
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.ExtractSuperclassRefactoring 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.EXTRACT_FUNCTION
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.* 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 -> doTest(path) { file ->
file as KtFile file as KtFile
@@ -366,21 +366,26 @@ abstract class AbstractExtractionTest() : KotlinLightCodeInsightFixtureTestCase(
val editor = fixture.editor val editor = fixture.editor
val originalClass = file.findElementAt(editor.caretModel.offset)?.getStrictParentOfType<KtClassOrObject>()!! val originalClass = file.findElementAt(editor.caretModel.offset)?.getStrictParentOfType<KtClassOrObject>()!!
val memberInfos = chooseMembers(extractClassMembers(originalClass)) 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) { project.checkConflictsInteractively(conflicts) {
val extractInfo = ExtractSuperclassInfo( val extractInfo = ExtractSuperInfo(
originalClass, originalClass,
memberInfos, memberInfos,
targetParent, targetParent,
"$className.${KotlinFileType.EXTENSION}", "$className.${KotlinFileType.EXTENSION}",
className, className,
isInterface,
DocCommentPolicy<PsiComment>(DocCommentPolicy.ASIS) DocCommentPolicy<PsiComment>(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) { protected fun doTest(path: String, checkAdditionalAfterdata: Boolean = false, action: (PsiFile) -> Unit) {
val mainFile = File(path) val mainFile = File(path)
val afterFile = File("$path.after") val afterFile = File("$path.after")
@@ -4258,4 +4258,43 @@ public class ExtractionTestGenerated extends AbstractExtractionTest {
doExtractSuperclassTest(fileName); 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);
}
}
} }