From a68a6b77dfa50132193f31f0b92151282842ac54 Mon Sep 17 00:00:00 2001 From: Igor Yakovlev Date: Tue, 24 Dec 2019 23:23:07 +0300 Subject: [PATCH] Move top level declaration refactoring --- ...oveKotlinNestedClassesToUpperLevelModel.kt | 45 +++--- .../ui/MoveKotlinTopLevelDeclarationsModel.kt | 128 ++++++++---------- 2 files changed, 77 insertions(+), 96 deletions(-) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesToUpperLevelModel.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesToUpperLevelModel.kt index 9ddb3892a7a..7006f69ac5a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesToUpperLevelModel.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinNestedClassesToUpperLevelModel.kt @@ -17,13 +17,11 @@ import com.intellij.psi.PsiElement import com.intellij.psi.PsiManager import com.intellij.refactoring.PackageWrapper import com.intellij.refactoring.RefactoringBundle -import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesUtil import com.intellij.refactoring.util.RefactoringMessageUtil import com.intellij.refactoring.util.RefactoringUtil import com.intellij.util.IncorrectOperationException -import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.idea.KotlinFileType -import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.core.KotlinNameSuggester import org.jetbrains.kotlin.idea.refactoring.createKotlinFile import org.jetbrains.kotlin.idea.refactoring.move.getTargetPackageFqName @@ -57,13 +55,13 @@ internal abstract class MoveKotlinNestedClassesToUpperLevelModel( initialDir: PsiDirectory? ): VirtualFile? - private val innerClassDescriptor = innerClass.unsafeResolveToDescriptor(BodyResolveMode.FULL) as ClassDescriptor + private val innerClassDescriptor = innerClass.resolveToDescriptorIfAny(BodyResolveMode.FULL) private fun getTargetContainer(): PsiElement? { if (target is PsiDirectory) { val oldPackageFqName = getTargetPackageFqName(target) val targetName = packageName - if (!Comparing.equal(oldPackageFqName?.asString(), targetName)) { + if (oldPackageFqName?.asString() != targetName) { val projectRootManager = ProjectRootManager.getInstance(project) val contentSourceRoots = getSuitableDestinationSourceRoots(project) @@ -88,24 +86,21 @@ internal abstract class MoveKotlinNestedClassesToUpperLevelModel( targetSourceRoot = contentSourceRoots[0] } - var directory = RefactoringUtil.findPackageDirectoryInSourceRoot(newPackage, targetSourceRoot); - if (directory === null) { - runWriteAction { - try { - directory = RefactoringUtil.createPackageDirectoryInSourceRoot(newPackage, targetSourceRoot) - } catch (e: IncorrectOperationException) { - directory = null; - } + RefactoringUtil.findPackageDirectoryInSourceRoot(newPackage, targetSourceRoot)?.let { return it } + + return runWriteAction { + try { + RefactoringUtil.createPackageDirectoryInSourceRoot(newPackage, targetSourceRoot) + } catch (e: IncorrectOperationException) { + null } } - return directory; } return target } - return if (target is KtFile || target is KtClassOrObject) target else null - + return target as? KtFile ?: target as? KtClassOrObject } @Throws(ConfigurationException::class) @@ -142,11 +137,10 @@ internal abstract class MoveKotlinNestedClassesToUpperLevelModel( val targetPackageFqName = getTargetPackageFqName(target) ?: throw ConfigurationException("No package corresponds to this directory") - val existingClass = DescriptorUtils - .getContainingModule(innerClassDescriptor) - .getPackage(targetPackageFqName) - .memberScope - .getContributedClassifier(Name.identifier(className), NoLookupLocation.FROM_IDE) + val existingClass = innerClassDescriptor?.let { DescriptorUtils.getContainingModule(it) } + ?.getPackage(targetPackageFqName) + ?.memberScope + ?.getContributedClassifier(Name.identifier(className), NoLookupLocation.FROM_IDE) if (existingClass != null) { throw ConfigurationException("Class $className already exists in package $targetPackageFqName") } @@ -163,21 +157,20 @@ internal abstract class MoveKotlinNestedClassesToUpperLevelModel( private fun getMoveTarget(): KotlinMoveTarget { val target = getTargetContainerWithValidation() if (target is PsiDirectory) { - val targetDir = target - val targetPackageFqName = getTargetPackageFqName(target) ?: throw ConfigurationException("Cannot find target package name") val suggestedName = KotlinNameSuggester.suggestNameByName(className) { - targetDir.findFile(it + "." + KotlinFileType.EXTENSION) == null + target.findFile(it + "." + KotlinFileType.EXTENSION) == null } val targetFileName = suggestedName + "." + KotlinFileType.EXTENSION return KotlinMoveTargetForDeferredFile( targetPackageFqName, - targetDir, null - ) { createKotlinFile(targetFileName, targetDir, targetPackageFqName.asString()) } + target, + targetFile = null + ) { createKotlinFile(targetFileName, target, targetPackageFqName.asString()) } } else { return KotlinMoveTargetForExistingElement(target as KtElement) } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsModel.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsModel.kt index 7ab5f65dd37..89036ffe709 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsModel.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/ui/MoveKotlinTopLevelDeclarationsModel.kt @@ -7,26 +7,23 @@ package org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.ui import com.intellij.openapi.fileTypes.FileTypeManager import com.intellij.openapi.project.Project -import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.options.ConfigurationException import com.intellij.psi.* import com.intellij.refactoring.BaseRefactoringProcessor import com.intellij.refactoring.MoveDestination import com.intellij.refactoring.PackageWrapper -import com.intellij.refactoring.RefactoringBundle import com.intellij.refactoring.move.MoveCallback import com.intellij.refactoring.move.moveClassesOrPackages.AutocreatingSingleSourceRootMoveDestination import com.intellij.refactoring.move.moveClassesOrPackages.MultipleRootsMoveDestination -import com.intellij.util.IncorrectOperationException import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.core.getPackage +import org.jetbrains.kotlin.idea.core.util.toPsiDirectory import org.jetbrains.kotlin.idea.core.util.toPsiFile import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringBundle import org.jetbrains.kotlin.idea.refactoring.getOrCreateKotlinFile import org.jetbrains.kotlin.idea.refactoring.move.getOrCreateDirectory import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.* import org.jetbrains.kotlin.idea.refactoring.move.updatePackageDirective -import org.jetbrains.kotlin.idea.util.application.executeCommand -import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtNamedDeclaration @@ -51,29 +48,24 @@ internal class MoveKotlinTopLevelDeclarationsModel( val moveCallback: MoveCallback? ) : Model { - private inline fun List.mapToSingleOrNull(transform: (T) -> K?): K? = + private inline fun Set.mapToSingleOrNull(transform: (T) -> K?): K? = mapTo(mutableSetOf(), transform).singleOrNull() - private val sourceDirectory by lazy { + private fun checkedGetSourceDirectory() = sourceFiles.mapToSingleOrNull { it.parent } ?: throw ConfigurationException("Can't determine sources directory") - } - private val sourceFiles = elementsToMove.map { it.containingKtFile }.distinct() + private val sourceFiles: Set = elementsToMove.mapTo(mutableSetOf()) { it.containingKtFile } - private data class TargetDirAndDestination(val targetDir: VirtualFile?, val destination: MoveDestination) + private val singleSourceFileMode = sourceFiles.size == 1 - private fun selectPackageBasedTargetDirAndDestination(): TargetDirAndDestination { + private fun selectPackageBasedDestination(): MoveDestination { val targetPackageWrapper = PackageWrapper(PsiManager.getInstance(project), targetPackage) return if (selectedPsiDirectory == null) - TargetDirAndDestination(null, MultipleRootsMoveDestination(targetPackageWrapper)) - else { - TargetDirAndDestination( - selectedPsiDirectory.virtualFile, - AutocreatingSingleSourceRootMoveDestination(targetPackageWrapper, selectedPsiDirectory.virtualFile) - ) - } + MultipleRootsMoveDestination(targetPackageWrapper) + else + AutocreatingSingleSourceRootMoveDestination(targetPackageWrapper, selectedPsiDirectory.virtualFile) } private fun checkTargetFileName(fileName: String) { @@ -82,7 +74,7 @@ internal class MoveKotlinTopLevelDeclarationsModel( } } - private fun getFilesExistingInTargetDir( + private fun getFilesExistingInTargetDirectory( targetFileName: String?, targetDirectory: PsiDirectory ): Set { @@ -93,41 +85,43 @@ internal class MoveKotlinTopLevelDeclarationsModel( } } + private fun tryMoveToPackageForExistingDirectory(targetFileName: String?, targetDirectory: PsiDirectory): KotlinMoveTarget? { + + val filesExistingInTargetDir = getFilesExistingInTargetDirectory(targetFileName, targetDirectory) + + if (filesExistingInTargetDir.isEmpty()) return null + + if (singleSourceFileMode) { + val singeTargetFile = filesExistingInTargetDir.single() as? KtFile + if (singeTargetFile != null) { + return KotlinMoveTargetForExistingElement(singeTargetFile) + } + } else { + val filePathsToReport = filesExistingInTargetDir.joinToString( + separator = "\n", + prefix = "Cannot perform refactoring since the following files already exist:\n\n" + ) { it.virtualFile.path } + throw ConfigurationException(filePathsToReport) + } + + return null + } + private fun selectMoveTargetToPackage(): KotlinMoveTarget { require(sourceFiles.isNotEmpty()) - val (targetDir, moveDestination) = selectPackageBasedTargetDirAndDestination() - val targetDirectory = moveDestination.getTargetIfExists(sourceDirectory) - ?: throw ConfigurationException("Can't get target directory for selected package") - - val destination = sourceFiles - .mapToSingleOrNull { moveDestination.getTargetIfExists(it) } - ?: throw ConfigurationException("Can't get target for all source elements") - - val singleSourceFileMode = sourceFiles.size == 1 + val moveDestination = selectPackageBasedDestination() + val targetDirectory: PsiDirectory? = moveDestination.getTargetIfExists(checkedGetSourceDirectory()) val targetFileName = if (singleSourceFileMode) fileNameInPackage.also(::checkTargetFileName) else null - val filesExistingInTargetDir = getFilesExistingInTargetDir(targetFileName, targetDirectory) - if (filesExistingInTargetDir.isNotEmpty()) { - if (singleSourceFileMode) { - val singeTargetFile = filesExistingInTargetDir.single() as? KtFile - if (singeTargetFile != null) { - return KotlinMoveTargetForExistingElement(singeTargetFile) - } - } else { - val filePathsToReport = filesExistingInTargetDir.joinToString( - separator = "\n", - prefix = "Cannot perform refactoring since the following files already exist:\n\n" - ) { it.virtualFile.path } - throw ConfigurationException(filePathsToReport) - } + if (targetDirectory != null) { + tryMoveToPackageForExistingDirectory(targetFileName, targetDirectory)?.let { return it } } return KotlinMoveTargetForDeferredFile( FqName(targetPackage), - destination, - targetDir + targetDirectory ) { val deferredFileName = if (singleSourceFileMode) fileNameInPackage else it.name val deferredFileDirectory = moveDestination.getTargetDirectory(it) @@ -137,51 +131,49 @@ internal class MoveKotlinTopLevelDeclarationsModel( private fun selectMoveTargetToFile(): KotlinMoveTarget { - try { - Paths.get(targetFilePath) + val targetFile = try { + Paths.get(targetFilePath).toFile() } catch (e: InvalidPathException) { throw ConfigurationException("Invalid target path $targetFilePath") } - val targetFile = File(targetFilePath) checkTargetFileName(targetFile.name) val jetFile = targetFile.toPsiFile(project) as? KtFile if (jetFile != null) { - if (sourceFiles.size == 1 && sourceFiles.contains(jetFile)) { + if (sourceFiles.singleOrNull() == jetFile) { throw ConfigurationException("Can't move to the original file") } return KotlinMoveTargetForExistingElement(jetFile) } - val targetDirPath = targetFile.toPath().parent + val targetDirectoryPath = targetFile.toPath().parent ?: throw ConfigurationException("Incorrect target path. Directory is not specified.") val projectBasePath = project.basePath ?: throw ConfigurationException("Can't move for current project") - if (!targetDirPath.startsWith(projectBasePath)) { - throw ConfigurationException("Incorrect target path. Directory $targetDirPath does not belong to current project.") + if (!targetDirectoryPath.startsWith(projectBasePath)) { + throw ConfigurationException("Incorrect target path. Directory $targetDirectoryPath does not belong to current project.") } - val absoluteTargetDirPath = targetDirPath.toString() - val psiDirectory: PsiDirectory - try { - psiDirectory = getOrCreateDirectory(absoluteTargetDirPath, project) - } catch (e: IncorrectOperationException) { - throw ConfigurationException("Failed to create parent directory: $absoluteTargetDirPath") - } + val psiDirectory = targetDirectoryPath.toFile().toPsiDirectory(project) val targetPackageFqName = sourceFiles.singleOrNull()?.packageFqName - ?: JavaDirectoryService.getInstance().getPackage(psiDirectory)?.let { FqName(it.qualifiedName) } - ?: throw ConfigurationException("Could not find package corresponding to $absoluteTargetDirPath") + ?: psiDirectory?.getPackage()?.let { FqName(it.qualifiedName) } + ?: throw ConfigurationException("Could not find package corresponding to $targetDirectoryPath") + val targetDirectoryPathString = targetDirectoryPath.toString() val finalTargetPackageFqName = targetPackageFqName.asString() + return KotlinMoveTargetForDeferredFile( targetPackageFqName, psiDirectory, targetFile = null - ) { getOrCreateKotlinFile(targetFile.name, psiDirectory, finalTargetPackageFqName) } + ) { + val actualPsiDirectory = psiDirectory ?: getOrCreateDirectory(targetDirectoryPathString, project) + getOrCreateKotlinFile(targetFile.name, actualPsiDirectory, finalTargetPackageFqName) + } } private fun selectMoveTarget() = @@ -191,7 +183,7 @@ internal class MoveKotlinTopLevelDeclarationsModel( if (elementsToMove.isEmpty()) throw ConfigurationException("At least one member must be selected") if (sourceFiles.isEmpty()) throw ConfigurationException("None elements were selected") - if (sourceFiles.size == 1 && fileNameInPackage.isBlank()) throw ConfigurationException("File name may not be empty") + if (singleSourceFileMode && fileNameInPackage.isBlank()) throw ConfigurationException("File name may not be empty") if (isMoveToPackage) { if (targetPackage.isNotEmpty() && !PsiNameHelper.getInstance(project).isQualifiedName(targetPackage)) { @@ -225,20 +217,16 @@ internal class MoveKotlinTopLevelDeclarationsModel( val targetFileName = if (sourceFiles.size > 1) null else fileNameInPackage if (targetFileName != null) checkTargetFileName(targetFileName) - val moveDestination = selectPackageBasedTargetDirAndDestination().destination - val targetDir = moveDestination.getTargetIfExists(sourceDirectory) ?: return null + val moveDestination = selectPackageBasedDestination() + val targetDirectory = moveDestination.getTargetIfExists(checkedGetSourceDirectory()) ?: return null - val filesExistingInTargetDir = getFilesExistingInTargetDir(targetFileName, targetDir) + val filesExistingInTargetDir = getFilesExistingInTargetDirectory(targetFileName, targetDirectory) val moveAsFile = filesExistingInTargetDir.isEmpty() || filesExistingInTargetDir.singleOrNull()?.let { sourceFiles.contains(it) } == true if (!moveAsFile) return null - val targetDirectory = project.executeCommand(RefactoringBundle.message("move.title"), null) { - runWriteAction { moveDestination.getTargetDirectory(sourceDirectory) } - } - sourceFiles.forEach { it.updatePackageDirective = isUpdatePackageDirective } return if (targetFileName != null) @@ -255,7 +243,7 @@ internal class MoveKotlinTopLevelDeclarationsModel( else KotlinAwareMoveFilesOrDirectoriesProcessor( project, - sourceFiles, + sourceFiles.toList(), targetDirectory, isSearchReferences, searchInComments = isSearchInComments,