Move: Fix processing of callable references to nested Java classes and static methods

#KT-17827 Fixed
This commit is contained in:
Alexey Sedunov
2018-02-13 18:58:11 +03:00
parent 441f88c144
commit 17e64ebae0
46 changed files with 230 additions and 12 deletions
@@ -19,10 +19,7 @@ package org.jetbrains.kotlin.psi.psiUtil
import com.intellij.extapi.psi.StubBasedPsiElementBase import com.intellij.extapi.psi.StubBasedPsiElementBase
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement import com.intellij.psi.*
import com.intellij.psi.PsiParameter
import com.intellij.psi.PsiParameterList
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.stubs.StubElement import com.intellij.psi.stubs.StubElement
import com.intellij.psi.tree.TokenSet import com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.KtNodeTypes
@@ -596,4 +593,12 @@ fun String?.isIdentifier(): Boolean {
return lexer.tokenType == null return lexer.tokenType == null
} }
fun String.quoteIfNeeded(): String = if (this.isIdentifier()) this else "`$this`" fun String.quoteIfNeeded(): String = if (this.isIdentifier()) this else "`$this`"
fun PsiElement.isTopLevelKtOrJavaMember(): Boolean {
return when (this) {
is KtDeclaration -> parent is KtFile
is PsiClass -> containingClass == null && this.qualifiedName != null
else -> false
}
}
@@ -25,6 +25,7 @@ import com.intellij.util.SmartList
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference import org.jetbrains.kotlin.idea.caches.resolve.resolveImportReference
import org.jetbrains.kotlin.idea.codeInsight.shorten.addDelayedImportRequest
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
import org.jetbrains.kotlin.idea.core.ShortenReferences import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.copied import org.jetbrains.kotlin.idea.core.copied
@@ -152,9 +153,9 @@ class KtSimpleNameReference(expression: KtSimpleNameExpression) : KtSimpleRefere
bindToElement(element, ShorteningMode.DELAYED_SHORTENING) bindToElement(element, ShorteningMode.DELAYED_SHORTENING)
fun bindToElement(element: PsiElement, shorteningMode: ShorteningMode): PsiElement = fun bindToElement(element: PsiElement, shorteningMode: ShorteningMode): PsiElement =
element.getKotlinFqName()?.let { fqName -> bindToFqName(fqName, shorteningMode) } ?: expression element.getKotlinFqName()?.let { fqName -> bindToFqName(fqName, shorteningMode, element) } ?: expression
fun bindToFqName(fqName: FqName, shorteningMode: ShorteningMode = ShorteningMode.DELAYED_SHORTENING): PsiElement { fun bindToFqName(fqName: FqName, shorteningMode: ShorteningMode = ShorteningMode.DELAYED_SHORTENING, targetElement: PsiElement? = null): PsiElement {
val expression = expression val expression = expression
if (fqName.isRoot) return expression if (fqName.isRoot) return expression
@@ -162,7 +163,7 @@ class KtSimpleNameReference(expression: KtSimpleNameExpression) : KtSimpleRefere
if (expression !is KtNameReferenceExpression) return expression if (expression !is KtNameReferenceExpression) return expression
if (expression.parent is KtThisExpression || expression.parent is KtSuperExpression) return expression // TODO: it's a bad design of PSI tree, we should change it if (expression.parent is KtThisExpression || expression.parent is KtSuperExpression) return expression // TODO: it's a bad design of PSI tree, we should change it
val newExpression = expression.changeQualifiedName(fqName.quoteIfNeeded()) val newExpression = expression.changeQualifiedName(fqName.quoteIfNeeded(), targetElement)
val newQualifiedElement = newExpression.getQualifiedElementOrCallableRef() val newQualifiedElement = newExpression.getQualifiedElementOrCallableRef()
if (shorteningMode == ShorteningMode.NO_SHORTENING) return newExpression if (shorteningMode == ShorteningMode.NO_SHORTENING) return newExpression
@@ -184,7 +185,7 @@ class KtSimpleNameReference(expression: KtSimpleNameExpression) : KtSimpleRefere
* Result is either the same as original element, or [[KtQualifiedExpression]], or [[KtUserType]] * Result is either the same as original element, or [[KtQualifiedExpression]], or [[KtUserType]]
* Note that FqName may not be empty * Note that FqName may not be empty
*/ */
private fun KtNameReferenceExpression.changeQualifiedName(fqName: FqName): KtNameReferenceExpression { private fun KtNameReferenceExpression.changeQualifiedName(fqName: FqName, targetElement: PsiElement? = null): KtNameReferenceExpression {
assert(!fqName.isRoot) { "Can't set empty FqName for element $this" } assert(!fqName.isRoot) { "Can't set empty FqName for element $this" }
val shortName = fqName.shortName().asString() val shortName = fqName.shortName().asString()
@@ -195,7 +196,7 @@ class KtSimpleNameReference(expression: KtSimpleNameExpression) : KtSimpleRefere
val qualifier = parent.qualifier val qualifier = parent.qualifier
val qualifierReference = qualifier?.referenceExpression as? KtNameReferenceExpression val qualifierReference = qualifier?.referenceExpression as? KtNameReferenceExpression
if (qualifierReference != null && qualifier.typeArguments.isNotEmpty()) { if (qualifierReference != null && qualifier.typeArguments.isNotEmpty()) {
qualifierReference.changeQualifiedName(fqName.parent()) qualifierReference.changeQualifiedName(fqName.parent(), targetElement)
return this return this
} }
} }
@@ -210,7 +211,15 @@ class KtSimpleNameReference(expression: KtSimpleNameExpression) : KtSimpleRefere
parentDelimiter = "" parentDelimiter = ""
val callableRefCopy = parent.copied() val callableRefCopy = parent.copied()
callableRefCopy.receiverExpression?.delete() callableRefCopy.receiverExpression?.delete()
callableRefCopy.callableReference.replace(psiFactory.createSimpleName(shortName)).parent!!.text val newCallableRef = callableRefCopy
.callableReference
.replace(psiFactory.createSimpleName(shortName))
.parent as KtCallableReferenceExpression
if (targetElement != null && targetElement.isTopLevelKtOrJavaMember()) {
addDelayedImportRequest(targetElement, parent.containingKtFile)
return parent.replaced(newCallableRef).callableReference as KtNameReferenceExpression
}
newCallableRef.text
} }
else -> shortName else -> shortName
} }
@@ -281,7 +281,7 @@ class CallableReferenceMoveRenameUsageInfo(
val element = element ?: return null val element = element ?: return null
val reference = reference ?: return null val reference = reference ?: return null
val referencedElement = referencedElement ?: return null val referencedElement = referencedElement ?: return null
if (target is KtDeclaration && target.parent is KtFile) { if (target != null && target.isTopLevelKtOrJavaMember()) {
element.getStrictParentOfType<KtCallableReferenceExpression>()?.receiverExpression?.delete() element.getStrictParentOfType<KtCallableReferenceExpression>()?.receiverExpression?.delete()
return UnqualifiableMoveRenameUsageInfo(element, reference, referencedElement, element.containingFile!!, addImportToOriginalFile, isInternal) return UnqualifiableMoveRenameUsageInfo(element, reference, referencedElement, element.containingFile!!, addImportToOriginalFile, isInternal)
} }
@@ -0,0 +1,3 @@
package foo
val v = MemberNewEndJava::NestedJava
@@ -0,0 +1,4 @@
package foo;
public class MemberEndJava {
}
@@ -0,0 +1,5 @@
package foo;
public class MemberNewEndJava {
public static class NestedJava {}
}
@@ -0,0 +1,3 @@
package foo
val v = MemberEndJava::NestedJava
@@ -0,0 +1,5 @@
package foo;
public class MemberEndJava {
public static class <caret>NestedJava {}
}
@@ -0,0 +1,4 @@
package foo;
public class MemberNewEndJava {
}
@@ -0,0 +1,5 @@
{
"mainFile": "foo/MemberEndJava.java",
"type": "MOVE_MEMBERS",
"targetClass": "foo.MemberNewEndJava"
}
@@ -0,0 +1,5 @@
package bar;
public class MemberNewEndJava {
public static class NestedJava {}
}
@@ -0,0 +1,5 @@
package foo
import bar.MemberNewEndJava
val v = MemberNewEndJava::NestedJava
@@ -0,0 +1,4 @@
package foo;
public class MemberEndJava {
}
@@ -0,0 +1,4 @@
package bar;
public class MemberNewEndJava {
}
@@ -0,0 +1,3 @@
package foo
val v = MemberEndJava::NestedJava
@@ -0,0 +1,5 @@
package foo;
public class MemberEndJava {
public static class <caret>NestedJava {}
}
@@ -0,0 +1,5 @@
{
"mainFile": "foo/MemberEndJava.java",
"type": "MOVE_MEMBERS",
"targetClass": "bar.MemberNewEndJava"
}
@@ -0,0 +1,3 @@
package foo
val v = ::NestedJava
@@ -0,0 +1,4 @@
package foo;
public class MemberEndJava {
}
@@ -0,0 +1,3 @@
package foo;
public class NestedJava {}
@@ -0,0 +1,3 @@
package foo
val v = MemberEndJava::NestedJava
@@ -0,0 +1,5 @@
package foo;
public class MemberEndJava {
public static class <caret>NestedJava {}
}
@@ -0,0 +1,5 @@
{
"mainFile": "foo/MemberEndJava.java",
"type": "MOVE_INNER_CLASS",
"targetPackage": "foo"
}
@@ -0,0 +1,3 @@
package bar;
public class NestedJava {}
@@ -0,0 +1,5 @@
package foo
import bar.NestedJava
val v = ::NestedJava
@@ -0,0 +1,4 @@
package foo;
public class MemberEndJava {
}
@@ -0,0 +1,3 @@
package foo
val v = MemberEndJava::NestedJava
@@ -0,0 +1,5 @@
package foo;
public class MemberEndJava {
public static class <caret>NestedJava {}
}
@@ -0,0 +1,5 @@
{
"mainFile": "foo/MemberEndJava.java",
"type": "MOVE_INNER_CLASS",
"targetPackage": "bar"
}
@@ -0,0 +1,3 @@
package foo
val v2 = MemberNewEndJava::staticMethod
@@ -0,0 +1,4 @@
package foo;
public class MemberEndJava {
}
@@ -0,0 +1,5 @@
package foo;
public class MemberNewEndJava {
public static void staticMethod() {}
}
@@ -0,0 +1,3 @@
package foo
val v2 = MemberEndJava::staticMethod
@@ -0,0 +1,5 @@
package foo;
public class MemberEndJava {
public static void <caret>staticMethod() {}
}
@@ -0,0 +1,4 @@
package foo;
public class MemberNewEndJava {
}
@@ -0,0 +1,5 @@
{
"mainFile": "foo/MemberEndJava.java",
"type": "MOVE_MEMBERS",
"targetClass": "foo.MemberNewEndJava"
}
@@ -0,0 +1,5 @@
package bar;
public class MemberNewEndJava {
public static void staticMethod() {}
}
@@ -0,0 +1,5 @@
package foo
import bar.MemberNewEndJava
val v2 = MemberNewEndJava::staticMethod
@@ -0,0 +1,4 @@
package foo;
public class MemberEndJava {
}
@@ -0,0 +1,4 @@
package bar;
public class MemberNewEndJava {
}
@@ -0,0 +1,3 @@
package foo
val v2 = MemberEndJava::staticMethod
@@ -0,0 +1,5 @@
package foo;
public class MemberEndJava {
public static void <caret>staticMethod() {}
}
@@ -0,0 +1,5 @@
{
"mainFile": "foo/MemberEndJava.java",
"type": "MOVE_MEMBERS",
"targetClass": "bar.MemberNewEndJava"
}
@@ -25,6 +25,30 @@ public class MoveTestGenerated extends AbstractMoveTest {
KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/refactoring/move"), Pattern.compile("^(.+)\\.test$"), TargetBackend.ANY); KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/refactoring/move"), Pattern.compile("^(.+)\\.test$"), TargetBackend.ANY);
} }
@TestMetadata("java/moveClass/callableReferences/nestedToAnotherClass/nestedToAnotherClass.test")
public void testJava_moveClass_callableReferences_nestedToAnotherClass_NestedToAnotherClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/java/moveClass/callableReferences/nestedToAnotherClass/nestedToAnotherClass.test");
doTest(fileName);
}
@TestMetadata("java/moveClass/callableReferences/nestedToAnotherClassAndAnotherPackage/nestedToAnotherClassAndAnotherPackage.test")
public void testJava_moveClass_callableReferences_nestedToAnotherClassAndAnotherPackage_NestedToAnotherClassAndAnotherPackage() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/java/moveClass/callableReferences/nestedToAnotherClassAndAnotherPackage/nestedToAnotherClassAndAnotherPackage.test");
doTest(fileName);
}
@TestMetadata("java/moveClass/callableReferences/nestedToTopLevel/nestedToTopLevel.test")
public void testJava_moveClass_callableReferences_nestedToTopLevel_NestedToTopLevel() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/java/moveClass/callableReferences/nestedToTopLevel/nestedToTopLevel.test");
doTest(fileName);
}
@TestMetadata("java/moveClass/callableReferences/nestedToTopLevelAndAnotherPackage/nestedToTopLevelAndAnotherPackage.test")
public void testJava_moveClass_callableReferences_nestedToTopLevelAndAnotherPackage_NestedToTopLevelAndAnotherPackage() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/java/moveClass/callableReferences/nestedToTopLevelAndAnotherPackage/nestedToTopLevelAndAnotherPackage.test");
doTest(fileName);
}
@TestMetadata("java/moveClass/moveAsMember/moveClassToExternalNestedClass/moveClassToExternalNestedClass.test") @TestMetadata("java/moveClass/moveAsMember/moveClassToExternalNestedClass/moveClassToExternalNestedClass.test")
public void testJava_moveClass_moveAsMember_moveClassToExternalNestedClass_MoveClassToExternalNestedClass() throws Exception { public void testJava_moveClass_moveAsMember_moveClassToExternalNestedClass_MoveClassToExternalNestedClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/java/moveClass/moveAsMember/moveClassToExternalNestedClass/moveClassToExternalNestedClass.test"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/java/moveClass/moveAsMember/moveClassToExternalNestedClass/moveClassToExternalNestedClass.test");
@@ -157,6 +181,18 @@ public class MoveTestGenerated extends AbstractMoveTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("java/moveMethod/callableReferences/staticMethodToTopLevelClass/staticMethodToTopLevelClass.test")
public void testJava_moveMethod_callableReferences_staticMethodToTopLevelClass_StaticMethodToTopLevelClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/java/moveMethod/callableReferences/staticMethodToTopLevelClass/staticMethodToTopLevelClass.test");
doTest(fileName);
}
@TestMetadata("java/moveMethod/callableReferences/staticMethodToTopLevelClassInAnotherPackage/staticMethodToTopLevelClassInAnotherPackage.test")
public void testJava_moveMethod_callableReferences_staticMethodToTopLevelClassInAnotherPackage_StaticMethodToTopLevelClassInAnotherPackage() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/java/moveMethod/callableReferences/staticMethodToTopLevelClassInAnotherPackage/staticMethodToTopLevelClassInAnotherPackage.test");
doTest(fileName);
}
@TestMetadata("java/moveMethod/moveMethodToExternalNestedClass/moveMethodToExternalNestedClass.test") @TestMetadata("java/moveMethod/moveMethodToExternalNestedClass/moveMethodToExternalNestedClass.test")
public void testJava_moveMethod_moveMethodToExternalNestedClass_MoveMethodToExternalNestedClass() throws Exception { public void testJava_moveMethod_moveMethodToExternalNestedClass_MoveMethodToExternalNestedClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/java/moveMethod/moveMethodToExternalNestedClass/moveMethodToExternalNestedClass.test"); String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/java/moveMethod/moveMethodToExternalNestedClass/moveMethodToExternalNestedClass.test");