Pull Up/Extract Super: Support members declared in the companion object of the original class
This commit is contained in:
@@ -149,6 +149,7 @@ These artifacts include extensions for the types available in the latter JDKs, s
|
||||
- [`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
|
||||
Pull Up: Support properties declared in the primary constructor
|
||||
Pull Up: Support members declared in the companion object of the original class
|
||||
|
||||
#### Android Lint
|
||||
|
||||
|
||||
@@ -139,4 +139,17 @@ abstract class KtNamedDeclarationStub<T extends KotlinStubWithFqName<?>> extends
|
||||
}
|
||||
return KtNamedDeclarationUtil.getFQName(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() throws IncorrectOperationException {
|
||||
KtClassOrObject classOrObject = KtPsiUtilKt.getContainingClassOrObject(this);
|
||||
|
||||
super.delete();
|
||||
|
||||
if (classOrObject instanceof KtObjectDeclaration
|
||||
&& ((KtObjectDeclaration) classOrObject).isCompanion()
|
||||
&& classOrObject.getDeclarations().isEmpty()) {
|
||||
classOrObject.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -880,3 +880,8 @@ fun checkSuperMethods(
|
||||
|
||||
return askUserForMethodsToSearch(declarationDescriptor, overriddenElementsToDescriptor)
|
||||
}
|
||||
|
||||
fun KtNamedDeclaration.isCompanionMemberOf(klass: KtClassOrObject): Boolean {
|
||||
val containingObject = containingClassOrObject as? KtObjectDeclaration ?: return false
|
||||
return containingObject.isCompanion() && containingObject.containingClassOrObject == klass
|
||||
}
|
||||
@@ -36,7 +36,11 @@ import org.jetbrains.kotlin.renderer.DescriptorRendererModifier
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.singletonOrEmptySet
|
||||
|
||||
class KotlinMemberInfo(member: KtNamedDeclaration, val isSuperClass: Boolean = false) : MemberInfoBase<KtNamedDeclaration>(member) {
|
||||
class KotlinMemberInfo @JvmOverloads constructor(
|
||||
member: KtNamedDeclaration,
|
||||
val isSuperClass: Boolean = false,
|
||||
val isCompanionMember: Boolean = false
|
||||
) : MemberInfoBase<KtNamedDeclaration>(member) {
|
||||
companion object {
|
||||
private val RENDERER = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.withOptions {
|
||||
modifiers = DescriptorRendererModifier.INNER.singletonOrEmptySet()
|
||||
@@ -62,6 +66,9 @@ class KotlinMemberInfo(member: KtNamedDeclaration, val isSuperClass: Boolean = f
|
||||
if (memberDescriptor is MemberDescriptor && memberDescriptor.modality == Modality.ABSTRACT) {
|
||||
displayName = "abstract $displayName"
|
||||
}
|
||||
if (isCompanionMember) {
|
||||
displayName = "companion $displayName"
|
||||
}
|
||||
|
||||
val overriddenDescriptors = (memberDescriptor as? CallableMemberDescriptor)?.overriddenDescriptors ?: emptySet()
|
||||
if (overriddenDescriptors.isNotEmpty()) {
|
||||
|
||||
+19
-8
@@ -85,6 +85,21 @@ fun extractClassMembers(
|
||||
collectSuperTypeEntries: Boolean = true,
|
||||
filter: ((KtNamedDeclaration) -> Boolean)? = null
|
||||
): List<KotlinMemberInfo> {
|
||||
fun KtClassOrObject.extractFromClassBody(
|
||||
filter: ((KtNamedDeclaration) -> Boolean)?,
|
||||
isCompanion: Boolean,
|
||||
result: MutableCollection<KotlinMemberInfo>
|
||||
) {
|
||||
declarations
|
||||
.filter {
|
||||
it is KtNamedDeclaration
|
||||
&& it !is KtConstructor<*>
|
||||
&& !(it is KtObjectDeclaration && it.isCompanion())
|
||||
&& (filter == null || filter(it))
|
||||
}
|
||||
.mapTo(result) { KotlinMemberInfo(it as KtNamedDeclaration, isCompanionMember = isCompanion) }
|
||||
}
|
||||
|
||||
if (aClass !is KtClassOrObject) return emptyList()
|
||||
|
||||
val result = ArrayList<KotlinMemberInfo>()
|
||||
@@ -98,8 +113,8 @@ fun extractClassMembers(
|
||||
val classDescriptor = type?.constructor?.declarationDescriptor as? ClassDescriptor
|
||||
classDescriptor?.source?.getPsi() as? KtClass
|
||||
}
|
||||
.filter { it.isInterface() }
|
||||
.mapTo(result) { KotlinMemberInfo(it, true) }
|
||||
.filter { it.isInterface() }
|
||||
.mapTo(result) { KotlinMemberInfo(it, true) }
|
||||
}
|
||||
|
||||
aClass.getPrimaryConstructor()
|
||||
@@ -107,12 +122,8 @@ fun extractClassMembers(
|
||||
?.filter { it.hasValOrVar() }
|
||||
?.mapTo(result) { KotlinMemberInfo(it) }
|
||||
|
||||
aClass.declarations
|
||||
.filter { it is KtNamedDeclaration
|
||||
&& it !is KtConstructor<*>
|
||||
&& !(it is KtObjectDeclaration && it.isCompanion())
|
||||
&& (filter == null || filter(it)) }
|
||||
.mapTo(result) { KotlinMemberInfo(it as KtNamedDeclaration) }
|
||||
aClass.extractFromClassBody(filter, false, result)
|
||||
(aClass as? KtClass)?.getCompanionObjects()?.firstOrNull()?.extractFromClassBody(filter, true, result)
|
||||
|
||||
return result
|
||||
}
|
||||
+1
-1
@@ -34,7 +34,7 @@ class KotlinMemberSelectionTable(
|
||||
abstractColumnHeader: String?
|
||||
) : AbstractMemberSelectionTable<KtNamedDeclaration, KotlinMemberInfo>(memberInfos, memberInfoModel, abstractColumnHeader) {
|
||||
override fun getAbstractColumnValue(memberInfo: KotlinMemberInfo): Any? {
|
||||
if (memberInfo.isStatic()) return null
|
||||
if (memberInfo.isStatic || memberInfo.isCompanionMember) return null
|
||||
|
||||
val member = memberInfo.member
|
||||
if (member !is KtNamedFunction && member !is KtProperty && member !is KtParameter) return null
|
||||
|
||||
@@ -28,8 +28,12 @@ import com.intellij.refactoring.classMembers.MemberInfoModel
|
||||
import com.intellij.refactoring.memberPullUp.PullUpProcessor
|
||||
import com.intellij.refactoring.util.DocCommentPolicy
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.idea.refactoring.isCompanionMemberOf
|
||||
import org.jetbrains.kotlin.idea.refactoring.isInterfaceClass
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.*
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfoStorage
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionTable
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.toJavaMemberInfo
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
class KotlinPullUpDialog(
|
||||
@@ -62,14 +66,18 @@ class KotlinPullUpDialog(
|
||||
val superClass = superClass ?: return false
|
||||
if (superClass is PsiClass) return false
|
||||
if (superClass !is KtClass) return false
|
||||
if (!superClass.isInterface()) return true
|
||||
|
||||
val member = memberInfo.member
|
||||
if (member.isCompanionMemberOf(sourceClass)) return false
|
||||
|
||||
if (!superClass.isInterface()) return true
|
||||
|
||||
return member is KtNamedFunction || (member is KtProperty && !member.mustBeAbstractInInterface()) || member is KtParameter
|
||||
}
|
||||
|
||||
override fun isAbstractWhenDisabled(memberInfo: KotlinMemberInfo): Boolean {
|
||||
val member = memberInfo.member
|
||||
if (member.isCompanionMemberOf(sourceClass)) return false
|
||||
return ((member is KtProperty || member is KtParameter) && superClass !is PsiClass)
|
||||
|| (member is KtNamedFunction && superClass is PsiClass)
|
||||
}
|
||||
@@ -78,7 +86,10 @@ class KotlinPullUpDialog(
|
||||
val superClass = superClass ?: return false
|
||||
val member = memberInfo.member
|
||||
|
||||
if (superClass is PsiClass && !member.canMoveMemberToJavaClass(superClass)) return false
|
||||
if (superClass is PsiClass) {
|
||||
if (!member.canMoveMemberToJavaClass(superClass)) return false
|
||||
if (member.isCompanionMemberOf(sourceClass)) return false
|
||||
}
|
||||
if (memberInfo in memberInfoStorage.getDuplicatedMemberInfos(superClass)) return false
|
||||
if (member in memberInfoStorage.getExtending(superClass)) return false
|
||||
return true
|
||||
|
||||
@@ -34,10 +34,12 @@ import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
|
||||
import org.jetbrains.kotlin.idea.core.dropDefaultValue
|
||||
import org.jetbrains.kotlin.idea.core.getOrCreateCompanionObject
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.intentions.setType
|
||||
import org.jetbrains.kotlin.idea.refactoring.createJavaField
|
||||
import org.jetbrains.kotlin.idea.refactoring.dropOverrideKeywordIfNecessary
|
||||
import org.jetbrains.kotlin.idea.refactoring.isCompanionMemberOf
|
||||
import org.jetbrains.kotlin.idea.refactoring.safeDelete.removeOverrideModifier
|
||||
import org.jetbrains.kotlin.idea.util.anonymousObjectSuperTypeOrNull
|
||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiUnifier
|
||||
@@ -383,6 +385,9 @@ class KotlinPullUpHelper(
|
||||
val newType = substitutor.substitute(lightMethod.returnType)
|
||||
val newField = createJavaField(member, data.targetClass)
|
||||
newField.typeElement?.replace(elementFactory.createTypeElement(newType))
|
||||
if (member.isCompanionMemberOf(data.sourceClass)) {
|
||||
newField.modifierList?.setModifierProperty(PsiModifier.STATIC, true)
|
||||
}
|
||||
if (member is KtParameter) {
|
||||
(member.parent as? KtParameterList)?.removeParameter(member)
|
||||
}
|
||||
@@ -461,12 +466,15 @@ class KotlinPullUpHelper(
|
||||
member is KtProperty -> member.mustBeAbstractInInterface()
|
||||
else -> false
|
||||
}
|
||||
|
||||
val classToAddTo = if (member.isCompanionMemberOf(data.sourceClass)) data.targetClass.getOrCreateCompanionObject() else data.targetClass
|
||||
|
||||
if (toAbstract) {
|
||||
if (!originalIsAbstract) {
|
||||
makeAbstract(memberCopy, data.memberDescriptors[member] as CallableMemberDescriptor, data.sourceToTargetClassSubstitutor, data.targetClass)
|
||||
}
|
||||
|
||||
movedMember = doAddCallableMember(memberCopy, clashingSuper, data.targetClass)
|
||||
movedMember = doAddCallableMember(memberCopy, clashingSuper, classToAddTo)
|
||||
if (member.typeReference == null) {
|
||||
movedMember.typeReference?.addToShorteningWaitSet()
|
||||
}
|
||||
@@ -474,7 +482,7 @@ class KotlinPullUpHelper(
|
||||
removeOriginalMemberOrAddOverride(member)
|
||||
}
|
||||
else {
|
||||
movedMember = doAddCallableMember(memberCopy, clashingSuper, data.targetClass)
|
||||
movedMember = doAddCallableMember(memberCopy, clashingSuper, classToAddTo)
|
||||
if (member is KtParameter && movedMember is KtParameter) {
|
||||
member.valOrVarKeyword?.delete()
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.pullUp
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiNamedElement
|
||||
import com.intellij.refactoring.RefactoringBundle
|
||||
@@ -32,6 +33,7 @@ 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.containingClassOrObject
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
@@ -109,6 +111,13 @@ private fun KotlinPullUpData.checkClashWithSuperDeclaration(
|
||||
conflicts.putValue(member, message.capitalize())
|
||||
}
|
||||
|
||||
private fun PsiClass.isSourceOrTarget(data: KotlinPullUpData): Boolean {
|
||||
var element = unwrapped
|
||||
if (element is KtObjectDeclaration && element.isCompanion()) element = element.containingClassOrObject
|
||||
|
||||
return element == data.sourceClass || element == data.targetClass
|
||||
}
|
||||
|
||||
private fun KotlinPullUpData.checkAccidentalOverrides(
|
||||
member: KtNamedDeclaration,
|
||||
memberDescriptor: DeclarationDescriptor,
|
||||
@@ -119,7 +128,7 @@ private fun KotlinPullUpData.checkAccidentalOverrides(
|
||||
HierarchySearchRequest<PsiElement>(targetClass, targetClass.useScope)
|
||||
.searchInheritors()
|
||||
.asSequence()
|
||||
.filterNot { it.unwrapped == sourceClass || it.unwrapped == targetClass }
|
||||
.filterNot { it.isSourceOrTarget(this) }
|
||||
.mapNotNull { it.unwrapped as? KtClassOrObject }
|
||||
.forEach {
|
||||
val subClassDescriptor = resolutionFacade.resolveToDescriptor(it) as ClassDescriptor
|
||||
|
||||
@@ -64,18 +64,18 @@ fun addMemberToTarget(targetMember: KtNamedDeclaration, targetClass: KtClassOrOb
|
||||
return movedMember
|
||||
}
|
||||
|
||||
private fun KtParameter.needToBeAbstract(targetClass: KtClassOrObject): Boolean {
|
||||
return hasModifier(KtTokens.ABSTRACT_KEYWORD) || targetClass is KtClass && targetClass.isInterface()
|
||||
}
|
||||
|
||||
private fun KtParameter.toProperty(): KtProperty = KtPsiFactory(this).createProperty(text)
|
||||
|
||||
fun doAddCallableMember(
|
||||
memberCopy: KtCallableDeclaration,
|
||||
clashingSuper: KtCallableDeclaration?,
|
||||
targetClass: KtClass
|
||||
targetClass: KtClassOrObject
|
||||
): KtCallableDeclaration {
|
||||
val memberToAdd =
|
||||
if (memberCopy is KtParameter && (memberCopy.hasModifier(KtTokens.ABSTRACT_KEYWORD) || targetClass.isInterface())) {
|
||||
memberCopy.toProperty()
|
||||
}
|
||||
else memberCopy
|
||||
val memberToAdd = if (memberCopy is KtParameter && memberCopy.needToBeAbstract(targetClass)) memberCopy.toProperty() else memberCopy
|
||||
|
||||
if (clashingSuper != null && clashingSuper.hasModifier(KtTokens.ABSTRACT_KEYWORD)) {
|
||||
return clashingSuper.replaced(if (memberToAdd is KtParameter && clashingSuper is KtProperty) memberToAdd.toProperty() else memberToAdd)
|
||||
|
||||
Vendored
-2
@@ -1,6 +1,4 @@
|
||||
class Class {
|
||||
companion object {
|
||||
}
|
||||
}
|
||||
|
||||
fun f() {
|
||||
|
||||
@@ -3,6 +3,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
abstract class A {
|
||||
|
||||
public final int x;
|
||||
public static final int _x;
|
||||
|
||||
public abstract boolean foo(int n);
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
// WITH_RUNTIME
|
||||
abstract class <caret>B: A() {
|
||||
companion object {
|
||||
// INFO: {"checked": "true"}
|
||||
val _x = 1
|
||||
}
|
||||
|
||||
// INFO: {"checked": "true"}
|
||||
val x = 1
|
||||
// INFO: {"checked": "true"}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
abstract class B: A() {
|
||||
|
||||
// INFO: {"checked": "true"}
|
||||
val y: Int get() = 2
|
||||
// INFO: {"checked": "true"}
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
open class A
|
||||
|
||||
abstract class <caret>B: A() {
|
||||
companion object {
|
||||
// INFO: {"checked": "true"}
|
||||
val _x = 1
|
||||
|
||||
// INFO: {"checked": "true"}
|
||||
fun _foo(n: Int): Boolean = n > 0
|
||||
}
|
||||
|
||||
// INFO: {"checked": "true"}
|
||||
val x = 1
|
||||
// INFO: {"checked": "true"}
|
||||
|
||||
@@ -24,6 +24,14 @@ abstract class A {
|
||||
class Y {
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
// INFO: {"checked": "true"}
|
||||
val _x = 1
|
||||
|
||||
// INFO: {"checked": "true"}
|
||||
fun _foo(n: Int): Boolean = n > 0
|
||||
}
|
||||
}
|
||||
|
||||
abstract class B: A() {
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
interface T
|
||||
|
||||
abstract class <caret>B: T {
|
||||
companion object {
|
||||
// INFO: {"checked": "true"}
|
||||
val _x = 1
|
||||
|
||||
// INFO: {"checked": "true"}
|
||||
fun foo(n: Int): Boolean = n > 0
|
||||
}
|
||||
|
||||
// INFO: {"checked": "true"}
|
||||
val x = 1
|
||||
// INFO: {"checked": "true"}
|
||||
|
||||
@@ -19,9 +19,23 @@ interface T {
|
||||
class Y {
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
// INFO: {"checked": "true"}
|
||||
val _x: Int
|
||||
|
||||
// INFO: {"checked": "true"}
|
||||
fun foo(n: Int): Boolean = n > 0
|
||||
}
|
||||
}
|
||||
|
||||
abstract class B: T {
|
||||
companion object {
|
||||
// INFO: {"checked": "true"}
|
||||
override val _x = 1
|
||||
|
||||
}
|
||||
|
||||
// INFO: {"checked": "true"}
|
||||
override val x = 1
|
||||
// INFO: {"checked": "true"}
|
||||
|
||||
Reference in New Issue
Block a user