Fix move refactoring for several class files

Fixed #KT-35235
(also fixed #KT-35427)
This commit is contained in:
Igor Yakovlev
2019-12-19 01:03:57 +03:00
parent 51f726be9b
commit 6fb788d446
2 changed files with 93 additions and 82 deletions
@@ -76,18 +76,13 @@ class MoveToKotlinFileProcessor @JvmOverloads constructor(
} }
override fun performRefactoring(usages: Array<UsageInfo>) { override fun performRefactoring(usages: Array<UsageInfo>) {
val needTemporaryRename = targetDirectory.findFile(targetFileName) != null val needTemporaryRename = targetDirectory.findFile(sourceFile.name) != null
if (needTemporaryRename) { if (needTemporaryRename) {
renameFileTemporarily() renameFileTemporarily()
} }
try { super.performRefactoring(usages)
super.performRefactoring(usages) sourceFile.name = targetFileName
} finally {
if (needTemporaryRename) {
sourceFile.name = targetFileName
}
}
} }
companion object { companion object {
@@ -18,6 +18,7 @@ import com.intellij.refactoring.move.MoveCallback
import com.intellij.refactoring.move.moveClassesOrPackages.AutocreatingSingleSourceRootMoveDestination import com.intellij.refactoring.move.moveClassesOrPackages.AutocreatingSingleSourceRootMoveDestination
import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDestination import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDestination
import com.intellij.util.IncorrectOperationException import com.intellij.util.IncorrectOperationException
import org.jetbrains.kotlin.backend.common.onlyIf
import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.core.util.toPsiFile import org.jetbrains.kotlin.idea.core.util.toPsiFile
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringBundle import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringBundle
@@ -51,8 +52,11 @@ internal class MoveKotlinTopLevelDeclarationsModel(
val moveCallback: MoveCallback? val moveCallback: MoveCallback?
) : Model<BaseRefactoringProcessor> { ) : Model<BaseRefactoringProcessor> {
private inline fun <T, K> List<T>.mapToSingleOrNull(transform: (T) -> K?): K? =
mapTo(mutableSetOf(), transform).singleOrNull()
private val sourceDirectory by lazy { private val sourceDirectory by lazy {
sourceFiles.singleOrNull { it.parent !== null }?.parent ?: throw ConfigurationException("Can't determine sources directory") sourceFiles.mapToSingleOrNull { it.parent } ?: throw ConfigurationException("Can't determine sources directory")
} }
private val sourceFiles = elementsToMove.map { it.containingKtFile }.distinct() private val sourceFiles = elementsToMove.map { it.containingKtFile }.distinct()
@@ -81,48 +85,54 @@ internal class MoveKotlinTopLevelDeclarationsModel(
private fun getFilesExistingInTargetDir( private fun getFilesExistingInTargetDir(
targetFileName: String?, targetFileName: String?,
targetDirectory: PsiDirectory? targetDirectory: PsiDirectory
): List<PsiFile> { ): Set<PsiFile> {
targetDirectory ?: return emptyList() return if (targetFileName != null) {
targetDirectory.findFile(targetFileName)?.let { setOf(it) }.orEmpty()
val fileNames = targetFileName?.let { listOf(it) } ?: sourceFiles.map { it.name } } else {
sourceFiles.mapNotNullTo(mutableSetOf()) { targetDirectory.findFile(it.name) }
return fileNames }
.distinct()
.mapNotNull { targetDirectory.findFile(it) }
} }
private fun selectMoveTargetToPackage(): KotlinMoveTarget { private fun selectMoveTargetToPackage(): KotlinMoveTarget {
require(sourceFiles.isNotEmpty())
if (sourceFiles.size > 1) throw ConfigurationException("Can't move from multiply source files")
checkTargetFileName(fileNameInPackage)
val (targetDir, moveDestination) = selectPackageBasedTargetDirAndDestination() val (targetDir, moveDestination) = selectPackageBasedTargetDirAndDestination()
val targetDirectory = moveDestination.getTargetIfExists(sourceDirectory) val targetDirectory = moveDestination.getTargetIfExists(sourceDirectory)
val filesExistingInTargetDir = getFilesExistingInTargetDir(fileNameInPackage, targetDirectory) val destination = sourceFiles
.mapToSingleOrNull { moveDestination.getTargetIfExists(it) }
?: throw ConfigurationException("Can't get target for all source elements")
val singleSourceFileMode = sourceFiles.size == 1
val targetFileName = if (singleSourceFileMode) fileNameInPackage.also(::checkTargetFileName) else null
val filesExistingInTargetDir = getFilesExistingInTargetDir(targetFileName, targetDirectory)
if (filesExistingInTargetDir.isNotEmpty()) { if (filesExistingInTargetDir.isNotEmpty()) {
if (filesExistingInTargetDir.size > 1) { if (singleSourceFileMode) {
val singeTargetFile = filesExistingInTargetDir.single() as? KtFile
if (singeTargetFile != null) {
return KotlinMoveTargetForExistingElement(singeTargetFile)
}
} else {
val filePathsToReport = filesExistingInTargetDir.joinToString( val filePathsToReport = filesExistingInTargetDir.joinToString(
separator = "\n", separator = "\n",
prefix = "Cannot perform refactoring since the following files already exist:\n\n" prefix = "Cannot perform refactoring since the following files already exist:\n\n"
) { it.virtualFile.path } ) { it.virtualFile.path }
throw ConfigurationException(filePathsToReport) throw ConfigurationException(filePathsToReport)
} }
(filesExistingInTargetDir[0] as? KtFile)?.let {
return KotlinMoveTargetForExistingElement(it)
}
} }
// All source files must be in the same directory
return KotlinMoveTargetForDeferredFile( return KotlinMoveTargetForDeferredFile(
FqName(targetPackage), FqName(targetPackage),
moveDestination.getTargetIfExists(sourceFiles[0]), destination,
targetDir targetDir
) { getOrCreateKotlinFile(fileNameInPackage, moveDestination.getTargetDirectory(it)) } ) {
val deferredFileName = if (singleSourceFileMode) fileNameInPackage else it.name
val deferredFileDirectory = moveDestination.getTargetDirectory(it)
getOrCreateKotlinFile(deferredFileName, deferredFileDirectory)
}
} }
private fun selectMoveTargetToFile(): KotlinMoveTarget { private fun selectMoveTargetToFile(): KotlinMoveTarget {
@@ -137,7 +147,7 @@ internal class MoveKotlinTopLevelDeclarationsModel(
checkTargetFileName(targetFile.name) checkTargetFileName(targetFile.name)
val jetFile = targetFile.toPsiFile(project) as? KtFile val jetFile = targetFile.toPsiFile(project) as? KtFile
if (jetFile !== null) { if (jetFile != null) {
if (sourceFiles.size == 1 && sourceFiles.contains(jetFile)) { if (sourceFiles.size == 1 && sourceFiles.contains(jetFile)) {
throw ConfigurationException("Can't move to the original file") throw ConfigurationException("Can't move to the original file")
} }
@@ -173,13 +183,11 @@ internal class MoveKotlinTopLevelDeclarationsModel(
private fun selectMoveTarget() = private fun selectMoveTarget() =
if (isMoveToPackage) selectMoveTargetToPackage() else selectMoveTargetToFile() if (isMoveToPackage) selectMoveTargetToPackage() else selectMoveTargetToFile()
private fun verifyBeforeRun(target: KotlinMoveTarget) { private fun verifyBeforeRun() {
if (elementsToMove.isEmpty()) throw ConfigurationException("At least one member must be selected") if (elementsToMove.isEmpty()) throw ConfigurationException("At least one member must be selected")
if (sourceFiles.isEmpty()) throw ConfigurationException("None elements were selected")
for (element in elementsToMove) { if (sourceFiles.size == 1 && fileNameInPackage.isBlank()) throw ConfigurationException("File name may not be empty")
target.verify(element.containingFile)?.let { throw ConfigurationException(it) }
}
if (isMoveToPackage) { if (isMoveToPackage) {
if (targetPackage.isNotEmpty() && !PsiNameHelper.getInstance(project).isQualifiedName(targetPackage)) { if (targetPackage.isNotEmpty() && !PsiNameHelper.getInstance(project).isQualifiedName(targetPackage)) {
@@ -187,68 +195,76 @@ internal class MoveKotlinTopLevelDeclarationsModel(
} }
} else { } else {
val targetFile = File(targetFilePath).toPsiFile(project) val targetFile = File(targetFilePath).toPsiFile(project)
if (targetFile !== null && targetFile !is KtFile) { if (targetFile != null && targetFile !is KtFile) {
throw ConfigurationException(KotlinRefactoringBundle.message("refactoring.move.non.kotlin.file")) throw ConfigurationException(KotlinRefactoringBundle.message("refactoring.move.non.kotlin.file"))
} }
} }
if (sourceFiles.size == 1 && fileNameInPackage.isEmpty()) {
throw ConfigurationException("File name may not be empty")
}
} }
private val verifiedMoveTarget get() = selectMoveTarget().also { verifyBeforeRun(it) }
@Throws(ConfigurationException::class) @Throws(ConfigurationException::class)
override fun computeModelResult() = computeModelResult(throwOnConflicts = false) override fun computeModelResult() = computeModelResult(throwOnConflicts = false)
@Throws(ConfigurationException::class) @Throws(ConfigurationException::class)
override fun computeModelResult(throwOnConflicts: Boolean): BaseRefactoringProcessor { override fun computeModelResult(throwOnConflicts: Boolean): BaseRefactoringProcessor {
val target = verifiedMoveTarget verifyBeforeRun()
if (isFullFileMove && isMoveToPackage) { if (isFullFileMove && isMoveToPackage) {
val (_, moveDestination) = selectPackageBasedTargetDirAndDestination() tryMoveFile(throwOnConflicts)?.let { return it }
}
val targetDir = moveDestination.getTargetIfExists(sourceDirectory) return moveDeclaration(throwOnConflicts)
val targetFileName = if (sourceFiles.size > 1) null else fileNameInPackage }
val filesExistingInTargetDir = getFilesExistingInTargetDir(targetFileName, targetDir) private fun tryMoveFile(throwOnConflicts: Boolean): BaseRefactoringProcessor? {
if (filesExistingInTargetDir.isEmpty() || val targetFileName = if (sourceFiles.size > 1) null else fileNameInPackage
(filesExistingInTargetDir.size == 1 && sourceFiles.contains(filesExistingInTargetDir[0])) if (targetFileName != null) checkTargetFileName(targetFileName)
) {
val targetDirectory = project.executeCommand(RefactoringBundle.message("move.title"), null) {
runWriteAction<PsiDirectory> {
moveDestination.getTargetDirectory(sourceDirectory)
}
}
sourceFiles.forEach { it.updatePackageDirective = isUpdatePackageDirective } val moveDestination = selectPackageBasedTargetDirAndDestination().destination
val targetDir = moveDestination.getTargetIfExists(sourceDirectory)
return if (sourceFiles.size == 1 && targetFileName !== null) val filesExistingInTargetDir = getFilesExistingInTargetDir(targetFileName, targetDir)
MoveToKotlinFileProcessor(
project, val moveAsFile = filesExistingInTargetDir.isEmpty() ||
sourceFiles[0], filesExistingInTargetDir.singleOrNull()?.let { sourceFiles.contains(it) } == true
targetDirectory,
targetFileName, if (!moveAsFile) return null
searchInComments = isSearchInComments,
searchInNonJavaFiles = isSearchInNonJavaFiles, val targetDirectory = project.executeCommand(RefactoringBundle.message("move.title"), null) {
moveCallback = moveCallback, runWriteAction<PsiDirectory> { moveDestination.getTargetDirectory(sourceDirectory) }
throwOnConflicts = throwOnConflicts }
)
else sourceFiles.forEach { it.updatePackageDirective = isUpdatePackageDirective }
KotlinAwareMoveFilesOrDirectoriesProcessor(
project, return if (targetFileName != null)
sourceFiles, MoveToKotlinFileProcessor(
targetDirectory, project,
isSearchReferences, sourceFiles.first(),
searchInComments = isSearchInComments, targetDirectory,
searchInNonJavaFiles = isSearchInNonJavaFiles, targetFileName,
moveCallback = moveCallback, searchInComments = isSearchInComments,
throwOnConflicts = throwOnConflicts searchInNonJavaFiles = isSearchInNonJavaFiles,
) moveCallback = moveCallback,
} throwOnConflicts = throwOnConflicts
)
else
KotlinAwareMoveFilesOrDirectoriesProcessor(
project,
sourceFiles,
targetDirectory,
isSearchReferences,
searchInComments = isSearchInComments,
searchInNonJavaFiles = isSearchInNonJavaFiles,
moveCallback = moveCallback,
throwOnConflicts = throwOnConflicts
)
}
private fun moveDeclaration(throwOnConflicts: Boolean): BaseRefactoringProcessor {
val target = selectMoveTarget()
for (element in elementsToMove) {
target.verify(element.containingFile)?.let { throw ConfigurationException(it) }
} }
val options = MoveDeclarationsDescriptor( val options = MoveDeclarationsDescriptor(