Pull Up: Move property initializers
This commit is contained in:
@@ -392,4 +392,6 @@ public fun JetFunctionLiteralArgument.getFunctionLiteralArgumentName(bindingCont
|
|||||||
return (resolvedCall?.getArgumentMapping(this) as? ArgumentMatch)?.valueParameter?.getName()
|
return (resolvedCall?.getArgumentMapping(this) as? ArgumentMatch)?.valueParameter?.getName()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun JetExpression.asAssignment(): JetBinaryExpression? =
|
||||||
|
if (JetPsiUtil.isAssignment(this)) this as JetBinaryExpression else null
|
||||||
|
|
||||||
|
|||||||
@@ -16,22 +16,21 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.refactoring.pullUp
|
package org.jetbrains.kotlin.idea.refactoring.pullUp
|
||||||
|
|
||||||
|
import com.intellij.lang.Language
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.*
|
||||||
import com.intellij.psi.PsiField
|
import com.intellij.psi.impl.light.LightField
|
||||||
import com.intellij.psi.PsiMember
|
|
||||||
import com.intellij.psi.PsiSubstitutor
|
|
||||||
import com.intellij.psi.search.LocalSearchScope
|
import com.intellij.psi.search.LocalSearchScope
|
||||||
import com.intellij.psi.search.searches.ReferencesSearch
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
import com.intellij.refactoring.classMembers.MemberInfoBase
|
import com.intellij.refactoring.classMembers.MemberInfoBase
|
||||||
import com.intellij.refactoring.memberPullUp.PullUpData
|
import com.intellij.refactoring.memberPullUp.PullUpData
|
||||||
import com.intellij.refactoring.memberPullUp.PullUpHelper
|
import com.intellij.refactoring.memberPullUp.PullUpHelper
|
||||||
import org.jetbrains.kotlin.asJava.getRepresentativeLightMethod
|
import com.intellij.refactoring.util.RefactoringUtil
|
||||||
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
|
import com.intellij.util.containers.MultiMap
|
||||||
import org.jetbrains.kotlin.asJava.toLightClass
|
import org.jetbrains.kotlin.asJava.*
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.idea.JetLanguage
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
|
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
|
||||||
import org.jetbrains.kotlin.idea.core.replaced
|
import org.jetbrains.kotlin.idea.core.replaced
|
||||||
import org.jetbrains.kotlin.idea.imports.importableFqName
|
import org.jetbrains.kotlin.idea.imports.importableFqName
|
||||||
@@ -39,23 +38,23 @@ import org.jetbrains.kotlin.idea.refactoring.safeDelete.removeOverrideModifier
|
|||||||
import org.jetbrains.kotlin.idea.references.mainReference
|
import org.jetbrains.kotlin.idea.references.mainReference
|
||||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||||
import org.jetbrains.kotlin.idea.util.ShortenReferences
|
import org.jetbrains.kotlin.idea.util.ShortenReferences
|
||||||
|
import org.jetbrains.kotlin.idea.util.psi.patternMatching.JetPsiUnifier
|
||||||
import org.jetbrains.kotlin.lexer.JetModifierKeywordToken
|
import org.jetbrains.kotlin.lexer.JetModifierKeywordToken
|
||||||
import org.jetbrains.kotlin.lexer.JetTokens
|
import org.jetbrains.kotlin.lexer.JetTokens
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.allChildren
|
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiverOrThis
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getExplicitReceiverValue
|
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getExplicitReceiverValue
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
|
||||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||||
import org.jetbrains.kotlin.types.Variance
|
import org.jetbrains.kotlin.types.Variance
|
||||||
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
||||||
import org.jetbrains.kotlin.util.findCallableMemberBySignature
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
import java.util.ArrayList
|
import org.jetbrains.kotlin.utils.keysToMap
|
||||||
import java.util.LinkedHashSet
|
import java.util.*
|
||||||
|
|
||||||
class KotlinPullUpHelper(
|
class KotlinPullUpHelper(
|
||||||
private val javaData: PullUpData,
|
private val javaData: PullUpData,
|
||||||
@@ -67,6 +66,212 @@ class KotlinPullUpHelper(
|
|||||||
private var JetElement.newTypeText: String? by CopyableUserDataProperty(Key.create("NEW_TYPE_TEXT"))
|
private var JetElement.newTypeText: String? by CopyableUserDataProperty(Key.create("NEW_TYPE_TEXT"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun JetExpression.isMovable(): Boolean {
|
||||||
|
return accept(
|
||||||
|
object: JetVisitor<Boolean, Nothing?>() {
|
||||||
|
override fun visitJetElement(element: JetElement, arg: Nothing?): Boolean {
|
||||||
|
return element.allChildren.all { (it as? JetElement)?.accept(this, arg) ?: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitJetFile(file: JetFile, data: Nothing?) = false
|
||||||
|
|
||||||
|
override fun visitSimpleNameExpression(expression: JetSimpleNameExpression, arg: Nothing?): Boolean {
|
||||||
|
val resolvedCall = expression.getResolvedCall(data.resolutionFacade.analyze(expression)) ?: return true
|
||||||
|
val receiver = (resolvedCall.getExplicitReceiverValue() as? ExpressionReceiver)?.getExpression()
|
||||||
|
if (receiver != null && receiver !is JetThisExpression && receiver !is JetSuperExpression) return true
|
||||||
|
|
||||||
|
var descriptor: DeclarationDescriptor = resolvedCall.getResultingDescriptor()
|
||||||
|
if (descriptor is ConstructorDescriptor) {
|
||||||
|
descriptor = descriptor.getContainingDeclaration()
|
||||||
|
}
|
||||||
|
// todo: local functions
|
||||||
|
if (descriptor is ValueParameterDescriptor) return true
|
||||||
|
if (descriptor is ClassDescriptor && !descriptor.isInner()) return true
|
||||||
|
if (descriptor is MemberDescriptor) {
|
||||||
|
if (descriptor.getSource().getPsi() in propertiesToMoveInitializers) return true
|
||||||
|
descriptor = descriptor.getContainingDeclaration()
|
||||||
|
}
|
||||||
|
return descriptor is PackageFragmentDescriptor
|
||||||
|
|| (descriptor is ClassDescriptor && DescriptorUtils.isSubclass(data.targetClassDescriptor, descriptor))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCommonInitializer(
|
||||||
|
currentInitializer: JetExpression?,
|
||||||
|
scope: JetBlockExpression?,
|
||||||
|
propertyDescriptor: PropertyDescriptor,
|
||||||
|
elementsToRemove: MutableSet<JetElement>): JetExpression? {
|
||||||
|
if (scope == null) return currentInitializer
|
||||||
|
|
||||||
|
var initializerCandidate: JetExpression? = null
|
||||||
|
|
||||||
|
for (statement in scope.getStatements()) {
|
||||||
|
statement.asAssignment()?.let body@ {
|
||||||
|
val lhs = JetPsiUtil.safeDeparenthesize(it.getLeft() ?: return@body)
|
||||||
|
val receiver = (lhs as? JetQualifiedExpression)?.getReceiverExpression()
|
||||||
|
if (receiver != null && receiver !is JetThisExpression) return@body
|
||||||
|
|
||||||
|
val resolvedCall = lhs.getResolvedCall(data.resolutionFacade.analyze(it)) ?: return@body
|
||||||
|
if (resolvedCall.getResultingDescriptor() != propertyDescriptor) return@body
|
||||||
|
|
||||||
|
if (initializerCandidate == null) {
|
||||||
|
if (currentInitializer == null) {
|
||||||
|
if (!statement.isMovable()) return null
|
||||||
|
|
||||||
|
initializerCandidate = statement
|
||||||
|
elementsToRemove.add(statement)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!JetPsiUnifier.DEFAULT.unify(statement, currentInitializer).matched) return null
|
||||||
|
|
||||||
|
initializerCandidate = currentInitializer
|
||||||
|
elementsToRemove.add(statement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!JetPsiUnifier.DEFAULT.unify(statement, initializerCandidate).matched) return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return initializerCandidate
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class InitializerInfo(
|
||||||
|
val initializer: JetExpression?,
|
||||||
|
val usedProperties: Set<JetProperty>,
|
||||||
|
val usedParameters: Set<JetParameter>,
|
||||||
|
val elementsToRemove: Set<JetElement>
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun getInitializerInfo(property: JetProperty,
|
||||||
|
propertyDescriptor: PropertyDescriptor,
|
||||||
|
targetConstructor: JetElement): InitializerInfo? {
|
||||||
|
val sourceConstructors = targetToSourceConstructors[targetConstructor] ?: return null
|
||||||
|
val elementsToRemove = LinkedHashSet<JetElement>()
|
||||||
|
val commonInitializer = sourceConstructors.fold(null as JetExpression?) { commonInitializer, constructor ->
|
||||||
|
val body = (constructor as? JetSecondaryConstructor)?.getBodyExpression()
|
||||||
|
getCommonInitializer(commonInitializer, body, propertyDescriptor, elementsToRemove)
|
||||||
|
}
|
||||||
|
if (commonInitializer == null) {
|
||||||
|
elementsToRemove.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
val usedProperties = LinkedHashSet<JetProperty>()
|
||||||
|
val usedParameters = LinkedHashSet<JetParameter>()
|
||||||
|
val visitor = object : JetTreeVisitorVoid() {
|
||||||
|
override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) {
|
||||||
|
val context = data.resolutionFacade.analyze(expression)
|
||||||
|
val resolvedCall = expression.getResolvedCall(context) ?: return
|
||||||
|
val receiver = (resolvedCall.getExplicitReceiverValue() as? ExpressionReceiver)?.getExpression()
|
||||||
|
if (receiver != null && receiver !is JetThisExpression) return
|
||||||
|
val target = (resolvedCall.getResultingDescriptor() as? DeclarationDescriptorWithSource)?.getSource()?.getPsi()
|
||||||
|
when (target) {
|
||||||
|
is JetParameter -> usedParameters.add(target)
|
||||||
|
is JetProperty -> usedProperties.add(target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commonInitializer?.accept(visitor)
|
||||||
|
if (targetConstructor == data.targetClass.getPrimaryConstructor() ?: data.targetClass) {
|
||||||
|
property.getInitializer()?.accept(visitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return InitializerInfo(commonInitializer, usedProperties, usedParameters, elementsToRemove)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val propertiesToMoveInitializers = with(data) {
|
||||||
|
membersToMove
|
||||||
|
.filterIsInstance<JetProperty>()
|
||||||
|
.filter {
|
||||||
|
val descriptor = memberDescriptors[it] as? PropertyDescriptor
|
||||||
|
descriptor != null && data.sourceClassContext[BindingContext.BACKING_FIELD_REQUIRED, descriptor] ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val targetToSourceConstructors = LinkedHashMap<JetElement, MutableList<JetElement>>().let { result ->
|
||||||
|
if (!data.targetClass.isInterface()) {
|
||||||
|
result[data.targetClass.getPrimaryConstructor() ?: data.targetClass] = ArrayList<JetElement>()
|
||||||
|
data.sourceClass.accept(
|
||||||
|
object : JetTreeVisitorVoid() {
|
||||||
|
private fun processConstructorReference(expression: JetReferenceExpression, callingConstructorElement: JetElement) {
|
||||||
|
val descriptor = data.resolutionFacade.analyze(expression)[BindingContext.REFERENCE_TARGET, expression]
|
||||||
|
val constructorElement = (descriptor as? DeclarationDescriptorWithSource)?.getSource()?.getPsi() ?: return
|
||||||
|
if (constructorElement == data.targetClass
|
||||||
|
|| (constructorElement as? JetConstructor<*>)?.getContainingClassOrObject() == data.targetClass) {
|
||||||
|
result.getOrPut(constructorElement as JetElement, { ArrayList() }).add(callingConstructorElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitDelegationToSuperCallSpecifier(specifier: JetDelegatorToSuperCall) {
|
||||||
|
val constructorRef = specifier.getCalleeExpression().getConstructorReferenceExpression() ?: return
|
||||||
|
val containingClass = specifier.getStrictParentOfType<JetClassOrObject>() ?: return
|
||||||
|
val callingConstructorElement = containingClass.getPrimaryConstructor() ?: containingClass
|
||||||
|
processConstructorReference(constructorRef, callingConstructorElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitSecondaryConstructor(constructor: JetSecondaryConstructor) {
|
||||||
|
val constructorRef = constructor.getDelegationCall().getCalleeExpression() ?: return
|
||||||
|
processConstructorReference(constructorRef, constructor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
private val targetConstructorToPropertyInitializerInfoMap = LinkedHashMap<JetElement, Map<JetProperty, InitializerInfo>>().let { result ->
|
||||||
|
for (targetConstructor in targetToSourceConstructors.keySet()) {
|
||||||
|
val propertyToInitializerInfo = LinkedHashMap<JetProperty, InitializerInfo>()
|
||||||
|
for (property in propertiesToMoveInitializers) {
|
||||||
|
val propertyDescriptor = data.memberDescriptors[property] as? PropertyDescriptor ?: continue
|
||||||
|
propertyToInitializerInfo[property] = getInitializerInfo(property, propertyDescriptor, targetConstructor) ?: continue
|
||||||
|
}
|
||||||
|
val unmovableProperties = RefactoringUtil.transitiveClosure(
|
||||||
|
object : RefactoringUtil.Graph<JetProperty> {
|
||||||
|
override fun getVertices() = propertyToInitializerInfo.keySet()
|
||||||
|
|
||||||
|
override fun getTargets(source: JetProperty) = propertyToInitializerInfo[source]?.usedProperties
|
||||||
|
},
|
||||||
|
{ !propertyToInitializerInfo.containsKey(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
propertyToInitializerInfo.keySet().removeAll(unmovableProperties)
|
||||||
|
result[targetConstructor] = propertyToInitializerInfo
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
private var dummyField: PsiField? = null
|
||||||
|
|
||||||
|
private fun addMovedMember(newMember: JetNamedDeclaration) {
|
||||||
|
if (newMember is JetProperty) {
|
||||||
|
// Add dummy light field since PullUpProcessor won't invoke moveFieldInitializations() if no PsiFields are present
|
||||||
|
if (dummyField == null) {
|
||||||
|
val factory = JavaPsiFacade.getElementFactory(newMember.getProject())
|
||||||
|
val dummyField = object: LightField(
|
||||||
|
newMember.getManager(),
|
||||||
|
factory.createField("dummy", PsiType.BOOLEAN),
|
||||||
|
factory.createClass("Dummy")
|
||||||
|
) {
|
||||||
|
// Prevent processing by JavaPullUpHelper
|
||||||
|
override fun getLanguage() = JetLanguage.INSTANCE
|
||||||
|
}
|
||||||
|
javaData.getMovedMembers().add(dummyField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when (newMember) {
|
||||||
|
is JetProperty, is JetNamedFunction -> {
|
||||||
|
newMember.getRepresentativeLightMethod()?.let { javaData.getMovedMembers().add(it) }
|
||||||
|
}
|
||||||
|
is JetClassOrObject -> {
|
||||||
|
newMember.toLightClass()?.let { javaData.getMovedMembers().add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun willBeUsedInSourceClass(member: PsiElement): Boolean {
|
private fun willBeUsedInSourceClass(member: PsiElement): Boolean {
|
||||||
return !ReferencesSearch
|
return !ReferencesSearch
|
||||||
.search(member, LocalSearchScope(data.sourceClass), false)
|
.search(member, LocalSearchScope(data.sourceClass), false)
|
||||||
@@ -351,8 +556,6 @@ class KotlinPullUpHelper(
|
|||||||
&& (movedMember.hasModifier(JetTokens.ABSTRACT_KEYWORD))) {
|
&& (movedMember.hasModifier(JetTokens.ABSTRACT_KEYWORD))) {
|
||||||
data.targetClass.addModifierWithSpace(JetTokens.ABSTRACT_KEYWORD)
|
data.targetClass.addModifierWithSpace(JetTokens.ABSTRACT_KEYWORD)
|
||||||
}
|
}
|
||||||
|
|
||||||
movedMember.getRepresentativeLightMethod()?.let { javaData.getMovedMembers().add(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is JetClassOrObject -> {
|
is JetClassOrObject -> {
|
||||||
@@ -361,13 +564,14 @@ class KotlinPullUpHelper(
|
|||||||
}
|
}
|
||||||
movedMember = addMemberToTarget(memberCopy) as JetClassOrObject
|
movedMember = addMemberToTarget(memberCopy) as JetClassOrObject
|
||||||
member.delete()
|
member.delete()
|
||||||
movedMember.toLightClass()?.let { javaData.getMovedMembers().add(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> return
|
else -> return
|
||||||
}
|
}
|
||||||
|
|
||||||
processMarkedElements(movedMember)
|
processMarkedElements(movedMember)
|
||||||
|
|
||||||
|
addMovedMember(movedMember)
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
clearMarking(markedElements)
|
clearMarking(markedElements)
|
||||||
@@ -379,7 +583,92 @@ class KotlinPullUpHelper(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun moveFieldInitializations(movedFields: LinkedHashSet<PsiField>) {
|
override fun moveFieldInitializations(movedFields: LinkedHashSet<PsiField>) {
|
||||||
|
val psiFactory = JetPsiFactory(data.sourceClass)
|
||||||
|
|
||||||
|
fun JetClassOrObject.getOrCreateClassInitializer(): JetClassInitializer {
|
||||||
|
getOrCreateBody().getDeclarations().lastOrNull { it is JetClassInitializer }?.let { return it as JetClassInitializer }
|
||||||
|
return addDeclaration(psiFactory.createAnonymousInitializer()) as JetClassInitializer
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JetElement.getConstructorBodyBlock(): JetBlockExpression? {
|
||||||
|
return when (this) {
|
||||||
|
is JetClassOrObject -> {
|
||||||
|
getOrCreateClassInitializer().getBody()
|
||||||
|
}
|
||||||
|
is JetPrimaryConstructor -> {
|
||||||
|
getContainingClassOrObject().getOrCreateClassInitializer().getBody()
|
||||||
|
}
|
||||||
|
is JetSecondaryConstructor -> {
|
||||||
|
getBodyExpression() ?: add(psiFactory.createEmptyBody())
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
} as? JetBlockExpression
|
||||||
|
}
|
||||||
|
|
||||||
|
fun JetClassOrObject.getDelegatorToSuperCall(): JetDelegatorToSuperCall? {
|
||||||
|
return getDelegationSpecifiers().singleOrNull { it is JetDelegatorToSuperCall } as? JetDelegatorToSuperCall
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addUsedParameters(constructorElement: JetElement, info: InitializerInfo) {
|
||||||
|
if (!info.usedParameters.isNotEmpty()) return
|
||||||
|
val constructor: JetConstructor<*> = when (constructorElement) {
|
||||||
|
is JetConstructor<*> -> constructorElement
|
||||||
|
is JetClass -> constructorElement.createPrimaryConstructorIfAbsent()
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
|
||||||
|
with(constructor.getValueParameterList()!!) {
|
||||||
|
info.usedParameters.forEach { addParameter(it) }
|
||||||
|
}
|
||||||
|
targetToSourceConstructors[constructorElement]!!.forEach {
|
||||||
|
val superCall: JetCallElement? = when (it) {
|
||||||
|
is JetClassOrObject -> it.getDelegatorToSuperCall()
|
||||||
|
is JetPrimaryConstructor -> it.getContainingClassOrObject().getDelegatorToSuperCall()
|
||||||
|
is JetSecondaryConstructor -> {
|
||||||
|
if (it.hasImplicitDelegationCall()) {
|
||||||
|
it.replaceImplicitDelegationCallWithExplicit(false)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
it.getDelegationCall()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
superCall?.getValueArgumentList()?.let { args ->
|
||||||
|
info.usedParameters.forEach {
|
||||||
|
args.addArgument(psiFactory.createArgument(psiFactory.createExpression(it.getName() ?: "_")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((constructorElement, propertyToInitializerInfo) in targetConstructorToPropertyInitializerInfoMap.entrySet()) {
|
||||||
|
val properties = propertyToInitializerInfo.keySet().toArrayList().sortBy(
|
||||||
|
object : Comparator<JetProperty> {
|
||||||
|
override fun compare(property1: JetProperty, property2: JetProperty): Int {
|
||||||
|
val info1 = propertyToInitializerInfo[property1]!!
|
||||||
|
val info2 = propertyToInitializerInfo[property2]!!
|
||||||
|
return when {
|
||||||
|
property2 in info1.usedProperties -> -1
|
||||||
|
property1 in info2.usedProperties -> 1
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for (oldProperty in properties) {
|
||||||
|
val info = propertyToInitializerInfo[oldProperty]!!
|
||||||
|
|
||||||
|
addUsedParameters(constructorElement, info)
|
||||||
|
|
||||||
|
info.initializer?.let {
|
||||||
|
val body = constructorElement.getConstructorBodyBlock()
|
||||||
|
body?.addAfter(it, body.getStatements().lastOrNull() ?: body.getLBrace()!!)
|
||||||
|
}
|
||||||
|
info.elementsToRemove.forEach { it.delete() }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateUsage(element: PsiElement) {
|
override fun updateUsage(element: PsiElement) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ interface I
|
|||||||
|
|
||||||
interface Z<T>
|
interface Z<T>
|
||||||
|
|
||||||
abstract class A<T: I, U: I, V> {
|
abstract class A<T: I, U: I, V>(x: X, y: Y) {
|
||||||
// INFO: {"checked": "true"}
|
// INFO: {"checked": "true"}
|
||||||
val foo1: T
|
val foo1: T
|
||||||
// INFO: {"checked": "true"}
|
// INFO: {"checked": "true"}
|
||||||
@@ -33,7 +33,7 @@ abstract class A<T: I, U: I, V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class C<W: I> {
|
class C<W: I> {
|
||||||
inner class B<X: I, Y>(x: X, y: Y): A<X, I, Z<Y>>() {
|
inner class B<X: I, Y>(x: X, y: Y): A<X, I, Z<Y>>(x, y) {
|
||||||
|
|
||||||
// INFO: {"checked": "true", "toAbstract": "true"}
|
// INFO: {"checked": "true", "toAbstract": "true"}
|
||||||
override val foo5 = x
|
override val foo5 = x
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
open class A
|
||||||
|
|
||||||
|
class <caret>B: A {
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val n: Int
|
||||||
|
|
||||||
|
constructor(a: Int) {
|
||||||
|
n = a + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
open class A(a: Int) {
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val n: Int
|
||||||
|
|
||||||
|
init {
|
||||||
|
n = a + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class B: A {
|
||||||
|
|
||||||
|
constructor(a: Int) : super(a) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
open class A
|
||||||
|
|
||||||
|
class <caret>B: A {
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val n: Int
|
||||||
|
|
||||||
|
constructor(a: Int) {
|
||||||
|
n = 1 + 2
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
n = 1 + 2
|
||||||
|
}
|
||||||
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
open class A {
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val n: Int
|
||||||
|
|
||||||
|
init {
|
||||||
|
n = 1 + 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class B: A {
|
||||||
|
|
||||||
|
constructor(a: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
open class A
|
||||||
|
|
||||||
|
class <caret>B: A {
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val n: Int
|
||||||
|
|
||||||
|
constructor(a: Int) {
|
||||||
|
n = a + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(a: Int, b: Int) {
|
||||||
|
n = a*b
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
open class A {
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val n: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
class B: A {
|
||||||
|
|
||||||
|
constructor(a: Int) {
|
||||||
|
n = a + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(a: Int, b: Int) {
|
||||||
|
n = a*b
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
open class A
|
||||||
|
|
||||||
|
class <caret>B: A {
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
var n: Int
|
||||||
|
|
||||||
|
constructor(a: Int) {
|
||||||
|
n = a + 1
|
||||||
|
n = a plus 1
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
open class A(a: Int) {
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
var n: Int
|
||||||
|
|
||||||
|
init {
|
||||||
|
n = a + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class B: A {
|
||||||
|
|
||||||
|
constructor(a: Int) : super(a) {
|
||||||
|
n = a plus 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
interface T
|
||||||
|
|
||||||
|
class <caret>A: T {
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val n: Int
|
||||||
|
|
||||||
|
constructor(a: Int) {
|
||||||
|
n = a + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
interface T {
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val n: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
class A: T {
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
override val n: Int
|
||||||
|
|
||||||
|
constructor(a: Int) {
|
||||||
|
n = a + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
open class A
|
||||||
|
|
||||||
|
class <caret>B(a: Int): A() {
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val n: Int = a + 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
open class A(a: Int) {
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val n: Int = a + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
class B(a: Int): A(a) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
open class A
|
||||||
|
|
||||||
|
class <caret>B: A {
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val n: Int
|
||||||
|
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val x: Int = 3
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val y: Int = 4
|
||||||
|
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val a: Int = 1
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val b: Int = x + y
|
||||||
|
|
||||||
|
constructor(p: Int) {
|
||||||
|
n = a + b - p
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
open class A(p: Int) {
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val n: Int
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val x: Int = 3
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val y: Int = 4
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val a: Int = 1
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val b: Int = x + y
|
||||||
|
|
||||||
|
init {
|
||||||
|
n = a + b - p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class B: A {
|
||||||
|
|
||||||
|
constructor(p: Int) : super(p) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
open class A
|
||||||
|
|
||||||
|
class <caret>B: A {
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val n: Int
|
||||||
|
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val x: Int = 3
|
||||||
|
// INFO: {"checked": "false"}
|
||||||
|
val y: Int = 4
|
||||||
|
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val a: Int = 1
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val b: Int = x + y
|
||||||
|
|
||||||
|
constructor(p: Int) {
|
||||||
|
n = a + b - p
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
open class A {
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val n: Int
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val x: Int = 3
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val a: Int = 1
|
||||||
|
// INFO: {"checked": "true"}
|
||||||
|
val b: Int = x + y
|
||||||
|
}
|
||||||
|
|
||||||
|
class B: A {
|
||||||
|
|
||||||
|
// INFO: {"checked": "false"}
|
||||||
|
val y: Int = 4
|
||||||
|
|
||||||
|
constructor(p: Int) {
|
||||||
|
n = a + b - p
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,12 +89,36 @@ public class PullUpTestGenerated extends AbstractPullUpTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("initializerInConstructor.kt")
|
||||||
|
public void testInitializerInConstructor() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/initializerInConstructor.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("initializerInMultipleConstructorsEq.kt")
|
||||||
|
public void testInitializerInMultipleConstructorsEq() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/initializerInMultipleConstructorsEq.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("initializerInMultipleConstructorsNonEq.kt")
|
||||||
|
public void testInitializerInMultipleConstructorsNonEq() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/initializerInMultipleConstructorsNonEq.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("innerClassToInterface.kt")
|
@TestMetadata("innerClassToInterface.kt")
|
||||||
public void testInnerClassToInterface() throws Exception {
|
public void testInnerClassToInterface() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/innerClassToInterface.kt");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/innerClassToInterface.kt");
|
||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("multipleInitializersInConstructorsEq.kt")
|
||||||
|
public void testMultipleInitializersInConstructorsEq() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/multipleInitializersInConstructorsEq.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("noCaret.kt")
|
@TestMetadata("noCaret.kt")
|
||||||
public void testNoCaret() throws Exception {
|
public void testNoCaret() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/noCaret.kt");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/noCaret.kt");
|
||||||
@@ -107,6 +131,12 @@ public class PullUpTestGenerated extends AbstractPullUpTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("noInitializationInInterface.kt")
|
||||||
|
public void testNoInitializationInInterface() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/noInitializationInInterface.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("noSuperClass.kt")
|
@TestMetadata("noSuperClass.kt")
|
||||||
public void testNoSuperClass() throws Exception {
|
public void testNoSuperClass() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/noSuperClass.kt");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/noSuperClass.kt");
|
||||||
@@ -119,6 +149,24 @@ public class PullUpTestGenerated extends AbstractPullUpTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("parametersInPrimaryInitializer.kt")
|
||||||
|
public void testParametersInPrimaryInitializer() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/parametersInPrimaryInitializer.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("propertyDependenceSatisfied.kt")
|
||||||
|
public void testPropertyDependenceSatisfied() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/propertyDependenceSatisfied.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("propertyDependenceUnsatisfied.kt")
|
||||||
|
public void testPropertyDependenceUnsatisfied() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/propertyDependenceUnsatisfied.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("publicToInterface.kt")
|
@TestMetadata("publicToInterface.kt")
|
||||||
public void testPublicToInterface() throws Exception {
|
public void testPublicToInterface() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/publicToInterface.kt");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/publicToInterface.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user