Extract Class: Implement 'Extract Superclass' refactoring
#KT-11017 In Progress
This commit is contained in:
@@ -143,6 +143,7 @@ These artifacts include extensions for the types available in the latter JDKs, s
|
||||
##### New features
|
||||
|
||||
- [`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
|
||||
|
||||
#### Android Lint
|
||||
|
||||
|
||||
@@ -798,6 +798,7 @@ fun main(args: Array<String>) {
|
||||
model("refactoring/introduceJavaParameter", extension = "java", testMethod = "doIntroduceJavaParameterTest")
|
||||
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")
|
||||
}
|
||||
|
||||
testClass<AbstractPullUpTest>() {
|
||||
|
||||
@@ -82,58 +82,63 @@ class NewKotlinFileAction
|
||||
return obj is NewKotlinFileAction
|
||||
}
|
||||
|
||||
override fun createFileFromTemplate(name: String, template: FileTemplate, dir: PsiDirectory): PsiFile? {
|
||||
val directorySeparators = if (template.name == "Kotlin File") arrayOf('/', '\\') else arrayOf('/', '\\', '.')
|
||||
val (className, targetDir) = findOrCreateTarget(dir, name, directorySeparators)
|
||||
override fun createFileFromTemplate(name: String, template: FileTemplate, dir: PsiDirectory) =
|
||||
Companion.createFileFromTemplate(name, template, dir)
|
||||
|
||||
val service = DumbService.getInstance(dir.project)
|
||||
service.isAlternativeResolveEnabled = true
|
||||
try {
|
||||
return createFromTemplate(targetDir, className, template)
|
||||
}
|
||||
finally {
|
||||
service.isAlternativeResolveEnabled = false
|
||||
}
|
||||
}
|
||||
companion object {
|
||||
private fun findOrCreateTarget(dir: PsiDirectory, name: String, directorySeparators: Array<Char>): Pair<String, PsiDirectory> {
|
||||
var className = name.removeSuffix(".kt")
|
||||
var targetDir = dir
|
||||
|
||||
private fun findOrCreateTarget(dir: PsiDirectory, name: String, directorySeparators: Array<Char>): Pair<String, PsiDirectory> {
|
||||
var className = name.removeSuffix(".kt")
|
||||
var targetDir = dir
|
||||
for (splitChar in directorySeparators) {
|
||||
if (splitChar in className) {
|
||||
val names = className.trim().split(splitChar)
|
||||
|
||||
for (splitChar in directorySeparators) {
|
||||
if (splitChar in className) {
|
||||
val names = className.trim().split(splitChar)
|
||||
for (dirName in names.dropLast(1)) {
|
||||
targetDir = targetDir.findSubdirectory(dirName) ?: targetDir.createSubdirectory(dirName)
|
||||
}
|
||||
|
||||
for (dirName in names.dropLast(1)) {
|
||||
targetDir = targetDir.findSubdirectory(dirName) ?: targetDir.createSubdirectory(dirName)
|
||||
className = names.last()
|
||||
break
|
||||
}
|
||||
}
|
||||
return Pair(className, targetDir)
|
||||
}
|
||||
|
||||
className = names.last()
|
||||
break
|
||||
private fun createFromTemplate(dir: PsiDirectory, className: String, template: FileTemplate): PsiFile? {
|
||||
val project = dir.project
|
||||
val defaultProperties = FileTemplateManager.getInstance(project).defaultProperties
|
||||
|
||||
val properties = Properties(defaultProperties)
|
||||
|
||||
val element = try {
|
||||
CreateFromTemplateDialog(project, dir, template,
|
||||
AttributesDefaults(className).withFixedName(true),
|
||||
properties).create()
|
||||
}
|
||||
catch (e: IncorrectOperationException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: Exception) {
|
||||
LOG.error(e)
|
||||
return null
|
||||
}
|
||||
|
||||
return element?.containingFile
|
||||
}
|
||||
|
||||
fun createFileFromTemplate(name: String, template: FileTemplate, dir: PsiDirectory): PsiFile? {
|
||||
val directorySeparators = if (template.name == "Kotlin File") arrayOf('/', '\\') else arrayOf('/', '\\', '.')
|
||||
val (className, targetDir) = findOrCreateTarget(dir, name, directorySeparators)
|
||||
|
||||
val service = DumbService.getInstance(dir.project)
|
||||
service.isAlternativeResolveEnabled = true
|
||||
try {
|
||||
return createFromTemplate(targetDir, className, template)
|
||||
}
|
||||
finally {
|
||||
service.isAlternativeResolveEnabled = false
|
||||
}
|
||||
}
|
||||
return Pair(className, targetDir)
|
||||
}
|
||||
|
||||
private fun createFromTemplate(dir: PsiDirectory, className: String, template: FileTemplate): PsiFile? {
|
||||
val project = dir.project
|
||||
val defaultProperties = FileTemplateManager.getInstance(project).defaultProperties
|
||||
|
||||
val properties = Properties(defaultProperties)
|
||||
|
||||
val element = try {
|
||||
CreateFromTemplateDialog(project, dir, template,
|
||||
AttributesDefaults(className).withFixedName(true),
|
||||
properties).create()
|
||||
}
|
||||
catch (e: IncorrectOperationException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: Exception) {
|
||||
LOG.error(e)
|
||||
return null
|
||||
}
|
||||
|
||||
return element?.containingFile
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.kotlin.idea.findUsages
|
||||
|
||||
import com.intellij.codeInsight.highlighting.HighlightUsagesDescriptionLocation
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.ElementDescriptionLocation
|
||||
import com.intellij.psi.ElementDescriptionProvider
|
||||
import com.intellij.psi.PsiElement
|
||||
@@ -33,6 +34,7 @@ import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.refactoring.rename.RenameJavaSyntheticPropertyHandler
|
||||
import org.jetbrains.kotlin.idea.refactoring.rename.RenameKotlinPropertyProcessor
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
|
||||
import org.jetbrains.kotlin.idea.util.string.collapseSpaces
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
@@ -72,7 +74,12 @@ class KotlinElementDescriptionProvider : ElementDescriptionProvider {
|
||||
targetElement.parent as? KtProperty
|
||||
} else targetElement as? PsiNamedElement
|
||||
|
||||
if (namedElement == null || namedElement.language != KotlinLanguage.INSTANCE) return null
|
||||
if (namedElement == null) {
|
||||
return if (targetElement is KtElement) "'" + StringUtil.shortenTextWithEllipsis(targetElement.text.collapseSpaces(), 53, 0) + "'" else null
|
||||
}
|
||||
|
||||
if (namedElement.language != KotlinLanguage.INSTANCE) return null
|
||||
|
||||
return when(location) {
|
||||
is UsageViewTypeLocation -> elementKind()
|
||||
is UsageViewShortNameLocation, is UsageViewLongNameLocation -> namedElement.name
|
||||
|
||||
@@ -23,6 +23,7 @@ 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.extractFunction.ExtractKotlinFunctionHandler
|
||||
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.KotlinIntroduceParameterHandler
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinIntroducePropertyHandler
|
||||
@@ -78,6 +79,8 @@ class KotlinRefactoringSupportProvider : RefactoringSupportProvider() {
|
||||
override fun getPullUpHandler() = KotlinPullUpHandler()
|
||||
|
||||
override fun getPushDownHandler() = KotlinPushDownHandler()
|
||||
|
||||
override fun getExtractSuperClassHandler() = KotlinExtractSuperclassHandler
|
||||
}
|
||||
|
||||
class KotlinVetoRenameCondition: Condition<PsiElement> {
|
||||
|
||||
+298
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* 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.ide.fileTemplates.FileTemplateManager
|
||||
import com.intellij.psi.PsiDirectory
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.searches.MethodReferencesSearch
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.refactoring.RefactoringBundle
|
||||
import com.intellij.refactoring.extractSuperclass.ExtractSuperClassUtil
|
||||
import com.intellij.refactoring.memberPullUp.PullUpProcessor
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||
import com.intellij.refactoring.util.DocCommentPolicy
|
||||
import com.intellij.refactoring.util.MoveRenameUsageInfo
|
||||
import com.intellij.usageView.UsageInfo
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.asJava.toLightMethods
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.idea.actions.NewKotlinFileAction
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||
import org.jetbrains.kotlin.idea.codeInsight.shorten.performDelayedShortening
|
||||
import org.jetbrains.kotlin.idea.core.ShortenReferences
|
||||
import org.jetbrains.kotlin.idea.core.copied
|
||||
import org.jetbrains.kotlin.idea.core.getPackage
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.insertDeclaration
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.getChildrenToAnalyze
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.toJavaMemberInfo
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.KotlinMoveTargetForDeferredFile
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.KotlinMoveTargetForExistingElement
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveConflictChecker
|
||||
import org.jetbrains.kotlin.idea.refactoring.runSynchronouslyWithProgress
|
||||
import org.jetbrains.kotlin.idea.references.mainReference
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
|
||||
import java.util.*
|
||||
|
||||
data class ExtractSuperclassInfo(
|
||||
val originalClass: KtClassOrObject,
|
||||
val memberInfos: Collection<KotlinMemberInfo>,
|
||||
val targetParent: PsiElement,
|
||||
val targetFileName: String,
|
||||
val newClassName: String,
|
||||
val docPolicy: DocCommentPolicy<*>
|
||||
)
|
||||
|
||||
class ExtractSuperclassRefactoring(
|
||||
private var extractInfo: ExtractSuperclassInfo
|
||||
) {
|
||||
companion object {
|
||||
private fun getElementsToMove(
|
||||
memberInfos: Collection<KotlinMemberInfo>,
|
||||
originalClass: KtClassOrObject
|
||||
): Map<KtElement, KotlinMemberInfo?> {
|
||||
val project = originalClass.project
|
||||
val elementsToMove = LinkedHashMap<KtElement, KotlinMemberInfo?>()
|
||||
runReadAction {
|
||||
val superInterfacesToMove = ArrayList<KtElement>()
|
||||
for (memberInfo in memberInfos) {
|
||||
val member = memberInfo.member ?: continue
|
||||
if (memberInfo.isSuperClass) {
|
||||
superInterfacesToMove += member
|
||||
}
|
||||
else {
|
||||
elementsToMove[member] = memberInfo
|
||||
}
|
||||
}
|
||||
|
||||
val superTypeList = originalClass.getSuperTypeList()
|
||||
if (superTypeList != null) {
|
||||
for (superTypeListEntry in originalClass.getSuperTypeListEntries()) {
|
||||
val superType = superTypeListEntry.analyze(BodyResolveMode.PARTIAL)[BindingContext.TYPE, superTypeListEntry.typeReference]
|
||||
?: continue
|
||||
val superClassDescriptor = superType.constructor.declarationDescriptor ?: continue
|
||||
val superClass = DescriptorToSourceUtilsIde.getAnyDeclaration(project, superClassDescriptor) as? KtClass ?: continue
|
||||
if (!superClass.isInterface() || superClass in superInterfacesToMove) {
|
||||
elementsToMove[superTypeListEntry] = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return elementsToMove
|
||||
}
|
||||
|
||||
fun collectConflicts(
|
||||
originalClass: KtClassOrObject,
|
||||
memberInfos: List<KotlinMemberInfo>,
|
||||
targetParent: PsiElement,
|
||||
newClassName: String
|
||||
): MultiMap<PsiElement, String> {
|
||||
val conflicts = MultiMap<PsiElement, String>()
|
||||
|
||||
val project = originalClass.project
|
||||
|
||||
if (targetParent is KtElement) {
|
||||
val targetSibling = originalClass.parentsWithSelf.first { it.parent == targetParent } as KtElement
|
||||
targetSibling.getResolutionScope()
|
||||
.findClassifier(Name.identifier(newClassName), NoLookupLocation.FROM_IDE)
|
||||
?.let { DescriptorToSourceUtilsIde.getAnyDeclaration(project, it) }
|
||||
?.let { conflicts.putValue(it, "Class $newClassName already exists in the target scope") }
|
||||
}
|
||||
|
||||
val elementsToMove = getElementsToMove(memberInfos, originalClass).keys
|
||||
|
||||
val moveTarget = if (targetParent is PsiDirectory) {
|
||||
val targetPackage = targetParent.getPackage() ?: return conflicts
|
||||
KotlinMoveTargetForDeferredFile(FqName(targetPackage.qualifiedName), targetParent) { null }
|
||||
}
|
||||
else {
|
||||
KotlinMoveTargetForExistingElement(targetParent as KtElement)
|
||||
}
|
||||
val conflictChecker = MoveConflictChecker(project, elementsToMove, moveTarget, originalClass)
|
||||
|
||||
project.runSynchronouslyWithProgress(RefactoringBundle.message("detecting.possible.conflicts"), true) {
|
||||
runReadAction {
|
||||
val usages = ArrayList<UsageInfo>()
|
||||
for (element in elementsToMove) {
|
||||
ReferencesSearch.search(element).mapTo(usages) { MoveRenameUsageInfo(it, element) }
|
||||
if (element is KtCallableDeclaration) {
|
||||
element.toLightMethods().flatMapTo(usages) {
|
||||
MethodReferencesSearch.search(it).map { MoveRenameUsageInfo(it, element) }
|
||||
}
|
||||
}
|
||||
}
|
||||
conflictChecker.checkAllConflicts(usages, conflicts)
|
||||
if (targetParent is PsiDirectory) {
|
||||
ExtractSuperClassUtil.checkSuperAccessible(targetParent, conflicts, originalClass.toLightClass())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return conflicts
|
||||
}
|
||||
}
|
||||
|
||||
private val project = extractInfo.originalClass.project
|
||||
private val psiFactory = KtPsiFactory(project)
|
||||
private val typeParameters = LinkedHashSet<KtTypeParameter>()
|
||||
|
||||
private val bindingContext = extractInfo.originalClass.analyze(BodyResolveMode.PARTIAL)
|
||||
|
||||
private fun collectTypeParameters(refTarget: PsiElement?) {
|
||||
if (refTarget is KtTypeParameter && refTarget.getStrictParentOfType<KtTypeParameterListOwner>() == extractInfo.originalClass) {
|
||||
typeParameters += refTarget
|
||||
refTarget.accept(
|
||||
object : KtTreeVisitorVoid() {
|
||||
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
|
||||
(expression.mainReference.resolve() as? KtTypeParameter)?.let { typeParameters += it }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun analyzeContext() {
|
||||
val visitor = object : KtTreeVisitorVoid() {
|
||||
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
|
||||
val refTarget = expression.mainReference.resolve()
|
||||
collectTypeParameters(refTarget)
|
||||
}
|
||||
}
|
||||
getElementsToMove(extractInfo.memberInfos, extractInfo.originalClass)
|
||||
.asSequence()
|
||||
.flatMap {
|
||||
val (element, info) = it
|
||||
if (info != null) info.getChildrenToAnalyze().asSequence() else sequenceOf(element)
|
||||
}
|
||||
.forEach { it.accept(visitor) }
|
||||
}
|
||||
|
||||
private fun createClass(superClassEntry: KtSuperTypeListEntry?): KtClass {
|
||||
val targetParent = extractInfo.targetParent
|
||||
val newClassName = extractInfo.newClassName
|
||||
val originalClass = extractInfo.originalClass
|
||||
|
||||
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
|
||||
}
|
||||
else {
|
||||
val targetSibling = originalClass.parentsWithSelf.first { it.parent == targetParent }
|
||||
insertDeclaration(psiFactory.createClass("class $newClassName"), targetSibling)
|
||||
}
|
||||
|
||||
val shouldBeAbstract = extractInfo.memberInfos.any { it.isToAbstract }
|
||||
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 = ">")
|
||||
newClass.addAfter(psiFactory.createTypeParameterList(typeParameterListText), newClass.nameIdentifier)
|
||||
}
|
||||
|
||||
val targetPackageFqName = (targetParent as? PsiDirectory)?.getPackage()?.qualifiedName
|
||||
|
||||
val superTypeText = buildString {
|
||||
if (!targetPackageFqName.isNullOrEmpty()) {
|
||||
append(targetPackageFqName).append('.')
|
||||
}
|
||||
append(newClassName)
|
||||
if (typeParameters.isNotEmpty()) {
|
||||
append(typeParameters.sortedBy { it.startOffset }.map { it.name }.joinToString(prefix = "<", postfix = ">"))
|
||||
}
|
||||
}
|
||||
val needSuperCall = superClassEntry is KtSuperTypeCallEntry
|
||||
|| originalClass.hasPrimaryConstructor()
|
||||
|| originalClass.getSecondaryConstructors().isEmpty()
|
||||
val newSuperTypeCallEntry = if (needSuperCall) {
|
||||
psiFactory.createSuperTypeCallEntry("$superTypeText()")
|
||||
}
|
||||
else {
|
||||
psiFactory.createSuperTypeEntry(superTypeText)
|
||||
}
|
||||
if (superClassEntry != null) {
|
||||
val qualifiedTypeRefText = bindingContext[BindingContext.TYPE, superClassEntry.typeReference]?.let {
|
||||
IdeDescriptorRenderers.SOURCE_CODE.renderType(it)
|
||||
}
|
||||
val superClassEntryToAdd = if (qualifiedTypeRefText != null) {
|
||||
superClassEntry.copied().apply { typeReference?.replace(psiFactory.createType(qualifiedTypeRefText)) }
|
||||
}
|
||||
else superClassEntry
|
||||
newClass.addSuperTypeListEntry(superClassEntryToAdd)
|
||||
ShortenReferences.DEFAULT.process(superClassEntry.replaced(newSuperTypeCallEntry))
|
||||
}
|
||||
else {
|
||||
ShortenReferences.DEFAULT.process(originalClass.addSuperTypeListEntry(newSuperTypeCallEntry))
|
||||
}
|
||||
|
||||
ShortenReferences.DEFAULT.process(newClass)
|
||||
|
||||
return newClass
|
||||
}
|
||||
|
||||
fun performRefactoring() {
|
||||
val originalClass = extractInfo.originalClass
|
||||
|
||||
KotlinExtractSuperclassHandler.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
|
||||
}
|
||||
|
||||
project.runSynchronouslyWithProgress(RefactoringBundle.message("progress.text"), true) { runReadAction { analyzeContext() } }
|
||||
|
||||
project.executeWriteCommand(KotlinExtractSuperclassHandler.REFACTORING_NAME) {
|
||||
val newClass = createClass(superClassEntry)
|
||||
|
||||
val subClass = extractInfo.originalClass.toLightClass()
|
||||
val superClass = newClass.toLightClass()
|
||||
|
||||
PullUpProcessor(
|
||||
subClass,
|
||||
superClass ?: return@executeWriteCommand,
|
||||
extractInfo.memberInfos.mapNotNull { it.toJavaMemberInfo() }.toTypedArray(),
|
||||
extractInfo.docPolicy
|
||||
).moveMembersToBase()
|
||||
|
||||
performDelayedShortening(project)
|
||||
}
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.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 {
|
||||
val REFACTORING_NAME = "Extract Superclass"
|
||||
|
||||
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, it, project, editor) }
|
||||
)
|
||||
}
|
||||
|
||||
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")
|
||||
if (klass.isAnnotation()) return "Superclass cannot be extracted from an annotation class"
|
||||
}
|
||||
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?) {
|
||||
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() }
|
||||
).show()
|
||||
}
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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.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.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
|
||||
) {
|
||||
companion object {
|
||||
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()
|
||||
|
||||
fileNameField.text = "$extractedSuperName.${KotlinFileType.EXTENSION}"
|
||||
}
|
||||
|
||||
override fun getDestinationPackageRecentKey() = DESTINATION_PACKAGE_RECENT_KEY
|
||||
|
||||
override fun getClassNameLabelText() = RefactoringBundle.message("superclass.name")!!
|
||||
|
||||
override fun getPackageNameLabelText() = RefactoringBundle.message("package.for.new.superclass")!!
|
||||
|
||||
override fun getEntityName() = RefactoringBundle.message("ExtractSuperClass.superclass")!!
|
||||
|
||||
override fun getTopLabelText() = RefactoringBundle.message("extract.superclass.from")!!
|
||||
|
||||
override fun getDocCommentPolicySetting() = JavaRefactoringSettings.getInstance().EXTRACT_SUPERCLASS_JAVADOC
|
||||
|
||||
override fun setDocCommentPolicySetting(policy: Int) {
|
||||
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<PsiComment>(docCommentPolicy)
|
||||
)
|
||||
refactoring(extractInfo)
|
||||
}
|
||||
}
|
||||
+2
-17
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.idea.core.CollectingNameValidator
|
||||
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
||||
import org.jetbrains.kotlin.idea.core.compareDescriptors
|
||||
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.insertDeclaration
|
||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiRange
|
||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiUnifier
|
||||
@@ -220,22 +221,6 @@ fun IntroduceTypeAliasDescriptor.generateTypeAlias(previewOnly: Boolean = false)
|
||||
}
|
||||
}
|
||||
|
||||
fun insertDeclaration(): KtTypeAlias {
|
||||
val targetParent = originalData.targetSibling.parent
|
||||
|
||||
val anchorCandidates = SmartList<PsiElement>()
|
||||
anchorCandidates.add(targetSibling)
|
||||
if (targetSibling is KtEnumEntry) {
|
||||
anchorCandidates.add(targetSibling.siblings().last { it is KtEnumEntry })
|
||||
}
|
||||
|
||||
val anchor = anchorCandidates.minBy { it.startOffset }!!.parentsWithSelf.first { it.parent == targetParent }
|
||||
val targetContainer = anchor.parent!!
|
||||
return (targetContainer.addBefore(typeAlias, anchor) as KtTypeAlias).apply {
|
||||
targetContainer.addBefore(psiFactory.createWhiteSpace("\n\n"), anchor)
|
||||
}
|
||||
}
|
||||
|
||||
return if (previewOnly) {
|
||||
introduceTypeParameters()
|
||||
typeAlias
|
||||
@@ -243,6 +228,6 @@ fun IntroduceTypeAliasDescriptor.generateTypeAlias(previewOnly: Boolean = false)
|
||||
else {
|
||||
replaceUsage()
|
||||
introduceTypeParameters()
|
||||
insertDeclaration()
|
||||
insertDeclaration(typeAlias, originalData.targetSibling)
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import org.jetbrains.kotlin.idea.refactoring.selectElement
|
||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiRange
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.utils.SmartList
|
||||
|
||||
fun showErrorHint(project: Project, editor: Editor, message: String, title: String) {
|
||||
CodeInsightUtils.showErrorHint(project, editor, message, title, null)
|
||||
@@ -217,4 +218,22 @@ fun KtExpression.mustBeParenthesizedInInitializerPosition(): Boolean {
|
||||
return PsiChildRange(left, operationReference).any { (it is PsiWhiteSpace) && it.textContains('\n') }
|
||||
}
|
||||
|
||||
fun isObjectOrNonInnerClass(e: PsiElement): Boolean = e is KtObjectDeclaration || (e is KtClass && !e.isInner())
|
||||
fun isObjectOrNonInnerClass(e: PsiElement): Boolean = e is KtObjectDeclaration || (e is KtClass && !e.isInner())
|
||||
|
||||
fun <T : KtDeclaration> insertDeclaration(declaration: T, targetSibling: PsiElement): T {
|
||||
val targetParent = targetSibling.parent
|
||||
|
||||
val anchorCandidates = SmartList<PsiElement>()
|
||||
anchorCandidates.add(targetSibling)
|
||||
if (targetSibling is KtEnumEntry) {
|
||||
anchorCandidates.add(targetSibling.siblings().last { it is KtEnumEntry })
|
||||
}
|
||||
|
||||
val anchor = anchorCandidates.minBy { it.startOffset }!!.parentsWithSelf.first { it.parent == targetParent }
|
||||
val targetContainer = anchor.parent!!
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return (targetContainer.addBefore(declaration, anchor) as T).apply {
|
||||
targetContainer.addBefore(KtPsiFactory(declaration).createWhiteSpace("\n\n"), anchor)
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.impl.light.LightElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
|
||||
import com.intellij.refactoring.changeSignature.ChangeSignatureUtil
|
||||
@@ -68,6 +69,7 @@ import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getJavaMemberDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
@@ -96,6 +98,7 @@ import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import java.io.File
|
||||
import java.lang.AssertionError
|
||||
import java.lang.annotation.Retention
|
||||
import java.util.*
|
||||
import javax.swing.Icon
|
||||
@@ -291,9 +294,11 @@ class SelectionAwareScopeHighlighter(val editor: Editor) {
|
||||
fun highlight(wholeAffected: PsiElement) {
|
||||
dropHighlight()
|
||||
|
||||
val affectedRange = wholeAffected.textRange ?: return
|
||||
|
||||
val attributes = EditorColorsManager.getInstance().globalScheme.getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES)!!
|
||||
val selectedRange = with(editor.selectionModel) { TextRange(selectionStart, selectionEnd) }
|
||||
for (r in RangeSplitter.split(wholeAffected.textRange!!, Collections.singletonList(selectedRange))) {
|
||||
for (r in RangeSplitter.split(affectedRange, Collections.singletonList(selectedRange))) {
|
||||
addHighlighter(r, attributes)
|
||||
}
|
||||
}
|
||||
@@ -340,6 +345,10 @@ fun PsiElement.getLineCount(): Int {
|
||||
|
||||
fun PsiElement.isMultiLine(): Boolean = getLineCount() > 1
|
||||
|
||||
class SeparateFileWrapper(manager: PsiManager) : LightElement(manager, KotlinLanguage.INSTANCE) {
|
||||
override fun toString() = ""
|
||||
}
|
||||
|
||||
fun <T> chooseContainerElement(
|
||||
containers: List<T>,
|
||||
editor: Editor,
|
||||
@@ -380,6 +389,7 @@ fun <T> chooseContainerElement(
|
||||
}
|
||||
|
||||
private fun PsiElement.renderText(): String {
|
||||
if (this is SeparateFileWrapper) return "Extract to separate file"
|
||||
return StringUtil.shortenTextWithEllipsis(text!!.collapseSpaces(), 53, 0)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.intellij.refactoring.util.classMembers.MemberInfo
|
||||
import org.jetbrains.kotlin.asJava.LightClassUtil
|
||||
import org.jetbrains.kotlin.asJava.getRepresentativeLightMethod
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.asJava.unwrapped
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.MemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
@@ -78,4 +79,11 @@ fun KotlinMemberInfo.toJavaMemberInfo(): MemberInfo? {
|
||||
val info = MemberInfo(psiMember ?: return null, isSuperClass, null)
|
||||
info.isToAbstract = isToAbstract
|
||||
return info
|
||||
}
|
||||
|
||||
fun MemberInfo.toKotlinMemberInfo(): KotlinMemberInfo? {
|
||||
val declaration = member.unwrapped as? KtNamedDeclaration ?: return null
|
||||
return KotlinMemberInfo(declaration, declaration is KtClass && overrides != null).apply {
|
||||
this.isToAbstract = this@toKotlinMemberInfo.isToAbstract
|
||||
}
|
||||
}
|
||||
+35
-19
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.OverloadChecker
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import org.jetbrains.kotlin.types.typeUtil.immediateSupertypes
|
||||
import java.util.*
|
||||
@@ -73,25 +74,40 @@ class KotlinMemberInfoStorage(
|
||||
}
|
||||
|
||||
override fun extractClassMembers(aClass: PsiNamedElement, temp: ArrayList<KotlinMemberInfo>) {
|
||||
if (aClass !is KtClassOrObject) return
|
||||
|
||||
val context = aClass.analyze()
|
||||
aClass.declarations
|
||||
.filter { it is KtNamedDeclaration
|
||||
&& it !is KtConstructor<*>
|
||||
&& !(it is KtObjectDeclaration && it.isCompanion())
|
||||
&& myFilter.includeMember(it) }
|
||||
.mapTo(temp) { KotlinMemberInfo(it as KtNamedDeclaration) }
|
||||
if (aClass == myClass) {
|
||||
aClass.getSuperTypeListEntries()
|
||||
.filterIsInstance<KtSuperTypeEntry>()
|
||||
.map {
|
||||
val type = context[BindingContext.TYPE, it.typeReference]
|
||||
val classDescriptor = type?.constructor?.declarationDescriptor as? ClassDescriptor
|
||||
classDescriptor?.source?.getPsi() as? KtClass
|
||||
}
|
||||
.filter { it != null && it.isInterface() }
|
||||
.mapTo(temp) { KotlinMemberInfo(it!!, true) }
|
||||
if (aClass is KtClassOrObject) {
|
||||
temp += extractClassMembers(aClass, aClass == myClass) { myFilter.includeMember(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun extractClassMembers(
|
||||
aClass: KtClassOrObject,
|
||||
collectSuperTypeEntries: Boolean = true,
|
||||
filter: ((KtNamedDeclaration) -> Boolean)? = null
|
||||
): List<KotlinMemberInfo> {
|
||||
if (aClass !is KtClassOrObject) return emptyList()
|
||||
|
||||
val result = ArrayList<KotlinMemberInfo>()
|
||||
|
||||
if (collectSuperTypeEntries) {
|
||||
aClass.getSuperTypeListEntries()
|
||||
.filterIsInstance<KtSuperTypeEntry>()
|
||||
.mapNotNull {
|
||||
val typeReference = it.typeReference ?: return@mapNotNull null
|
||||
val type = typeReference.analyze(BodyResolveMode.PARTIAL)[BindingContext.TYPE, typeReference]
|
||||
val classDescriptor = type?.constructor?.declarationDescriptor as? ClassDescriptor
|
||||
classDescriptor?.source?.getPsi() as? KtClass
|
||||
}
|
||||
.filter { it.isInterface() }
|
||||
.mapTo(result) { KotlinMemberInfo(it, true) }
|
||||
}
|
||||
|
||||
aClass.declarations
|
||||
.filter { it is KtNamedDeclaration
|
||||
&& it !is KtConstructor<*>
|
||||
&& !(it is KtObjectDeclaration && it.isCompanion())
|
||||
&& (filter == null || filter(it)) }
|
||||
.mapTo(result) { KotlinMemberInfo(it as KtNamedDeclaration) }
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -17,13 +17,14 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.memberInfo
|
||||
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiNamedElement
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getJavaClassDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||
import org.jetbrains.kotlin.psi.KtClass
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.allChildren
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
||||
|
||||
fun PsiNamedElement.getClassDescriptorIfAny(resolutionFacade: ResolutionFacade? = null): ClassDescriptor? {
|
||||
@@ -42,4 +43,20 @@ fun PsiNamedElement.qualifiedClassNameForRendering(): String {
|
||||
else -> throw AssertionError("Not a class: ${getElementTextWithContext()}")
|
||||
}
|
||||
return fqName ?: name ?: "[Anonymous]"
|
||||
}
|
||||
|
||||
fun KotlinMemberInfo.getChildrenToAnalyze(): List<PsiElement> {
|
||||
val member = member
|
||||
val childrenToCheck = member.allChildren.toMutableList()
|
||||
if (isToAbstract && member is KtCallableDeclaration) {
|
||||
when (member) {
|
||||
is KtNamedFunction -> childrenToCheck.remove(member.bodyExpression as PsiElement?)
|
||||
is KtProperty -> {
|
||||
childrenToCheck.remove(member.initializer as PsiElement?)
|
||||
childrenToCheck.remove(member.delegateExpression as PsiElement?)
|
||||
childrenToCheck.removeAll(member.accessors)
|
||||
}
|
||||
}
|
||||
}
|
||||
return childrenToCheck
|
||||
}
|
||||
+1
-1
@@ -168,7 +168,7 @@ class MoveKotlinDeclarationsProcessor(
|
||||
}
|
||||
|
||||
val usages = ArrayList<UsageInfo>()
|
||||
val conflictChecker = MoveConflictChecker(project, elementsToMove, descriptor.moveTarget)
|
||||
val conflictChecker = MoveConflictChecker(project, elementsToMove, descriptor.moveTarget, elementsToMove.first())
|
||||
for ((sourceFile, kotlinToLightElements) in kotlinToLightElementsBySourceFile) {
|
||||
kotlinToLightElements.keys.forEach {
|
||||
if (descriptor.updateInternalReferences) {
|
||||
|
||||
+5
-4
@@ -29,7 +29,6 @@ import com.intellij.usageView.UsageInfo
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
|
||||
import org.jetbrains.kotlin.asJava.toLightMethods
|
||||
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.impl.MutablePackageFragmentDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.*
|
||||
@@ -47,10 +46,11 @@ import java.util.*
|
||||
|
||||
class MoveConflictChecker(
|
||||
private val project: Project,
|
||||
private val elementsToMove: List<KtNamedDeclaration>,
|
||||
private val moveTarget: KotlinMoveTarget
|
||||
private val elementsToMove: Collection<KtElement>,
|
||||
private val moveTarget: KotlinMoveTarget,
|
||||
contextElement: KtElement
|
||||
) {
|
||||
private val resolutionFacade by lazy { KotlinCacheService.getInstance(project).getResolutionFacade(elementsToMove) }
|
||||
private val resolutionFacade = contextElement.getResolutionFacade()
|
||||
|
||||
private val fakeFile = KtPsiFactory(project).createFile("")
|
||||
|
||||
@@ -152,6 +152,7 @@ class MoveConflictChecker(
|
||||
val target = ref.resolve() ?: return@forEach
|
||||
if (target.isInsideOf(elementsToMove)) return@forEach
|
||||
if (target in resolveScope) return@forEach
|
||||
if (target is KtTypeParameter) return@forEach
|
||||
|
||||
val superMethods = SmartSet.create<PsiMethod>()
|
||||
target.toLightMethods().forEach { superMethods += it.findDeepestSuperMethods() }
|
||||
|
||||
@@ -25,13 +25,13 @@ import org.jetbrains.kotlin.asJava.unwrapped
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.getChildrenToAnalyze
|
||||
import org.jetbrains.kotlin.idea.references.KtReference
|
||||
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
|
||||
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.allChildren
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
@@ -161,17 +161,8 @@ private fun KotlinPullUpData.checkVisibility(
|
||||
}
|
||||
|
||||
val member = memberInfo.member
|
||||
val childrenToCheck = member.allChildren.toMutableList()
|
||||
val childrenToCheck = memberInfo.getChildrenToAnalyze()
|
||||
if (memberInfo.isToAbstract && member is KtCallableDeclaration) {
|
||||
when (member) {
|
||||
is KtNamedFunction -> childrenToCheck.remove(member.bodyExpression as PsiElement?)
|
||||
is KtProperty -> {
|
||||
childrenToCheck.remove(member.initializer as PsiElement?)
|
||||
childrenToCheck.remove(member.delegateExpression as PsiElement?)
|
||||
childrenToCheck.removeAll(member.accessors)
|
||||
}
|
||||
}
|
||||
|
||||
if (member.typeReference == null) {
|
||||
(memberDescriptor as CallableDescriptor).returnType?.let { returnType ->
|
||||
val typeInTargetClass = sourceToTargetClassSubstitutor.substitute(returnType, Variance.INVARIANT)
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// NAME: X
|
||||
interface T {}
|
||||
|
||||
// SIBLING:
|
||||
class <caret>A : T {
|
||||
// INFO: {checked: "true"}
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// NAME: X
|
||||
interface T {}
|
||||
|
||||
open class X {
|
||||
// INFO: {checked: "true"}
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// SIBLING:
|
||||
class A : T, X() {
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// NAME: X
|
||||
interface T {}
|
||||
|
||||
// SIBLING:
|
||||
class <caret>A : T {
|
||||
constructor()
|
||||
|
||||
// INFO: {checked: "true"}
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
// NAME: X
|
||||
interface T {}
|
||||
|
||||
open class X {
|
||||
// INFO: {checked: "true"}
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// SIBLING:
|
||||
class A : T, X {
|
||||
constructor()
|
||||
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// NAME: X
|
||||
interface T {}
|
||||
|
||||
// SIBLING:
|
||||
class <caret>A(n: Int) : T {
|
||||
// INFO: {checked: "true"}
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// NAME: X
|
||||
interface T {}
|
||||
|
||||
open class X {
|
||||
// INFO: {checked: "true"}
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// SIBLING:
|
||||
class A(n: Int) : T, X() {
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
open class B<T, U : List<T>, W, X> : J<X>(), I<W> {
|
||||
// INFO: {checked: "true"}
|
||||
fun foo(u: U) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// SIBLING:
|
||||
class A<T, U : List<T>, V, W, X> : B<T, U, W, X>() {
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// NAME: B
|
||||
|
||||
// INFO: {checked: "true"}
|
||||
interface I<T>
|
||||
|
||||
open class J<T>
|
||||
|
||||
abstract class B<W, X> : J<X>(), I<W> {
|
||||
// INFO: {checked: "true", toAbstract: "true"}
|
||||
abstract fun foo()
|
||||
}
|
||||
|
||||
// SIBLING:
|
||||
class A<T, U : List<T>, V, W, X> : B<W, X>() {
|
||||
// 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 @@
|
||||
Superclass cannot be extracted from an annotation class
|
||||
@@ -0,0 +1,9 @@
|
||||
// NAME: B
|
||||
// SIBLING:
|
||||
enum class <caret>A {
|
||||
X, Y, Z;
|
||||
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Superclass cannot be extracted from enum
|
||||
@@ -0,0 +1,7 @@
|
||||
// NAME: B
|
||||
// SIBLING:
|
||||
interface <caret>A {
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Superclass cannot be extracted from interface
|
||||
@@ -0,0 +1,7 @@
|
||||
// NAME: X
|
||||
// SIBLING:
|
||||
class A {
|
||||
private open class B
|
||||
|
||||
private class <caret>C : B()
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
'B()' uses class B which will be inaccessible after move
|
||||
@@ -0,0 +1,16 @@
|
||||
// NAME: X
|
||||
class A {
|
||||
open class B
|
||||
|
||||
// SIBLING:
|
||||
class <caret>C : B() {
|
||||
// INFO: {checked: "true"}
|
||||
private fun foo() {
|
||||
|
||||
}
|
||||
|
||||
fun test() {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// NAME: X
|
||||
class A {
|
||||
open class B
|
||||
|
||||
open class X : B() {
|
||||
// INFO: {checked: "true"}
|
||||
protected fun foo() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// SIBLING:
|
||||
class C : X() {
|
||||
|
||||
fun test() {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// NAME: X
|
||||
interface T {}
|
||||
|
||||
open class A
|
||||
|
||||
// SIBLING:
|
||||
class <caret>B : A(), T {
|
||||
// INFO: {checked: "true"}
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// NAME: X
|
||||
interface T {}
|
||||
|
||||
open class A
|
||||
|
||||
open class X : A() {
|
||||
// INFO: {checked: "true"}
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// SIBLING:
|
||||
class B : X(), T {
|
||||
}
|
||||
@@ -36,13 +36,6 @@ import org.jetbrains.kotlin.test.util.findElementsByCommentPrefix
|
||||
import java.io.File
|
||||
|
||||
abstract class AbstractMemberPullPushTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
private data class ElementInfo(val checked: Boolean, val toAbstract: Boolean)
|
||||
|
||||
companion object {
|
||||
private var PsiElement.elementInfo: ElementInfo
|
||||
by NotNullableUserDataProperty(Key.create("ELEMENT_INFO"), ElementInfo(false, false))
|
||||
}
|
||||
|
||||
override fun getProjectDescriptor() = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
|
||||
|
||||
val fixture: JavaCodeInsightTestFixture get() = myFixture
|
||||
@@ -70,11 +63,7 @@ abstract class AbstractMemberPullPushTest : KotlinLightCodeInsightFixtureTestCas
|
||||
}
|
||||
|
||||
try {
|
||||
for ((element, info) in file.findElementsByCommentPrefix("// INFO: ")) {
|
||||
val parsedInfo = JsonParser().parse(info).asJsonObject
|
||||
element.elementInfo = ElementInfo(parsedInfo["checked"]?.asBoolean ?: false,
|
||||
parsedInfo["toAbstract"]?.asBoolean ?: false)
|
||||
}
|
||||
markMembersInfo(file)
|
||||
|
||||
action(file)
|
||||
|
||||
@@ -99,12 +88,26 @@ abstract class AbstractMemberPullPushTest : KotlinLightCodeInsightFixtureTestCas
|
||||
}
|
||||
}
|
||||
|
||||
protected fun <T : MemberInfoBase<*>> chooseMembers(members: List<T>): List<T> {
|
||||
members.forEach {
|
||||
val info = it.member.elementInfo
|
||||
it.isChecked = info.checked
|
||||
it.isToAbstract = info.toAbstract
|
||||
}
|
||||
return members.filter { it.isChecked }
|
||||
|
||||
}
|
||||
|
||||
internal fun markMembersInfo(file: PsiFile) {
|
||||
for ((element, info) in file.findElementsByCommentPrefix("// INFO: ")) {
|
||||
val parsedInfo = JsonParser().parse(info).asJsonObject
|
||||
element.elementInfo = ElementInfo(parsedInfo["checked"]?.asBoolean ?: false,
|
||||
parsedInfo["toAbstract"]?.asBoolean ?: false)
|
||||
}
|
||||
}
|
||||
|
||||
internal data class ElementInfo(val checked: Boolean, val toAbstract: Boolean)
|
||||
|
||||
internal var PsiElement.elementInfo: ElementInfo by NotNullableUserDataProperty(Key.create("ELEMENT_INFO"), ElementInfo(false, false))
|
||||
|
||||
internal fun <T : MemberInfoBase<*>> chooseMembers(members: List<T>): List<T> {
|
||||
members.forEach {
|
||||
val info = it.member.elementInfo
|
||||
it.isChecked = info.checked
|
||||
it.isToAbstract = info.toAbstract
|
||||
}
|
||||
return members.filter { it.isChecked }
|
||||
}
|
||||
+39
-4
@@ -22,6 +22,7 @@ import com.intellij.ide.DataManager
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.psi.PsiComment
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiJavaFile
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager
|
||||
@@ -33,10 +34,16 @@ import com.intellij.refactoring.introduceParameter.AbstractJavaInplaceIntroducer
|
||||
import com.intellij.refactoring.introduceParameter.IntroduceParameterProcessor
|
||||
import com.intellij.refactoring.introduceParameter.Util
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||
import com.intellij.refactoring.util.DocCommentPolicy
|
||||
import com.intellij.refactoring.util.occurrences.ExpressionOccurrenceManager
|
||||
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
|
||||
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
|
||||
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.extractFunction.EXTRACT_FUNCTION
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
|
||||
@@ -46,22 +53,23 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinI
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeAlias.KotlinIntroduceTypeAliasHandler
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceTypeParameter.KotlinIntroduceTypeParameterHandler
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler
|
||||
import org.jetbrains.kotlin.idea.refactoring.markMembersInfo
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.extractClassMembers
|
||||
import org.jetbrains.kotlin.idea.refactoring.selectElement
|
||||
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
|
||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.jetbrains.kotlin.test.util.findElementByCommentPrefix
|
||||
import org.jetbrains.kotlin.utils.emptyOrSingletonList
|
||||
import java.io.File
|
||||
import java.lang.AssertionError
|
||||
import java.util.*
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@@ -346,6 +354,33 @@ abstract class AbstractExtractionTest() : KotlinLightCodeInsightFixtureTestCase(
|
||||
}
|
||||
}
|
||||
|
||||
protected fun doExtractSuperclassTest(path: String) {
|
||||
doTest(path) { file ->
|
||||
file as KtFile
|
||||
|
||||
markMembersInfo(file)
|
||||
|
||||
val targetParent = file.findElementByCommentPrefix("// SIBLING:")?.parent ?: file.parent!!
|
||||
val fileText = file.text
|
||||
val className = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// NAME:")!!
|
||||
val editor = fixture.editor
|
||||
val originalClass = file.findElementAt(editor.caretModel.offset)?.getStrictParentOfType<KtClassOrObject>()!!
|
||||
val memberInfos = chooseMembers(extractClassMembers(originalClass))
|
||||
val conflicts = ExtractSuperclassRefactoring.collectConflicts(originalClass, memberInfos, targetParent, className)
|
||||
project.checkConflictsInteractively(conflicts) {
|
||||
val extractInfo = ExtractSuperclassInfo(
|
||||
originalClass,
|
||||
memberInfos,
|
||||
targetParent,
|
||||
"$className.${KotlinFileType.EXTENSION}",
|
||||
className,
|
||||
DocCommentPolicy<PsiComment>(DocCommentPolicy.ASIS)
|
||||
)
|
||||
ExtractSuperclassRefactoring(extractInfo).performRefactoring()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun doTest(path: String, checkAdditionalAfterdata: Boolean = false, action: (PsiFile) -> Unit) {
|
||||
val mainFile = File(path)
|
||||
val afterFile = File("$path.after")
|
||||
|
||||
+75
@@ -4183,4 +4183,79 @@ public class ExtractionTestGenerated extends AbstractExtractionTest {
|
||||
doIntroduceTypeAliasTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/refactoring/extractSuperclass")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class ExtractSuperclass extends AbstractExtractionTest {
|
||||
@TestMetadata("addSuperclassNoSecondaryConstructors.kt")
|
||||
public void testAddSuperclassNoSecondaryConstructors() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractSuperclass/addSuperclassNoSecondaryConstructors.kt");
|
||||
doExtractSuperclassTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("addSuperclassOnlySecondaryConstructors.kt")
|
||||
public void testAddSuperclassOnlySecondaryConstructors() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractSuperclass/addSuperclassOnlySecondaryConstructors.kt");
|
||||
doExtractSuperclassTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("addSuperclassPrimaryConstructor.kt")
|
||||
public void testAddSuperclassPrimaryConstructor() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractSuperclass/addSuperclassPrimaryConstructor.kt");
|
||||
doExtractSuperclassTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("addTypeParameters.kt")
|
||||
public void testAddTypeParameters() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractSuperclass/addTypeParameters.kt");
|
||||
doExtractSuperclassTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("addTypeParametersWithAbstract.kt")
|
||||
public void testAddTypeParametersWithAbstract() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractSuperclass/addTypeParametersWithAbstract.kt");
|
||||
doExtractSuperclassTest(fileName);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInExtractSuperclass() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/extractSuperclass"), Pattern.compile("^(.+)\\.(kt|kts)$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("annotation.kt")
|
||||
public void testAnnotation() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractSuperclass/annotation.kt");
|
||||
doExtractSuperclassTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("enum.kt")
|
||||
public void testEnum() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractSuperclass/enum.kt");
|
||||
doExtractSuperclassTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("interface.kt")
|
||||
public void testInterface() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractSuperclass/interface.kt");
|
||||
doExtractSuperclassTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("privateClass.kt")
|
||||
public void testPrivateClass() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractSuperclass/privateClass.kt");
|
||||
doExtractSuperclassTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("privateMember.kt")
|
||||
public void testPrivateMember() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractSuperclass/privateMember.kt");
|
||||
doExtractSuperclassTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("replaceSuperclass.kt")
|
||||
public void testReplaceSuperclass() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/extractSuperclass/replaceSuperclass.kt");
|
||||
doExtractSuperclassTest(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.intellij.refactoring.util.classMembers.MemberInfoStorage
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import org.jetbrains.kotlin.idea.core.getPackage
|
||||
import org.jetbrains.kotlin.idea.refactoring.AbstractMemberPullPushTest
|
||||
import org.jetbrains.kotlin.idea.refactoring.chooseMembers
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.qualifiedClassNameForRendering
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.pushDown
|
||||
|
||||
import org.jetbrains.kotlin.idea.refactoring.AbstractMemberPullPushTest
|
||||
import org.jetbrains.kotlin.idea.refactoring.chooseMembers
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
|
||||
|
||||
abstract class AbstractPushDownTest : AbstractMemberPullPushTest() {
|
||||
|
||||
Reference in New Issue
Block a user