Move: Support quoted package names

#KT-12411 Fixed
(cherry picked from commit 95239e5)
This commit is contained in:
Alexey Sedunov
2016-06-21 20:57:11 +03:00
parent 89816c5f12
commit 542f8d9fd2
48 changed files with 290 additions and 28 deletions
+1
View File
@@ -251,6 +251,7 @@
- [`KT-10033`](https://youtrack.jetbrains.com/issue/KT-10033) Qualify references to members of enum companions in case of conflict with enum entries
- [`KT-10713`](https://youtrack.jetbrains.com/issue/KT-10713) Skip read-only declarations when renaming parameters
- [`KT-10687`](https://youtrack.jetbrains.com/issue/KT-10687) Qualify property references to avoid shadowing by parameters
- [`KT-12411`](https://youtrack.jetbrains.com/issue/KT-12411) Fix package name quotation in Move refactoring
- [`KT-12543`](https://youtrack.jetbrains.com/issue/KT-12543) Qualify property references with 'this' to avoid renaming conflicts
- [`KT-12732`](https://youtrack.jetbrains.com/issue/KT-12732) Copy default parameter values to overriding function which is renamed by Java reference while its base function is unchanged
- [`KT-12747`](https://youtrack.jetbrains.com/issue/KT-12747) Fix exception on file copy
@@ -24,10 +24,10 @@ import com.intellij.util.IncorrectOperationException
import com.intellij.util.SmartList
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
import org.jetbrains.kotlin.idea.intentions.OperatorToFunctionIntention
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.lexer.KtToken
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor
@@ -154,7 +154,7 @@ class KtSimpleNameReference(expression: KtSimpleNameExpression) : KtSimpleRefere
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
val newExpression = expression.changeQualifiedName(fqName)
val newExpression = expression.changeQualifiedName(fqName.quoteIfNeeded())
val newQualifiedElement = newExpression.getQualifiedElement()
if (shorteningMode == ShorteningMode.NO_SHORTENING) return newExpression
@@ -181,14 +181,14 @@ class KtSimpleNameReference(expression: KtSimpleNameExpression) : KtSimpleRefere
private fun KtNameReferenceExpression.changeQualifiedName(fqName: FqName): KtNameReferenceExpression {
assert(!fqName.isRoot) { "Can't set empty FqName for element $this" }
val shortName = fqName.shortName().render()
val shortName = fqName.shortName().asString()
val psiFactory = KtPsiFactory(this)
val fqNameBase = (parent as? KtCallExpression)?.let { parent ->
val callCopy = parent.copy() as KtCallExpression
callCopy.calleeExpression!!.replace(psiFactory.createSimpleName(shortName)).parent!!.text
} ?: shortName
val text = if (!fqName.isOneSegmentFQN()) "${fqName.parent().render()}.$fqNameBase" else fqNameBase
val text = if (!fqName.isOneSegmentFQN()) "${fqName.parent().asString()}.$fqNameBase" else fqNameBase
val elementToReplace = getQualifiedElement()
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
import org.jetbrains.kotlin.idea.resolve.frontendService
import org.jetbrains.kotlin.idea.util.getImplicitReceiversWithInstanceToExpression
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedElementSelector
@@ -163,6 +164,12 @@ fun KtCallableDeclaration.canOmitDeclaredType(initializerOrBodyExpression: KtExp
fun String.quoteIfNeeded(): String = if (KotlinNameSuggester.isIdentifier(this)) this else "`$this`"
fun FqName.quoteSegmentsIfNeeded(): String {
return pathSegments().map { it.asString().quoteIfNeeded() }.joinToString(".")
}
fun FqName.quoteIfNeeded() = FqName(quoteSegmentsIfNeeded())
fun isEnumCompanionPropertyWithEntryConflict(element: PsiElement, expectedName: String): Boolean {
if (element !is KtProperty) return false
@@ -60,7 +60,7 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Paramete
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClasses
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionResult
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
import org.jetbrains.kotlin.idea.refactoring.quoteSegmentsIfNeeded
import org.jetbrains.kotlin.idea.core.quoteSegmentsIfNeeded
import org.jetbrains.kotlin.idea.util.DebuggerUtils
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.attachment.attachmentByPsiFile
@@ -687,11 +687,9 @@ fun invokeOnceOnCommandFinish(action: () -> Unit) {
commandProcessor.addCommandListener(listener)
}
fun FqName.quoteSegmentsIfNeeded(): String {
return pathSegments().map { it.asString().quoteIfNeeded() }.joinToString(".")
}
fun FqNameUnsafe.hasIdentifiersOnly(): Boolean = pathSegments().all { KotlinNameSuggester.isIdentifier(it.asString().quoteIfNeeded()) }
fun FqNameUnsafe.hasIdentifiersOnly(): Boolean = pathSegments().all { KotlinNameSuggester.isIdentifier(it.asString()) }
fun FqName.hasIdentifiersOnly(): Boolean = pathSegments().all { KotlinNameSuggester.isIdentifier(it.asString().quoteIfNeeded()) }
fun PsiNamedElement.isInterfaceClass(): Boolean = this is KtClass && isInterface() || this is PsiClass && isInterface
@@ -26,6 +26,7 @@ import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.intentions.SelfTargetingOffsetIndependentIntention
import org.jetbrains.kotlin.idea.refactoring.hasIdentifiersOnly
import org.jetbrains.kotlin.idea.core.quoteSegmentsIfNeeded
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.FqNameUnsafe
@@ -84,7 +85,7 @@ class ChangePackageIntention: SelfTargetingOffsetIndependentIntention<KtPackageD
val document = editor.document
project.executeWriteCommand(text) {
document.replaceString(affectedRange!!.startOffset, affectedRange!!.endOffset, currentName)
document.replaceString(affectedRange!!.startOffset, affectedRange!!.endOffset, FqName(currentName).quoteSegmentsIfNeeded())
}
PsiDocumentManager.getInstance(project).commitDocument(document)
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document)
@@ -32,7 +32,7 @@ class ChangePackageToMatchDirectoryIntention : SelfTargetingOffsetIndependentInt
if (file.isInjectedFragment || file.packageMatchesDirectory()) return false
val fqNameByDirectory = file.getFqNameByDirectory()
if (!fqNameByDirectory.toUnsafe().hasIdentifiersOnly()) {
if (!fqNameByDirectory.hasIdentifiersOnly()) {
if (isIntentionBaseInspectionEnabled(file.project, element)) {
text = "File package doesn't match directory"
return true
@@ -47,7 +47,7 @@ class ChangePackageToMatchDirectoryIntention : SelfTargetingOffsetIndependentInt
override fun applyTo(element: KtPackageDirective, editor: Editor?) {
val file = element.getContainingKtFile()
val newFqName = file.getFqNameByDirectory()
if (!newFqName.toUnsafe().hasIdentifiersOnly()) return
if (!newFqName.hasIdentifiersOnly()) return
KotlinChangePackageRefactoring(file).run(newFqName)
}
}
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.idea.refactoring.move.ContainerInfo
import org.jetbrains.kotlin.idea.refactoring.move.getInternalReferencesToUpdateOnPackageNameChange
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*
import org.jetbrains.kotlin.idea.refactoring.move.postProcessMoveUsages
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
@@ -63,7 +64,7 @@ class KotlinChangePackageRefactoring(val file: KtFile) {
val internalUsages = file.getInternalReferencesToUpdateOnPackageNameChange(changeInfo)
project.executeWriteCommand("Change file's package to '${newFqName.asString()}'") {
packageDirective.fqName = newFqName
packageDirective.fqName = newFqName.quoteIfNeeded()
postProcessMoveUsages(internalUsages)
project.runWithElementsToShortenIsEmptyIgnored { declarationProcessor.execute(declarationUsages) }
}
@@ -28,6 +28,7 @@ import com.intellij.util.Function
import org.jetbrains.kotlin.idea.core.getPackage
import org.jetbrains.kotlin.idea.refactoring.invokeOnceOnCommandFinish
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveKotlinDeclarationsProcessor
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtFile
import java.util.ArrayList
@@ -89,7 +90,7 @@ class KotlinMoveDirectoryWithClassesHelper : MoveDirectoryWithClassesHelper() {
moveContextMap[file] = MoveContext(moveDestination,
fileHandler.findInternalUsages(file, moveDestination),
moveDeclarationsProcessor)
moveDestination.getPackage()?.let { newPackage -> file.packageDirective?.fqName = FqName(newPackage.qualifiedName) }
moveDestination.getPackage()?.let { newPackage -> file.packageDirective?.fqName = FqName(newPackage.qualifiedName).quoteIfNeeded() }
return true
}
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.idea.core.packageMatchesDirectory
import org.jetbrains.kotlin.idea.refactoring.hasIdentifiersOnly
import org.jetbrains.kotlin.idea.refactoring.move.*
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
@@ -128,7 +129,7 @@ class MoveKotlinFileHandler : MoveFileHandler() {
if (file !is KtFile) return
val newDirectory = file.parent ?: return
val packageNameInfo = file.getPackageNameInfo(newDirectory, true) ?: return
file.packageDirective?.fqName = packageNameInfo.newContainer.fqName!!
file.packageDirective?.fqName = packageNameInfo.newContainer.fqName!!.quoteIfNeeded()
}
override fun retargetUsages(usageInfos: List<UsageInfo>?, oldToNewMap: Map<PsiElement, PsiElement>) {
@@ -0,0 +1,5 @@
{
"mainFile": "in/foo/fun/test.kt",
"intentionClass": "org.jetbrains.kotlin.idea.refactoring.move.changePackage.ChangePackageToMatchDirectoryIntention",
"intentionText": "Change file's package to 'in.foo.fun'"
}
@@ -0,0 +1,7 @@
package `in`.foo.`fun`
class Foo
fun foo() {
}
@@ -0,0 +1,8 @@
package in.foo.fun;
class Test {
static void test() {
new Foo();
TestKt.foo();
}
}
@@ -0,0 +1,6 @@
package `in`.foo.`fun`
fun test() {
Foo()
foo()
}
@@ -0,0 +1,10 @@
package in.foo.fun;
import static in.foo.fun.TestKt.foo;
class Test {
static void test() {
new Foo();
foo();
}
}
@@ -0,0 +1,9 @@
package `in`.foo.`fun`
import `in`.foo.`fun`.Foo
import `in`.foo.`fun`.foo
fun test() {
Foo()
foo()
}
@@ -0,0 +1,11 @@
package usages;
import in.foo.fun.Foo;
import in.foo.fun.TestKt;
class Test {
static void test() {
new Foo();
TestKt.foo();
}
}
@@ -0,0 +1,9 @@
package usages
import `in`.foo.`fun`.Foo
import `in`.foo.`fun`.foo
fun test() {
Foo()
foo()
}
@@ -0,0 +1,11 @@
package usages;
import in.foo.fun.Foo;
import static in.foo.fun.TestKt.foo;
class Test {
static void test() {
new Foo();
foo();
}
}
@@ -0,0 +1,9 @@
package usages
import `in`.foo.`fun`.Foo
import `in`.foo.`fun`.foo
fun test() {
Foo()
foo()
}
@@ -0,0 +1,7 @@
<caret>package bar
class Foo
fun foo() {
}
@@ -0,0 +1,8 @@
package in.foo.fun;
class Test {
static void test() {
new bar.Foo();
bar.TestKt.foo();
}
}
@@ -0,0 +1,6 @@
package `in`.foo.`fun`
fun test() {
bar.Foo()
bar.foo()
}
@@ -0,0 +1,11 @@
package in.foo.fun;
import bar.Foo;
import static bar.TestKt.foo;
class Test {
static void test() {
new Foo();
foo();
}
}
@@ -0,0 +1,9 @@
package `in`.foo.`fun`
import bar.Foo
import bar.foo
fun test() {
Foo()
foo()
}
@@ -0,0 +1,8 @@
package usages;
class Test {
static void test() {
new bar.Foo();
bar.TestKt.foo();
}
}
@@ -0,0 +1,6 @@
package usages
fun test() {
bar.Foo()
bar.foo()
}
@@ -0,0 +1,11 @@
package usages;
import bar.Foo;
import static bar.TestKt.foo;
class Test {
static void test() {
new Foo();
foo();
}
}
@@ -0,0 +1,9 @@
package usages
import bar.Foo
import bar.foo
fun test() {
Foo()
foo()
}
@@ -0,0 +1,9 @@
package `in`.p1
import `in`.p2.p.J
import `in`.p2.p.K
public class M {
val j = J()
val k = K()
}
@@ -0,0 +1,9 @@
package in.p2;
import in.p2.p.K;
import in.p1.M;
public class JJ {
K k = new K();
M m = new M();
}
@@ -0,0 +1,9 @@
package `in`.p2
import `in`.p1.M
import `in`.p2.p.K
public class L {
private val k = K()
private val m = M()
}
@@ -0,0 +1,5 @@
package in.p2.p;
public class J {
K k = new K();
}
@@ -0,0 +1,5 @@
package `in`.p2.p
public class K {
val j = J()
}
@@ -0,0 +1,9 @@
package `in`.p1
import `in`.p1.p.J
import `in`.p1.p.K
public class M {
val j = J()
val k = K()
}
@@ -0,0 +1,5 @@
package in.p1.p;
public class J {
K k = new K();
}
@@ -0,0 +1,5 @@
package `in`.p1.p
public class K {
val j = J()
}
@@ -0,0 +1,9 @@
package in.p2;
import in.p1.p.K;
import in.p1.M;
public class JJ {
K k = new K();
M m = new M();
}
@@ -0,0 +1,9 @@
package `in`.p2
import `in`.p1.M
import `in`.p1.p.K
public class L {
private val k = K()
private val m = M()
}
@@ -0,0 +1,6 @@
{
"mainFile": "in/p1/M.kt",
"type": "MOVE_DIRECTORY_WITH_CLASSES",
"sourceDir": "in/p1/p",
"targetDir": "in/p2"
}
@@ -1,5 +0,0 @@
package p
fun foo() {
}
@@ -0,0 +1,5 @@
package p.`123`
fun foo() {
}
@@ -143,6 +143,12 @@ public class MultiFileIntentionTestGenerated extends AbstractMultiFileIntentionT
doTest(fileName);
}
@TestMetadata("reconcilePackageWithDirectory/addQuotation/addQuotation.test")
public void testReconcilePackageWithDirectory_addQuotation_AddQuotation() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/reconcilePackageWithDirectory/addQuotation/addQuotation.test");
doTest(fileName);
}
@TestMetadata("reconcilePackageWithDirectory/changeToDefaultPackage/changeToDefaultPackage.test")
public void testReconcilePackageWithDirectory_changeToDefaultPackage_ChangeToDefaultPackage() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/reconcilePackageWithDirectory/changeToDefaultPackage/changeToDefaultPackage.test");
@@ -233,6 +233,12 @@ public class MoveTestGenerated extends AbstractMoveTest {
doTest(fileName);
}
@TestMetadata("kotlin/moveDirectoryWithQuotation/moveDirectoryWithQuotation.test")
public void testKotlin_moveDirectoryWithQuotation_MoveDirectoryWithQuotation() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveDirectoryWithQuotation/moveDirectoryWithQuotation.test");
doTest(fileName);
}
@TestMetadata("kotlin/moveFile/addExtensionImport/addExtensionImport.test")
public void testKotlin_moveFile_addExtensionImport_AddExtensionImport() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveFile/addExtensionImport/addExtensionImport.test");
@@ -245,12 +251,6 @@ public class MoveTestGenerated extends AbstractMoveTest {
doTest(fileName);
}
@TestMetadata("kotlin/moveFile/invalidTargetPackage/moveFileToInvalidPackage.test")
public void testKotlin_moveFile_invalidTargetPackage_MoveFileToInvalidPackage() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveFile/invalidTargetPackage/moveFileToInvalidPackage.test");
doTest(fileName);
}
@TestMetadata("kotlin/moveFile/moveFileToFile/moveFileToFile.test")
public void testKotlin_moveFile_moveFileToFile_MoveFileToFile() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveFile/moveFileToFile/moveFileToFile.test");
@@ -275,6 +275,12 @@ public class MoveTestGenerated extends AbstractMoveTest {
doTest(fileName);
}
@TestMetadata("kotlin/moveFile/packageWithQuotation/moveFileToPackageWithQuotation.test")
public void testKotlin_moveFile_packageWithQuotation_MoveFileToPackageWithQuotation() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveFile/packageWithQuotation/moveFileToPackageWithQuotation.test");
doTest(fileName);
}
@TestMetadata("kotlin/moveNestedClass/deepInnerToTopLevelWithOuterOuterThis/deepInnerToTopLevelWithOuterOuterThis.test")
public void testKotlin_moveNestedClass_deepInnerToTopLevelWithOuterOuterThis_DeepInnerToTopLevelWithOuterOuterThis() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/move/kotlin/moveNestedClass/deepInnerToTopLevelWithOuterOuterThis/deepInnerToTopLevelWithOuterOuterThis.test");