Pull Up/Extract Super: Support properties declared in the primary constructor
This commit is contained in:
@@ -148,6 +148,7 @@ These artifacts include extensions for the types available in the latter JDKs, s
|
||||
- [`KT-13155`](https://youtrack.jetbrains.com/issue/KT-13155) Implement "Introduce Type Parameter" refactoring
|
||||
- [`KT-11017`](https://youtrack.jetbrains.com/issue/KT-11017) Implement "Extract Superclass" refactoring
|
||||
- [`KT-11017`](https://youtrack.jetbrains.com/issue/KT-11017) Implement "Extract Interface" refactoring
|
||||
Pull Up: Support properties declared in the primary constructor
|
||||
|
||||
#### Android Lint
|
||||
|
||||
|
||||
@@ -439,6 +439,8 @@ class KtPsiFactory(private val project: Project) {
|
||||
return argumentList.arguments.single()
|
||||
}
|
||||
|
||||
fun createArgument(text: String) = createCallArguments("($text)").arguments.first()!!
|
||||
|
||||
fun createSuperTypeCallEntry(text: String): KtSuperTypeCallEntry {
|
||||
return createClass("class A: $text").getSuperTypeListEntries().first() as KtSuperTypeCallEntry
|
||||
}
|
||||
|
||||
@@ -27,11 +27,11 @@ import com.intellij.refactoring.RefactoringActionHandler
|
||||
import com.intellij.refactoring.RefactoringBundle
|
||||
import com.intellij.refactoring.lang.ElementsHandler
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil
|
||||
import org.jetbrains.kotlin.idea.refactoring.canRefactor
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||
import java.util.HashSet
|
||||
import java.util.*
|
||||
|
||||
abstract class AbstractPullPushMembersHandler(
|
||||
private val refactoringName: String,
|
||||
@@ -43,6 +43,8 @@ abstract class AbstractPullPushMembersHandler(
|
||||
CommonRefactoringUtil.showErrorHint(project, editor, message, refactoringName, helpId)
|
||||
}
|
||||
|
||||
private fun KtParameter.getContainingClass() = if (hasValOrVar()) (ownerFunction as? KtPrimaryConstructor)?.containingClassOrObject else null
|
||||
|
||||
protected fun reportWrongContext(project: Project, editor: Editor?) {
|
||||
val message = RefactoringBundle.getCannotRefactorMessage(
|
||||
RefactoringBundle.message("is.not.supported.in.the.current.context", refactoringName)
|
||||
@@ -61,7 +63,9 @@ abstract class AbstractPullPushMembersHandler(
|
||||
editor.scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE)
|
||||
|
||||
val target = (file.findElementAt(offset) ?: return).parentsWithSelf.firstOrNull {
|
||||
it is KtClassOrObject || ((it is KtNamedFunction || it is KtProperty) && it.parent is KtClassBody)
|
||||
it is KtClassOrObject
|
||||
|| ((it is KtNamedFunction || it is KtProperty) && it.parent is KtClassBody)
|
||||
|| it is KtParameter && it.hasValOrVar() && it.ownerFunction is KtPrimaryConstructor
|
||||
}
|
||||
|
||||
if (target == null) {
|
||||
@@ -80,6 +84,7 @@ abstract class AbstractPullPushMembersHandler(
|
||||
|
||||
val (classOrObject, member) = when (element) {
|
||||
is KtNamedFunction, is KtProperty -> element.getStrictParentOfType<KtClassOrObject>() to element as KtNamedDeclaration?
|
||||
is KtParameter -> element.getContainingClass() to element
|
||||
is KtClassOrObject -> element to null
|
||||
else -> {
|
||||
reportWrongPosition(project, editor)
|
||||
@@ -93,8 +98,8 @@ abstract class AbstractPullPushMembersHandler(
|
||||
override fun isEnabledOnElements(elements: Array<out PsiElement>): Boolean {
|
||||
return elements.mapTo(HashSet<PsiElement>()) {
|
||||
when (it) {
|
||||
is KtNamedFunction, is KtProperty ->
|
||||
(it.parent as? KtClassBody)?.parent as? KtClassOrObject
|
||||
is KtNamedFunction, is KtProperty -> (it.parent as? KtClassBody)?.parent as? KtClassOrObject
|
||||
is KtParameter -> it.getContainingClass()
|
||||
is KtClassOrObject -> it
|
||||
else -> null
|
||||
} ?: return false
|
||||
|
||||
+2
-5
@@ -26,10 +26,7 @@ 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
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
class KotlinExtractInterfaceDialog(
|
||||
originalClass: KtClassOrObject,
|
||||
@@ -54,7 +51,7 @@ class KotlinExtractInterfaceDialog(
|
||||
return object : MemberInfoModelBase(extractableMemberInfos) {
|
||||
override fun isAbstractEnabled(memberInfo: KotlinMemberInfo): Boolean {
|
||||
val member = memberInfo.member
|
||||
return member is KtNamedFunction || (member is KtProperty && !member.mustBeAbstractInInterface())
|
||||
return member is KtNamedFunction || (member is KtProperty && !member.mustBeAbstractInInterface()) || member is KtParameter
|
||||
}
|
||||
|
||||
override fun isAbstractWhenDisabled(member: KotlinMemberInfo) = member.member is KtProperty
|
||||
|
||||
+2
-1
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.extractClassMembers
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
|
||||
class KotlinExtractSuperclassDialog(
|
||||
@@ -46,7 +47,7 @@ class KotlinExtractSuperclassDialog(
|
||||
return object : MemberInfoModelBase(extractClassMembers(originalClass)) {
|
||||
override fun isAbstractEnabled(memberInfo: KotlinMemberInfo): Boolean {
|
||||
val member = memberInfo.member
|
||||
return member is KtNamedFunction || member is KtProperty
|
||||
return member is KtNamedFunction || member is KtProperty || member is KtParameter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,8 +75,11 @@ import org.jetbrains.kotlin.idea.caches.resolve.getJavaMemberDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||
import org.jetbrains.kotlin.idea.core.*
|
||||
import org.jetbrains.kotlin.idea.highlighter.markers.getAccessorLightMethods
|
||||
import org.jetbrains.kotlin.idea.intentions.RemoveCurlyBracesFromTemplateIntention
|
||||
import org.jetbrains.kotlin.idea.j2k.IdeaJavaToKotlinServices
|
||||
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinValVar
|
||||
import org.jetbrains.kotlin.idea.refactoring.changeSignature.toValVar
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.string.collapseSpaces
|
||||
@@ -542,8 +545,9 @@ fun createJavaMethod(template: PsiMethod, targetClass: PsiClass): PsiMethod {
|
||||
return method
|
||||
}
|
||||
|
||||
fun createJavaField(property: KtProperty, targetClass: PsiClass): PsiField {
|
||||
val template = LightClassUtil.getLightClassPropertyMethods(property).getter
|
||||
fun createJavaField(property: KtNamedDeclaration, targetClass: PsiClass): PsiField {
|
||||
val accessorLightMethods = property.getAccessorLightMethods()
|
||||
val template = accessorLightMethods.getter
|
||||
?: throw AssertionError("Can't generate light method: ${property.getElementTextWithContext()}")
|
||||
|
||||
val factory = PsiElementFactory.SERVICE.getInstance(template.project)
|
||||
@@ -552,7 +556,7 @@ fun createJavaField(property: KtProperty, targetClass: PsiClass): PsiField {
|
||||
with(field.modifierList!!) {
|
||||
val templateModifiers = template.modifierList
|
||||
setModifierProperty(VisibilityUtil.getVisibilityModifier(templateModifiers), true)
|
||||
if (!property.isVar || targetClass.isInterface) {
|
||||
if ((property as KtValVarKeywordOwner).valOrVarKeyword.toValVar() != KotlinValVar.Var || targetClass.isInterface) {
|
||||
setModifierProperty(PsiModifier.FINAL, true)
|
||||
}
|
||||
copyModifierListItems(templateModifiers, this, false)
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.memberInfo
|
||||
|
||||
import com.intellij.psi.PsiField
|
||||
import com.intellij.psi.PsiMember
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.refactoring.RefactoringBundle
|
||||
import com.intellij.refactoring.classMembers.MemberInfoBase
|
||||
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.toLightElements
|
||||
import org.jetbrains.kotlin.asJava.unwrapped
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.MemberDescriptor
|
||||
@@ -31,6 +33,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
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) {
|
||||
@@ -72,7 +75,9 @@ fun KotlinMemberInfo.toJavaMemberInfo(): MemberInfo? {
|
||||
val declaration = member
|
||||
val psiMember: PsiMember? = when (declaration) {
|
||||
is KtNamedFunction -> declaration.getRepresentativeLightMethod()
|
||||
is KtProperty -> declaration.getRepresentativeLightMethod() ?: LightClassUtil.getLightClassPropertyMethods(declaration).backingField
|
||||
is KtProperty, is KtParameter -> declaration.toLightElements().let {
|
||||
it.firstIsInstanceOrNull<PsiMethod>() ?: it.firstIsInstanceOrNull<PsiField>()
|
||||
} as PsiMember?
|
||||
is KtClassOrObject -> declaration.toLightClass()
|
||||
else -> null
|
||||
}
|
||||
|
||||
@@ -102,6 +102,11 @@ fun extractClassMembers(
|
||||
.mapTo(result) { KotlinMemberInfo(it, true) }
|
||||
}
|
||||
|
||||
aClass.getPrimaryConstructor()
|
||||
?.valueParameters
|
||||
?.filter { it.hasValOrVar() }
|
||||
?.mapTo(result) { KotlinMemberInfo(it) }
|
||||
|
||||
aClass.declarations
|
||||
.filter { it is KtNamedDeclaration
|
||||
&& it !is KtConstructor<*>
|
||||
|
||||
+4
-3
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.idea.KotlinIconProvider
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import javax.swing.Icon
|
||||
|
||||
@@ -36,7 +37,7 @@ class KotlinMemberSelectionTable(
|
||||
if (memberInfo.isStatic()) return null
|
||||
|
||||
val member = memberInfo.member
|
||||
if (member !is KtNamedFunction && member !is KtProperty) return null
|
||||
if (member !is KtNamedFunction && member !is KtProperty && member !is KtParameter) return null
|
||||
|
||||
if (member.hasModifier(KtTokens.ABSTRACT_KEYWORD)) {
|
||||
myMemberInfoModel.isFixedAbstract(memberInfo)?.let { return it }
|
||||
@@ -51,7 +52,7 @@ class KotlinMemberSelectionTable(
|
||||
if (memberInfo.isStatic()) return false
|
||||
|
||||
val member = memberInfo.member
|
||||
if (member !is KtNamedFunction && member !is KtProperty) return false
|
||||
if (member !is KtNamedFunction && member !is KtProperty && member !is KtParameter) return false
|
||||
|
||||
if (member.hasModifier(KtTokens.ABSTRACT_KEYWORD)) {
|
||||
myMemberInfoModel.isFixedAbstract(memberInfo)?.let { return false }
|
||||
@@ -68,7 +69,7 @@ class KotlinMemberSelectionTable(
|
||||
val defaultIcon = AbstractMemberSelectionTable.EMPTY_OVERRIDE_ICON
|
||||
|
||||
val member = memberInfo.member
|
||||
if (member !is KtNamedFunction && member !is KtProperty) return defaultIcon
|
||||
if (member !is KtNamedFunction && member !is KtProperty && member !is KtParameter) return defaultIcon
|
||||
|
||||
return when (memberInfo.getOverrides()) {
|
||||
true -> AllIcons.General.OverridingMethod
|
||||
|
||||
@@ -25,7 +25,9 @@ import org.jetbrains.kotlin.idea.refactoring.memberInfo.getClassDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
||||
import org.jetbrains.kotlin.resolve.scopes.utils.collectDescriptorsFiltered
|
||||
import org.jetbrains.kotlin.types.*
|
||||
@@ -42,10 +44,21 @@ class KotlinPullUpData(val sourceClass: KtClassOrObject,
|
||||
|
||||
val sourceClassDescriptor = sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, sourceClass] as ClassDescriptor
|
||||
|
||||
val memberDescriptors = membersToMove.keysToMap { sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it]!! }
|
||||
val memberDescriptors = membersToMove.keysToMap {
|
||||
if (it is KtParameter) {
|
||||
sourceClassContext[BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, it]!!
|
||||
}
|
||||
else {
|
||||
sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it]!!
|
||||
}
|
||||
}
|
||||
|
||||
val targetClassDescriptor = targetClass.getClassDescriptorIfAny(resolutionFacade)!!
|
||||
|
||||
val superEntryForTargetClass = sourceClass.getSuperTypeEntryByDescriptor(targetClassDescriptor, sourceClassContext)
|
||||
|
||||
val targetClassSuperResolvedCall = superEntryForTargetClass.getResolvedCall(sourceClassContext)
|
||||
|
||||
val typeParametersInSourceClassContext by lazy {
|
||||
sourceClassDescriptor.declaredTypeParameters +
|
||||
sourceClass.getResolutionScope(sourceClassContext, resolutionFacade)
|
||||
|
||||
@@ -65,12 +65,13 @@ class KotlinPullUpDialog(
|
||||
if (!superClass.isInterface()) return true
|
||||
|
||||
val member = memberInfo.member
|
||||
return member is KtNamedFunction || (member is KtProperty && !member.mustBeAbstractInInterface())
|
||||
return member is KtNamedFunction || (member is KtProperty && !member.mustBeAbstractInInterface()) || member is KtParameter
|
||||
}
|
||||
|
||||
override fun isAbstractWhenDisabled(memberInfo: KotlinMemberInfo): Boolean {
|
||||
val member = memberInfo.member
|
||||
return (member is KtProperty && superClass !is PsiClass) || (member is KtNamedFunction && superClass is PsiClass)
|
||||
return ((member is KtProperty || member is KtParameter) && superClass !is PsiClass)
|
||||
|| (member is KtNamedFunction && superClass is PsiClass)
|
||||
}
|
||||
|
||||
override fun isMemberEnabled(memberInfo: KotlinMemberInfo): Boolean {
|
||||
|
||||
@@ -34,6 +34,7 @@ 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.replaced
|
||||
import org.jetbrains.kotlin.idea.intentions.setType
|
||||
import org.jetbrains.kotlin.idea.refactoring.createJavaField
|
||||
import org.jetbrains.kotlin.idea.refactoring.dropOverrideKeywordIfNecessary
|
||||
@@ -42,11 +43,9 @@ import org.jetbrains.kotlin.idea.util.anonymousObjectSuperTypeOrNull
|
||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiUnifier
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.allChildren
|
||||
import org.jetbrains.kotlin.psi.psiUtil.asAssignment
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
@@ -380,11 +379,16 @@ class KotlinPullUpHelper(
|
||||
val lightMethod = member.getRepresentativeLightMethod()!!
|
||||
|
||||
val movedMember: PsiMember = when (member) {
|
||||
is KtProperty -> {
|
||||
is KtProperty, is KtParameter -> {
|
||||
val newType = substitutor.substitute(lightMethod.returnType)
|
||||
val newField = createJavaField(member, data.targetClass)
|
||||
newField.typeElement?.replace(elementFactory.createTypeElement(newType))
|
||||
member.delete()
|
||||
if (member is KtParameter) {
|
||||
(member.parent as? KtParameterList)?.removeParameter(member)
|
||||
}
|
||||
else {
|
||||
member.delete()
|
||||
}
|
||||
newField
|
||||
}
|
||||
is KtNamedFunction -> {
|
||||
@@ -448,6 +452,8 @@ class KotlinPullUpHelper(
|
||||
val movedMember: KtCallableDeclaration
|
||||
val clashingSuper = fixOverrideAndGetClashingSuper(member, memberCopy)
|
||||
|
||||
val psiFactory = KtPsiFactory(member)
|
||||
|
||||
val originalIsAbstract = member.hasModifier(KtTokens.ABSTRACT_KEYWORD)
|
||||
val toAbstract = when {
|
||||
info.isToAbstract -> true
|
||||
@@ -469,7 +475,33 @@ class KotlinPullUpHelper(
|
||||
}
|
||||
else {
|
||||
movedMember = doAddCallableMember(memberCopy, clashingSuper, data.targetClass)
|
||||
member.delete()
|
||||
if (member is KtParameter && movedMember is KtParameter) {
|
||||
member.valOrVarKeyword?.delete()
|
||||
|
||||
val superEntry = data.superEntryForTargetClass
|
||||
val superResolvedCall = data.targetClassSuperResolvedCall
|
||||
if (superResolvedCall != null) {
|
||||
val superCall = if (superEntry !is KtSuperTypeCallEntry || superEntry.valueArgumentList == null) {
|
||||
superEntry!!.replaced(psiFactory.createSuperTypeCallEntry("${superEntry.text}()"))
|
||||
} else superEntry
|
||||
val argumentList = superCall.valueArgumentList!!
|
||||
|
||||
val parameterIndex = movedMember.parameterIndex()
|
||||
val prevParameterDescriptor = superResolvedCall.resultingDescriptor.valueParameters.getOrNull(parameterIndex - 1)
|
||||
val prevArgument = superResolvedCall.valueArguments[prevParameterDescriptor]?.arguments?.singleOrNull() as? KtValueArgument
|
||||
val newArgumentName = if (prevArgument != null && prevArgument.isNamed()) Name.identifier(member.name!!) else null
|
||||
val newArgument = psiFactory.createArgument(psiFactory.createExpression(member.name!!), newArgumentName)
|
||||
if (prevArgument == null) {
|
||||
argumentList.addArgument(newArgument)
|
||||
}
|
||||
else {
|
||||
argumentList.addArgumentAfter(newArgument, prevArgument)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
member.delete()
|
||||
}
|
||||
}
|
||||
|
||||
if (originalIsAbstract && data.isInterfaceTarget) {
|
||||
|
||||
@@ -90,13 +90,22 @@ internal fun KotlinPullUpData.getClashingMemberInTargetClass(memberDescriptor: C
|
||||
private fun KotlinPullUpData.checkClashWithSuperDeclaration(
|
||||
member: KtNamedDeclaration,
|
||||
memberDescriptor: DeclarationDescriptor,
|
||||
conflicts: MultiMap<PsiElement, String>) {
|
||||
conflicts: MultiMap<PsiElement, String>
|
||||
) {
|
||||
val message = "${targetClassDescriptor.renderForConflicts()} already contains ${memberDescriptor.renderForConflicts()}"
|
||||
|
||||
if (member is KtParameter) {
|
||||
if (((targetClass as? KtClass)?.getPrimaryConstructorParameters() ?: emptyList()).any { it.name == member.name }) {
|
||||
conflicts.putValue(member, message.capitalize())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (memberDescriptor !is CallableMemberDescriptor) return
|
||||
|
||||
val clashingSuper = getClashingMemberInTargetClass(memberDescriptor) ?: return
|
||||
if (clashingSuper.modality == Modality.ABSTRACT) return
|
||||
if (clashingSuper.kind != CallableMemberDescriptor.Kind.DECLARATION) return
|
||||
val message = "${targetClassDescriptor.renderForConflicts()} already contains ${memberDescriptor.renderForConflicts()}"
|
||||
conflicts.putValue(member, message.capitalize())
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.intentions.setType
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.idea.util.anonymousObjectSuperTypeOrNull
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
@@ -39,10 +38,10 @@ fun KtProperty.mustBeAbstractInInterface() =
|
||||
|
||||
fun KtNamedDeclaration.canMoveMemberToJavaClass(targetClass: PsiClass): Boolean {
|
||||
return when (this) {
|
||||
is KtProperty -> {
|
||||
is KtProperty, is KtParameter -> {
|
||||
if (targetClass.isInterface) return false
|
||||
if (hasModifier(KtTokens.OPEN_KEYWORD) || hasModifier(KtTokens.ABSTRACT_KEYWORD)) return false
|
||||
if (accessors.isNotEmpty() || delegateExpression != null) return false
|
||||
if (this is KtProperty && (accessors.isNotEmpty() || delegateExpression != null)) return false
|
||||
true
|
||||
}
|
||||
is KtNamedFunction -> valueParameters.all { it.defaultValue == null }
|
||||
@@ -51,22 +50,38 @@ fun KtNamedDeclaration.canMoveMemberToJavaClass(targetClass: PsiClass): Boolean
|
||||
}
|
||||
|
||||
fun addMemberToTarget(targetMember: KtNamedDeclaration, targetClass: KtClassOrObject): KtNamedDeclaration {
|
||||
if (targetMember is KtParameter) {
|
||||
val parameterList = (targetClass as KtClass).createPrimaryConstructorIfAbsent().valueParameterList!!
|
||||
val anchor = parameterList.parameters.firstOrNull { it.isVarArg || it.hasDefaultValue() }
|
||||
return parameterList.addParameterBefore(targetMember, anchor)
|
||||
}
|
||||
|
||||
val anchor = targetClass.declarations.filterIsInstance(targetMember.javaClass).lastOrNull()
|
||||
val movedMember = when {
|
||||
anchor == null && targetMember is KtProperty -> targetClass.addDeclarationBefore(targetMember, null)
|
||||
else -> targetClass.addDeclarationAfter(targetMember, anchor)
|
||||
}
|
||||
return movedMember as KtNamedDeclaration
|
||||
return movedMember
|
||||
}
|
||||
|
||||
private fun KtParameter.toProperty(): KtProperty = KtPsiFactory(this).createProperty(text)
|
||||
|
||||
fun doAddCallableMember(
|
||||
memberCopy: KtCallableDeclaration,
|
||||
clashingSuper: KtCallableDeclaration?,
|
||||
targetClass: KtClass): KtCallableDeclaration {
|
||||
targetClass: KtClass
|
||||
): KtCallableDeclaration {
|
||||
val memberToAdd =
|
||||
if (memberCopy is KtParameter && (memberCopy.hasModifier(KtTokens.ABSTRACT_KEYWORD) || targetClass.isInterface())) {
|
||||
memberCopy.toProperty()
|
||||
}
|
||||
else memberCopy
|
||||
|
||||
if (clashingSuper != null && clashingSuper.hasModifier(KtTokens.ABSTRACT_KEYWORD)) {
|
||||
return clashingSuper.replaced(memberCopy)
|
||||
return clashingSuper.replaced(if (memberToAdd is KtParameter && clashingSuper is KtProperty) memberToAdd.toProperty() else memberToAdd)
|
||||
}
|
||||
return addMemberToTarget(memberCopy, targetClass) as KtCallableDeclaration
|
||||
|
||||
return addMemberToTarget(memberToAdd, targetClass) as KtCallableDeclaration
|
||||
}
|
||||
|
||||
// TODO: Formatting rules don't apply here for some reason
|
||||
@@ -85,13 +100,11 @@ fun KtClass.makeAbstract() {
|
||||
fun KtClassOrObject.getSuperTypeEntryByDescriptor(
|
||||
descriptor: ClassDescriptor,
|
||||
context: BindingContext
|
||||
): KtSuperTypeEntry? {
|
||||
return getSuperTypeListEntries()
|
||||
.filterIsInstance<KtSuperTypeEntry>()
|
||||
.firstOrNull {
|
||||
val referencedType = context[BindingContext.TYPE, it.typeReference]
|
||||
referencedType?.constructor?.declarationDescriptor == descriptor
|
||||
}
|
||||
): KtSuperTypeListEntry? {
|
||||
return getSuperTypeListEntries().firstOrNull {
|
||||
val referencedType = context[BindingContext.TYPE, it.typeReference]
|
||||
referencedType?.constructor?.declarationDescriptor == descriptor
|
||||
}
|
||||
}
|
||||
|
||||
fun makeAbstract(member: KtCallableDeclaration,
|
||||
@@ -136,7 +149,7 @@ fun makeAbstract(member: KtCallableDeclaration,
|
||||
}
|
||||
|
||||
fun addSuperTypeEntry(
|
||||
delegator: KtSuperTypeEntry,
|
||||
delegator: KtSuperTypeListEntry,
|
||||
targetClass: KtClassOrObject,
|
||||
targetClassDescriptor: ClassDescriptor,
|
||||
context: BindingContext,
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.jetbrains.kotlin.idea.refactoring.pullUp.PULL_MEMBERS_UP
|
||||
import org.jetbrains.kotlin.psi.KtClass
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isInheritable
|
||||
|
||||
val PUSH_MEMBERS_DOWN = "Push Members Down"
|
||||
@@ -71,7 +72,7 @@ class KotlinPushDownHandler : AbstractPullPushMembersHandler(
|
||||
return
|
||||
}
|
||||
|
||||
val members = KotlinMemberInfoStorage(classOrObject).getClassMemberInfos(classOrObject)
|
||||
val members = KotlinMemberInfoStorage(classOrObject).getClassMemberInfos(classOrObject).filter { it.member !is KtParameter }
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
val helper = dataContext?.getData(PUSH_DOWN_TEST_HELPER_KEY) as TestHelper
|
||||
val selectedMembers = helper.adjustMembers(members)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
class A {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class A {
|
||||
|
||||
public final int n;
|
||||
@NotNull
|
||||
public final String s;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// WITH_RUNTIME
|
||||
abstract class <caret>B(
|
||||
// INFO: {"checked": "true"}
|
||||
val n: Int,
|
||||
// INFO: {"checked": "true"}
|
||||
val s: String,
|
||||
val b: Boolean
|
||||
): A()
|
||||
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
abstract class B(
|
||||
// INFO: {"checked": "true"}
|
||||
// INFO: {"checked": "true"}
|
||||
val b: Boolean
|
||||
): A()
|
||||
@@ -0,0 +1,9 @@
|
||||
interface I
|
||||
|
||||
class <caret>B(
|
||||
// INFO: {"checked": "true", "toAbstract": "true"}
|
||||
val s: String,
|
||||
// INFO: {"checked": "true", "toAbstract": "true"}
|
||||
val b: Boolean,
|
||||
val i: Int
|
||||
) : I
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
interface I {
|
||||
val s: String
|
||||
val b: Boolean
|
||||
}
|
||||
|
||||
class B(
|
||||
// INFO: {"checked": "true", "toAbstract": "true"}
|
||||
override val s: String,
|
||||
// INFO: {"checked": "true", "toAbstract": "true"}
|
||||
override val b: Boolean,
|
||||
val i: Int
|
||||
) : I
|
||||
@@ -0,0 +1,11 @@
|
||||
open class A(n: Int) {
|
||||
|
||||
}
|
||||
|
||||
class <caret>B(
|
||||
// INFO: {"checked": "true"}
|
||||
val s: String,
|
||||
// INFO: {"checked": "true"}
|
||||
val b: Boolean,
|
||||
val i: Int
|
||||
) : A(1)
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
open class A(n: Int, val s: String, val b: Boolean) {
|
||||
|
||||
}
|
||||
|
||||
class B(
|
||||
// INFO: {"checked": "true"}
|
||||
s: String,
|
||||
// INFO: {"checked": "true"}
|
||||
b: Boolean,
|
||||
val i: Int
|
||||
) : A(1, s, b)
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
open class A(n: Int) {
|
||||
|
||||
}
|
||||
|
||||
class <caret>B(
|
||||
// INFO: {"checked": "true", "toAbstract": "true"}
|
||||
val s: String,
|
||||
// INFO: {"checked": "true", "toAbstract": "true"}
|
||||
val b: Boolean,
|
||||
val i: Int
|
||||
) : A(1)
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
abstract class A(n: Int) {
|
||||
abstract val s: String
|
||||
abstract val b: Boolean
|
||||
|
||||
}
|
||||
|
||||
class B(
|
||||
// INFO: {"checked": "true", "toAbstract": "true"}
|
||||
override val s: String,
|
||||
// INFO: {"checked": "true", "toAbstract": "true"}
|
||||
override val b: Boolean,
|
||||
val i: Int
|
||||
) : A(1)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
open class A(n: Int, s: String = "") {
|
||||
|
||||
}
|
||||
|
||||
class <caret>B(
|
||||
// INFO: {"checked": "true"}
|
||||
val i: Int
|
||||
) : A(1, "2")
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
open class A(n: Int, val i: Int, s: String = "") {
|
||||
|
||||
}
|
||||
|
||||
class B(
|
||||
// INFO: {"checked": "true"}
|
||||
i: Int
|
||||
) : A(1, i, "2")
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
open class A(n: Int, s: String = "") {
|
||||
|
||||
}
|
||||
|
||||
class <caret>B(
|
||||
// INFO: {"checked": "true"}
|
||||
val i: Int
|
||||
) : A(1)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
open class A(n: Int, val i: Int, s: String = "") {
|
||||
|
||||
}
|
||||
|
||||
class B(
|
||||
// INFO: {"checked": "true"}
|
||||
i: Int
|
||||
) : A(1, i)
|
||||
@@ -0,0 +1,8 @@
|
||||
open class A(n: Int, s: String) {
|
||||
|
||||
}
|
||||
|
||||
class <caret>B(
|
||||
// INFO: {"checked": "true"}
|
||||
val i: Int
|
||||
) : A(s = "2", n = 1)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
open class A(n: Int, s: String, val i: Int) {
|
||||
|
||||
}
|
||||
|
||||
class B(
|
||||
// INFO: {"checked": "true"}
|
||||
i: Int
|
||||
) : A(s = "2", i = i, n = 1)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
open class A(n: Int, s: String = "") {
|
||||
|
||||
}
|
||||
|
||||
class <caret>B(
|
||||
// INFO: {"checked": "true"}
|
||||
val i: Int
|
||||
) : A(s = "2", n = 1)
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
open class A(n: Int, val i: Int, s: String = "") {
|
||||
|
||||
}
|
||||
|
||||
class B(
|
||||
// INFO: {"checked": "true"}
|
||||
i: Int
|
||||
) : A(s = "2", n = 1, i = i)
|
||||
@@ -0,0 +1,8 @@
|
||||
open class A(n: Int, vararg s: String) {
|
||||
|
||||
}
|
||||
|
||||
class <caret>B(
|
||||
// INFO: {"checked": "true"}
|
||||
val i: Int
|
||||
) : A(1, "2", "3")
|
||||
@@ -0,0 +1,8 @@
|
||||
open class A(n: Int, val i: Int, vararg s: String) {
|
||||
|
||||
}
|
||||
|
||||
class B(
|
||||
// INFO: {"checked": "true"}
|
||||
i: Int
|
||||
) : A(1, i, "2", "3")
|
||||
@@ -0,0 +1,8 @@
|
||||
open class A(x: Int) {
|
||||
|
||||
}
|
||||
|
||||
class <caret>B(
|
||||
// INFO: {"checked": "true"}
|
||||
val x: String
|
||||
) : A(1)
|
||||
@@ -0,0 +1 @@
|
||||
Class A already contains property 'val x: String'
|
||||
@@ -49,6 +49,54 @@ public class PullUpTestGenerated extends AbstractPullUpTest {
|
||||
doKotlinTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("constructorParametersToInterface.kt")
|
||||
public void testConstructorParametersToInterface() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/k2k/constructorParametersToInterface.kt");
|
||||
doKotlinTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("constructorParametersToSuperClass.kt")
|
||||
public void testConstructorParametersToSuperClass() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/k2k/constructorParametersToSuperClass.kt");
|
||||
doKotlinTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("constructorParametersToSuperClassAndMakeAbstract.kt")
|
||||
public void testConstructorParametersToSuperClassAndMakeAbstract() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/k2k/constructorParametersToSuperClassAndMakeAbstract.kt");
|
||||
doKotlinTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("constructorParametersWithDefaultValue1.kt")
|
||||
public void testConstructorParametersWithDefaultValue1() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/k2k/constructorParametersWithDefaultValue1.kt");
|
||||
doKotlinTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("constructorParametersWithDefaultValue2.kt")
|
||||
public void testConstructorParametersWithDefaultValue2() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/k2k/constructorParametersWithDefaultValue2.kt");
|
||||
doKotlinTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("constructorParametersWithNamedArgs.kt")
|
||||
public void testConstructorParametersWithNamedArgs() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/k2k/constructorParametersWithNamedArgs.kt");
|
||||
doKotlinTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("constructorParametersWithNamedArgsAndDefault.kt")
|
||||
public void testConstructorParametersWithNamedArgsAndDefault() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/k2k/constructorParametersWithNamedArgsAndDefault.kt");
|
||||
doKotlinTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("constructorParametersWithVararg.kt")
|
||||
public void testConstructorParametersWithVararg() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/k2k/constructorParametersWithVararg.kt");
|
||||
doKotlinTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("defaultValuesInOverride.kt")
|
||||
public void testDefaultValuesInOverride() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/k2k/defaultValuesInOverride.kt");
|
||||
@@ -217,6 +265,12 @@ public class PullUpTestGenerated extends AbstractPullUpTest {
|
||||
doKotlinTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("parameterNameConflict.kt")
|
||||
public void testParameterNameConflict() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/k2k/parameterNameConflict.kt");
|
||||
doKotlinTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("parametersInPrimaryInitializer.kt")
|
||||
public void testParametersInPrimaryInitializer() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/k2k/parametersInPrimaryInitializer.kt");
|
||||
@@ -292,6 +346,12 @@ public class PullUpTestGenerated extends AbstractPullUpTest {
|
||||
KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/refactoring/pullUp/k2j"), Pattern.compile("^(.+)\\.kt$"));
|
||||
}
|
||||
|
||||
@TestMetadata("constructorParameterToClass.kt")
|
||||
public void testConstructorParameterToClass() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/k2j/constructorParameterToClass.kt");
|
||||
doKotlinTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("defaultValuesInOverride.kt")
|
||||
public void testDefaultValuesInOverride() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/k2j/defaultValuesInOverride.kt");
|
||||
|
||||
Reference in New Issue
Block a user