Move: Use Kotlin declaration mover to handle KtFile
IDEA-provided file mover does not process conflicts #KT-13911 Fixed
This commit is contained in:
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.test
|
||||
|
||||
import com.intellij.concurrency.IdeaForkJoinWorkerThreadFactory
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.module.Module
|
||||
@@ -30,7 +29,6 @@ import com.intellij.psi.impl.PsiManagerEx
|
||||
import com.intellij.psi.impl.file.impl.FileManagerImpl
|
||||
import com.intellij.psi.impl.source.PsiFileImpl
|
||||
import com.intellij.testFramework.LightPlatformTestCase
|
||||
import com.intellij.testFramework.ThreadTracker
|
||||
import com.intellij.util.Consumer
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
|
||||
@@ -42,8 +40,6 @@ import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.util.*
|
||||
import java.util.concurrent.ForkJoinPool
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
enum class ModuleKind {
|
||||
KOTLIN_JVM_WITH_STDLIB_SOURCES,
|
||||
@@ -129,20 +125,28 @@ fun invalidateLibraryCache(project: Project) {
|
||||
}
|
||||
|
||||
fun Document.extractMarkerOffset(project: Project, caretMarker: String = "<caret>"): Int {
|
||||
val offset = runWriteAction {
|
||||
return extractMultipleMarkerOffsets(project, caretMarker).singleOrNull() ?: -1
|
||||
}
|
||||
|
||||
fun Document.extractMultipleMarkerOffsets(project: Project, caretMarker: String = "<caret>"): List<Int> {
|
||||
val offsets = ArrayList<Int>()
|
||||
|
||||
runWriteAction {
|
||||
val text = StringBuilder(text)
|
||||
val offset = text.indexOf(caretMarker)
|
||||
while (true) {
|
||||
val offset = text.indexOf(caretMarker)
|
||||
if (offset >= 0) {
|
||||
text.delete(offset, offset + caretMarker.length)
|
||||
setText(text.toString())
|
||||
|
||||
if (offset >= 0) {
|
||||
text.delete(offset, offset + caretMarker.length)
|
||||
setText(text.toString())
|
||||
offsets += offset
|
||||
}
|
||||
else break
|
||||
}
|
||||
|
||||
offset
|
||||
}
|
||||
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(this)
|
||||
|
||||
return offset
|
||||
return offsets
|
||||
}
|
||||
+19
-5
@@ -46,7 +46,11 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
|
||||
private fun getUniqueContainer(elements: Array<out PsiElement>): PsiElement? {
|
||||
val getContainer: (PsiElement) -> PsiElement? =
|
||||
if (elements.any { it.parent !is KtFile }) { e ->
|
||||
(e as? KtNamedDeclaration)?.containingClassOrObject
|
||||
when (e) {
|
||||
is KtNamedDeclaration -> e.containingClassOrObject ?: e.parent
|
||||
is KtFile -> e.parent
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else { e ->
|
||||
e.containingFile?.parent
|
||||
@@ -69,7 +73,13 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
|
||||
return false
|
||||
}
|
||||
|
||||
val elementsToSearch = elements.mapTo(LinkedHashSet()) { it as KtNamedDeclaration }
|
||||
val elementsToSearch = elements.flatMapTo(LinkedHashSet<KtNamedDeclaration>()) {
|
||||
when (it) {
|
||||
is KtNamedDeclaration -> listOf(it)
|
||||
is KtFile -> it.declarations.filterIsInstance<KtNamedDeclaration>()
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
// todo: allow moving companion object
|
||||
if (elementsToSearch.any { it is KtObjectDeclaration && it.isCompanion() }) {
|
||||
@@ -142,10 +152,14 @@ class MoveKotlinDeclarationsHandler : MoveHandlerDelegate() {
|
||||
}
|
||||
|
||||
return elements.all { e ->
|
||||
if (e is KtClass || (e is KtObjectDeclaration && !e.isObjectLiteral()) || e is KtNamedFunction || e is KtProperty) {
|
||||
(editorMode || (e as KtNamedDeclaration).canMove()) && e.canRefactor()
|
||||
when {
|
||||
e is KtClass || e is KtObjectDeclaration && !e.isObjectLiteral() || e is KtNamedFunction || e is KtProperty ->
|
||||
(editorMode || (e as KtNamedDeclaration).canMove()) && e.canRefactor()
|
||||
e is KtFile ->
|
||||
e.declarations.any { it is KtNamedDeclaration } && e.canRefactor()
|
||||
else ->
|
||||
false
|
||||
}
|
||||
else false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package pack
|
||||
|
||||
fun test() {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class <caret>A
|
||||
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
class <caret>A
|
||||
|
||||
fun <caret>foo() {
|
||||
|
||||
}
|
||||
|
||||
val <caret>bar = 1
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
class A2
|
||||
|
||||
fun foo2() {
|
||||
|
||||
}
|
||||
|
||||
val bar2 = 1
|
||||
@@ -0,0 +1,5 @@
|
||||
class A {
|
||||
inner class <caret>B {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo() {
|
||||
class <caret>A
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
fun foo() {
|
||||
fun <caret>bar() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fun foo() {
|
||||
val <caret>bar = 1
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class A {
|
||||
fun <caret>foo() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class A {
|
||||
val <caret>foo = 1
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
class A {
|
||||
class <caret>B
|
||||
|
||||
class <caret>C
|
||||
|
||||
class <caret>D
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
class <caret>A
|
||||
|
||||
fun <caret>foo() {
|
||||
|
||||
}
|
||||
|
||||
val <caret>bar = 1
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
class <caret>A
|
||||
|
||||
fun <caret>foo() {
|
||||
|
||||
}
|
||||
|
||||
val <caret>bar = 1
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
class <caret>A2
|
||||
|
||||
fun <caret>foo2() {
|
||||
|
||||
}
|
||||
|
||||
val <caret>bar2 = 1
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
class <caret>A
|
||||
|
||||
fun <caret>foo() {
|
||||
|
||||
}
|
||||
|
||||
val <caret>bar = 1
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
class <caret>A2
|
||||
|
||||
fun <caret>foo2() {
|
||||
|
||||
}
|
||||
|
||||
val <caret>bar2 = 1
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
class <caret>A {
|
||||
inner class <caret>B {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class A {
|
||||
class <caret>B {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
class X {
|
||||
class <caret>Y
|
||||
}
|
||||
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
class B {
|
||||
class C
|
||||
}
|
||||
|
||||
annotation class Ann
|
||||
@@ -0,0 +1,7 @@
|
||||
open class A
|
||||
|
||||
fun foo() {
|
||||
val o = <caret>object : A() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class <caret>A
|
||||
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
class <caret>X
|
||||
|
||||
fun foo() {
|
||||
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
class B {
|
||||
class C
|
||||
}
|
||||
|
||||
annotation class Ann
|
||||
@@ -0,0 +1,5 @@
|
||||
class A
|
||||
|
||||
fun <caret>foo() {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class A
|
||||
|
||||
val <caret>foo = 1
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.move
|
||||
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.testFramework.PlatformTestCase
|
||||
import com.intellij.testFramework.PsiTestUtil
|
||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveKotlinDeclarationsHandler
|
||||
import org.jetbrains.kotlin.idea.refactoring.toPsiDirectory
|
||||
import org.jetbrains.kotlin.idea.refactoring.toPsiFile
|
||||
import org.jetbrains.kotlin.idea.test.KotlinMultiFileTestCase
|
||||
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
||||
import org.jetbrains.kotlin.idea.test.extractMultipleMarkerOffsets
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
||||
|
||||
class MoveKotlinDeclarationsHandlerTest : KotlinMultiFileTestCase() {
|
||||
override fun getTestDataPath() = PluginTestCaseBase.getTestDataPathBase()
|
||||
|
||||
override fun getTestRoot() = "/refactoring/moveHandler/declarations"
|
||||
|
||||
private fun doTest(action: (rootDir: VirtualFile, handler: MoveKotlinDeclarationsHandler) -> Unit) {
|
||||
val path = "$testDataPath$testRoot/${getTestName(true)}"
|
||||
val rootDir = PsiTestUtil.createTestProjectStructure(myProject, myModule, path, PlatformTestCase.myFilesToDelete, false)
|
||||
prepareProject(rootDir)
|
||||
PsiDocumentManager.getInstance(myProject).commitAllDocuments()
|
||||
action(rootDir, MoveKotlinDeclarationsHandler())
|
||||
}
|
||||
|
||||
private fun getPsiDirectory(rootDir: VirtualFile, path: String) = rootDir.findFileByRelativePath(path)!!.toPsiDirectory(project)!!
|
||||
|
||||
private fun getPsiFile(rootDir: VirtualFile, path: String) = rootDir.findFileByRelativePath(path)!!.toPsiFile(project)!!
|
||||
|
||||
private fun getElementAtCaret(rootDir: VirtualFile, path: String) = getElementsAtCarets(rootDir, path).single()
|
||||
|
||||
private fun getElementsAtCarets(rootDir: VirtualFile, path: String): List<PsiElement> {
|
||||
val file = getPsiFile(rootDir, path)
|
||||
val document = FileDocumentManager.getInstance().getDocument(file.virtualFile)!!
|
||||
return document.extractMultipleMarkerOffsets(project).map { file.findElementAt(it)!! }
|
||||
}
|
||||
|
||||
fun testObjectLiteral() = doTest { rootDir, handler ->
|
||||
val objectDeclaration = getElementAtCaret(rootDir, "test.kt").getNonStrictParentOfType<KtObjectDeclaration>()!!
|
||||
assert(!handler.canMove(arrayOf<PsiElement>(objectDeclaration), null))
|
||||
}
|
||||
|
||||
fun testLocalClass() = doTest { rootDir, handler ->
|
||||
val klass = getElementAtCaret(rootDir, "test.kt").getNonStrictParentOfType<KtClass>()!!
|
||||
assert(!handler.canMove(arrayOf<PsiElement>(klass), null))
|
||||
}
|
||||
|
||||
fun testLocalFun() = doTest { rootDir, handler ->
|
||||
val function = getElementAtCaret(rootDir, "test.kt").getNonStrictParentOfType<KtNamedFunction>()!!
|
||||
assert(!handler.canMove(arrayOf<PsiElement>(function), null))
|
||||
}
|
||||
|
||||
fun testLocalVal() = doTest { rootDir, handler ->
|
||||
val property = getElementAtCaret(rootDir, "test.kt").getNonStrictParentOfType<KtProperty>()!!
|
||||
assert(!handler.canMove(arrayOf<PsiElement>(property), null))
|
||||
}
|
||||
|
||||
fun testMemberFun() = doTest { rootDir, handler ->
|
||||
val function = getElementAtCaret(rootDir, "test.kt").getNonStrictParentOfType<KtNamedFunction>()!!
|
||||
assert(!handler.canMove(arrayOf<PsiElement>(function), null))
|
||||
}
|
||||
|
||||
fun testMemberVal() = doTest { rootDir, handler ->
|
||||
val property = getElementAtCaret(rootDir, "test.kt").getNonStrictParentOfType<KtProperty>()!!
|
||||
assert(!handler.canMove(arrayOf<PsiElement>(property), null))
|
||||
}
|
||||
|
||||
fun testNestedClass() = doTest { rootDir, handler ->
|
||||
val klass = getElementAtCaret(rootDir, "test.kt").getNonStrictParentOfType<KtClass>()!!
|
||||
assert(handler.canMove(arrayOf<PsiElement>(klass), null))
|
||||
}
|
||||
|
||||
fun testInnerClass() = doTest { rootDir, handler ->
|
||||
val klass = getElementAtCaret(rootDir, "test.kt").getNonStrictParentOfType<KtClass>()!!
|
||||
assert(handler.canMove(arrayOf<PsiElement>(klass), null))
|
||||
}
|
||||
|
||||
fun testTopLevelClass() = doTest { rootDir, handler ->
|
||||
val klass = getElementAtCaret(rootDir, "test.kt").getNonStrictParentOfType<KtClass>()!!
|
||||
assert(handler.canMove(arrayOf<PsiElement>(klass), null))
|
||||
}
|
||||
|
||||
fun testTopLevelFun() = doTest { rootDir, handler ->
|
||||
val function = getElementAtCaret(rootDir, "test.kt").getNonStrictParentOfType<KtNamedFunction>()!!
|
||||
assert(handler.canMove(arrayOf<PsiElement>(function), null))
|
||||
}
|
||||
|
||||
fun testTopLevelVal() = doTest { rootDir, handler ->
|
||||
val property = getElementAtCaret(rootDir, "test.kt").getNonStrictParentOfType<KtProperty>()!!
|
||||
assert(handler.canMove(arrayOf<PsiElement>(property), null))
|
||||
}
|
||||
|
||||
fun testMultipleNestedClasses() = doTest { rootDir, handler ->
|
||||
val classes = getElementsAtCarets(rootDir, "test.kt").map { it.getNonStrictParentOfType<KtClass>()!! }
|
||||
assert(handler.canMove(classes.toTypedArray(), null))
|
||||
}
|
||||
|
||||
fun testNestedAndTopLevelClass() = doTest { rootDir, handler ->
|
||||
val classes = getElementsAtCarets(rootDir, "test.kt").map { it.getNonStrictParentOfType<KtClass>()!! }
|
||||
assert(!handler.canMove(classes.toTypedArray(), null))
|
||||
}
|
||||
|
||||
fun testMultipleTopLevelDeclarations() = doTest { rootDir, handler ->
|
||||
val declarations = getElementsAtCarets(rootDir, "test.kt").map { it.getNonStrictParentOfType<KtNamedDeclaration>()!! }
|
||||
assert(handler.canMove(declarations.toTypedArray(), null))
|
||||
}
|
||||
|
||||
fun testMultipleTopLevelDeclarationsInDifferentFiles() = doTest { rootDir, handler ->
|
||||
val declarations = listOf("test.kt", "test2.kt")
|
||||
.flatMap { getElementsAtCarets(rootDir, it) }
|
||||
.map { it.getNonStrictParentOfType<KtNamedDeclaration>()!! }
|
||||
assert(handler.canMove(declarations.toTypedArray(), null))
|
||||
|
||||
val files = listOf("test.kt", "test2.kt").map { getPsiFile(rootDir, it) }
|
||||
assert(handler.canMove(files.toTypedArray(), null))
|
||||
}
|
||||
|
||||
fun testMultipleTopLevelDeclarationsInDifferentDirs() = doTest { rootDir, handler ->
|
||||
val declarations = listOf("test1/test.kt", "test2/test2.kt")
|
||||
.flatMap { getElementsAtCarets(rootDir, it) }
|
||||
.map { it.getNonStrictParentOfType<KtNamedDeclaration>()!! }
|
||||
assert(!handler.canMove(declarations.toTypedArray(), null))
|
||||
|
||||
val files = listOf("test1/test.kt", "test2/test2.kt").map { getPsiFile(rootDir, it) }
|
||||
assert(!handler.canMove(files.toTypedArray(), null))
|
||||
}
|
||||
|
||||
fun testFileAndTopLevelDeclarations() = doTest { rootDir, handler ->
|
||||
val elements = getElementsAtCarets(rootDir, "test.kt").map { it.getNonStrictParentOfType<KtNamedDeclaration>()!! } +
|
||||
getPsiFile(rootDir, "test2.kt")
|
||||
assert(!handler.canMove(elements.toTypedArray(), null))
|
||||
}
|
||||
|
||||
fun testCommonTargets() = doTest { rootDir, handler ->
|
||||
val elementsToMove = arrayOf<PsiElement>(getElementAtCaret(rootDir, "test.kt").getNonStrictParentOfType<KtClass>()!!)
|
||||
|
||||
val targetPackage = JavaPsiFacade.getInstance(project).findPackage("pack")!!
|
||||
assert(handler.canMove(elementsToMove, targetPackage))
|
||||
|
||||
val targetDirectory = getPsiDirectory(rootDir, "pack")
|
||||
assert(handler.canMove(elementsToMove, targetDirectory))
|
||||
|
||||
val targetFile = getPsiFile(rootDir, "pack/test2.kt")
|
||||
assert(handler.canMove(elementsToMove, targetFile))
|
||||
}
|
||||
|
||||
fun testTopLevelClassToClass() = doTest { rootDir, handler ->
|
||||
val elementsToMove = arrayOf<PsiElement>(getElementAtCaret(rootDir, "test.kt").getNonStrictParentOfType<KtClass>()!!)
|
||||
val targetFile = getPsiFile(rootDir, "test2.kt") as KtFile
|
||||
|
||||
val topLevelTarget = targetFile.declarations.firstIsInstance<KtClass>()
|
||||
assert(topLevelTarget.name == "B")
|
||||
assert(!handler.canMove(elementsToMove, topLevelTarget))
|
||||
|
||||
val annotationTarget = targetFile.declarations.first { it.name == "Ann" } as KtClass
|
||||
assert(!handler.canMove(elementsToMove, annotationTarget))
|
||||
|
||||
val nestedTarget = topLevelTarget.declarations.firstIsInstance<KtClass>()
|
||||
assert(nestedTarget.name == "C")
|
||||
assert(!handler.canMove(elementsToMove, nestedTarget))
|
||||
}
|
||||
|
||||
fun testNestedClassToClass() = doTest { rootDir, handler ->
|
||||
val elementsToMove = arrayOf<PsiElement>(getElementAtCaret(rootDir, "test.kt").getNonStrictParentOfType<KtClass>()!!)
|
||||
val targetFile = getPsiFile(rootDir, "test2.kt") as KtFile
|
||||
|
||||
val topLevelTarget = targetFile.declarations.firstIsInstance<KtClass>()
|
||||
assert(topLevelTarget.name == "B")
|
||||
assert(handler.canMove(elementsToMove, topLevelTarget))
|
||||
|
||||
val annotationTarget = targetFile.declarations.first { it.name == "Ann" } as KtClass
|
||||
assert(!handler.canMove(elementsToMove, annotationTarget))
|
||||
|
||||
val nestedTarget = topLevelTarget.declarations.firstIsInstance<KtClass>()
|
||||
assert(nestedTarget.name == "C")
|
||||
assert(handler.canMove(elementsToMove, nestedTarget))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user