Move Inner Class to Upper Level: Fix replacement of outer class instances used in inner class constructor calls

#KT-13928 Fixed
This commit is contained in:
Alexey Sedunov
2016-09-29 16:36:54 +03:00
parent c76c834009
commit 86c123164a
8 changed files with 71 additions and 36 deletions
+1
View File
@@ -195,6 +195,7 @@ These artifacts include extensions for the types available in the latter JDKs, s
- Show "Variables and values captured in a closure" highlighting only for usages
- [`KT-13838`](https://youtrack.jetbrains.com/issue/KT-13838) Add file name to the presentation of private top-level declaration (Go to symbol, etc.)
- [`KT-14096`](https://youtrack.jetbrains.com/issue/KT-14096) Rename: When renaming Kotlin file outside of source root do not rename its namesake in a source root
- [`KT-13928`](https://youtrack.jetbrains.com/issue/KT-13928) Move Inner Class to Upper Level: Fix replacement of outer class instances used in inner class constructor calls
#### Intention actions, inspections and quickfixes
@@ -61,16 +61,18 @@ class KotlinImplicitThisUsage(
callElement: KtElement,
val targetDescriptor: DeclarationDescriptor
): KotlinImplicitReceiverUsage(callElement) {
override fun getNewReceiverText(): String {
val name = targetDescriptor.name
return when {
name.isSpecial -> "this"
DescriptorUtils.isCompanionObject(targetDescriptor) -> IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(targetDescriptor as ClassifierDescriptor)
else -> "this@${name.asString()}"
}
}
override fun getNewReceiverText() = explicateReceiverOf(targetDescriptor)
override fun processReplacedElement(element: KtElement) {
element.addToShorteningWaitSet(Options(removeThisLabels = true, removeThis = true))
}
}
fun explicateReceiverOf(descriptor: DeclarationDescriptor): String {
val name = descriptor.name
return when {
name.isSpecial -> "this"
DescriptorUtils.isCompanionObject(descriptor) -> IdeDescriptorRenderers.SOURCE_CODE.renderClassifierName(descriptor as ClassifierDescriptor)
else -> "this@${name.asString()}"
}
}
@@ -21,42 +21,28 @@ import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.refactoring.move.moveInner.MoveInnerClassUsagesHandler
import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.references.matchesTarget
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtQualifiedExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
import java.util.*
import org.jetbrains.kotlin.psi.KtValueArgumentList
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector
class MoveJavaInnerClassKotlinUsagesHandler: MoveInnerClassUsagesHandler {
override fun correctInnerClassUsage(usage: UsageInfo, outerClass: PsiClass) {
val innerCall = usage.element?.parent as? KtCallExpression ?: return
val receiver = (innerCall.parent as? KtQualifiedExpression)?.receiverExpression ?: return
val outerClassRefExpr = when (receiver) {
is KtCallExpression -> receiver.calleeExpression
is KtQualifiedExpression -> receiver.getQualifiedElementSelector()
else -> null
} as? KtSimpleNameExpression
val outerClassRef = outerClassRefExpr?.mainReference ?: return
if (!outerClassRef.matchesTarget(outerClass)) return
val receiverExpression = innerCall.getQualifiedExpressionForSelector()?.receiverExpression
val psiFactory = KtPsiFactory(usage.project)
val argumentList = innerCall.valueArgumentList
if (argumentList != null) {
val newArguments = ArrayList<String>()
newArguments.add(receiver.text!!)
argumentList.arguments.mapTo(newArguments) { it.text!! }
argumentList.replace(psiFactory.createCallArguments(newArguments.joinToString(prefix = "(", postfix = ")")))
}
else {
innerCall.lambdaArguments.firstOrNull()?.let { lambdaArg ->
val anchor = PsiTreeUtil.skipSiblingsBackward(lambdaArg, PsiWhiteSpace::class.java)
innerCall.addAfter(psiFactory.createCallArguments("(${receiver.text})"), anchor)
}
}
val argumentToAdd = psiFactory.createArgument(receiverExpression ?: psiFactory.createExpression("this"))
val argumentList =
innerCall.valueArgumentList
?: (innerCall.lambdaArguments.firstOrNull()?.let { lambdaArg ->
val anchor = PsiTreeUtil.skipSiblingsBackward(lambdaArg, PsiWhiteSpace::class.java)
innerCall.addAfter(psiFactory.createCallArguments("()"), anchor)
} as KtValueArgumentList?)
?: return
argumentList.addArgumentAfter(argumentToAdd, null)
}
}
@@ -0,0 +1,3 @@
class InnerClass(private val test: Test) {
fun fun1() {}
}
@@ -0,0 +1,14 @@
class Test {
val test = InnerClass(this)
}
fun main(args: Array<String>) {
val test = Test()
val innerClass = InnerClass(test)
innerClass.fun1()
}
fun Test.foo() {
InnerClass(this)
}
@@ -0,0 +1,17 @@
class Test {
inner class <caret>InnerClass {
fun fun1() {}
}
val test = InnerClass()
}
fun main(args: Array<String>) {
val test = Test()
val innerClass = test.InnerClass()
innerClass.fun1()
}
fun Test.foo() {
InnerClass()
}
@@ -0,0 +1,6 @@
{
"mainFile": "test.kt",
"type": "MOVE_KOTLIN_NESTED_CLASS",
"withRuntime": "true",
"outerInstanceParameter": "test"
}
@@ -317,6 +317,12 @@ public class MoveTestGenerated extends AbstractMoveTest {
doTest(fileName);
}
@TestMetadata("kotlin/moveNestedClass/innerToTopLevelAddInstanceParam/innerToTopLevelAddInstanceParam.test")
public void testKotlin_moveNestedClass_innerToTopLevelAddInstanceParam_InnerToTopLevelAddInstanceParam() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveNestedClass/innerToTopLevelAddInstanceParam/innerToTopLevelAddInstanceParam.test");
doTest(fileName);
}
@TestMetadata("kotlin/moveNestedClass/innerToTopLevelNoThis/innerToTopLevelNoThis.test")
public void testKotlin_moveNestedClass_innerToTopLevelNoThis_InnerToTopLevelNoThis() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveNestedClass/innerToTopLevelNoThis/innerToTopLevelNoThis.test");