diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 5e7cc69bc4d..482dded093b 100755 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -116,6 +116,7 @@ import org.jetbrains.kotlin.idea.quickfix.AbstractQuickFixMultiFileTest import org.jetbrains.kotlin.idea.quickfix.AbstractQuickFixTest import org.jetbrains.kotlin.idea.refactoring.AbstractNameSuggestionProviderTest import org.jetbrains.kotlin.idea.refactoring.copy.AbstractCopyTest +import org.jetbrains.kotlin.idea.refactoring.copy.AbstractMultiModuleCopyTest import org.jetbrains.kotlin.idea.refactoring.inline.AbstractInlineTest import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractExtractionTest import org.jetbrains.kotlin.idea.refactoring.move.AbstractMoveTest @@ -759,6 +760,10 @@ fun main(args: Array) { model("refactoring/moveMultiModule", extension = "test", singleClass = true) } + testClass { + model("refactoring/copyMultiModule", extension = "test", singleClass = true) + } + testClass { model("multiFileIntentions", extension = "test", singleClass = true, filenameStartsLowerCase = true) } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ProjectRootsUtil.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ProjectRootsUtil.kt index adce46e83d0..a2ed03f39bc 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ProjectRootsUtil.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ProjectRootsUtil.kt @@ -22,10 +22,12 @@ import com.intellij.injected.editor.VirtualFileWindow import com.intellij.openapi.project.Project import com.intellij.openapi.roots.FileIndex import com.intellij.openapi.roots.ProjectFileIndex +import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.util.Ref import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFileSystemItem import org.jetbrains.kotlin.idea.KotlinModuleFileType import org.jetbrains.kotlin.idea.caches.resolve.JsProjectDetector import org.jetbrains.kotlin.idea.core.script.KotlinScriptConfigurationManager @@ -40,6 +42,11 @@ fun FileIndex.isInSourceContentWithoutInjected(file: VirtualFile): Boolean { return file !is VirtualFileWindow && isInSourceContent(file) } +fun VirtualFile.getSourceRoot(project: Project): VirtualFile? = ProjectRootManager.getInstance(project).fileIndex.getSourceRootForFile(this) + +val PsiFileSystemItem.sourceRoot: VirtualFile? + get() = virtualFile?.getSourceRoot(project) + object ProjectRootsUtil { @JvmStatic fun isInContent(project: Project, file: VirtualFile, includeProjectSource: Boolean, includeLibrarySource: Boolean, includeLibraryClasses: Boolean, diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/copy/CopyKotlinDeclarationDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/copy/CopyKotlinDeclarationDialog.kt index ee92554b2aa..87f5eb105ef 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/copy/CopyKotlinDeclarationDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/copy/CopyKotlinDeclarationDialog.kt @@ -15,10 +15,12 @@ */ package org.jetbrains.kotlin.idea.refactoring.copy +import com.intellij.ide.util.DirectoryChooser import com.intellij.openapi.project.Project import com.intellij.openapi.roots.JavaProjectRootsUtil import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.ui.Messages +import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiManager import com.intellij.refactoring.HelpID @@ -39,6 +41,7 @@ import org.jetbrains.annotations.NonNls import org.jetbrains.kotlin.idea.core.getPackage import org.jetbrains.kotlin.idea.refactoring.Pass import org.jetbrains.kotlin.idea.refactoring.hasIdentifiersOnly +import org.jetbrains.kotlin.idea.util.sourceRoot import org.jetbrains.kotlin.name.FqNameUnsafe import org.jetbrains.kotlin.psi.KtNamedDeclaration import java.awt.BorderLayout @@ -68,6 +71,9 @@ class CopyKotlinDeclarationDialog( var targetDirectory: MoveDestination? = null private set + val targetSourceRoot: VirtualFile? + get() = ((destinationComboBox.comboBox.selectedItem as? DirectoryChooser.ItemWrapper)?.directory ?: originalFile).sourceRoot + init { informationLabel.text = RefactoringBundle.message("copy.class.copy.0.1", UsageViewUtil.getType(declaration), UsageViewUtil.getLongName(declaration)) informationLabel.font = informationLabel.font.deriveFont(Font.BOLD) diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/copy/CopyKotlinDeclarationsHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/copy/CopyKotlinDeclarationsHandler.kt index 674fc2d3c58..9cda2eb287a 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/copy/CopyKotlinDeclarationsHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/copy/CopyKotlinDeclarationsHandler.kt @@ -22,10 +22,12 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.roots.ProjectRootManager import com.intellij.openapi.ui.Messages import com.intellij.openapi.util.Key +import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiNamedElement +import com.intellij.refactoring.BaseRefactoringProcessor import com.intellij.refactoring.RefactoringBundle import com.intellij.refactoring.copy.CopyFilesOrDirectoriesDialog import com.intellij.refactoring.copy.CopyHandlerDelegateBase @@ -33,14 +35,20 @@ import com.intellij.refactoring.rename.RenameProcessor import com.intellij.refactoring.util.MoveRenameUsageInfo import com.intellij.usageView.UsageInfo import com.intellij.util.IncorrectOperationException +import com.intellij.util.containers.MultiMap import org.jetbrains.annotations.TestOnly import org.jetbrains.kotlin.idea.codeInsight.shorten.performDelayedRefactoringRequests import org.jetbrains.kotlin.idea.core.quoteIfNeeded +import org.jetbrains.kotlin.idea.refactoring.checkConflictsInteractively import org.jetbrains.kotlin.idea.refactoring.createKotlinFile import org.jetbrains.kotlin.idea.refactoring.move.* +import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.KotlinDirectoryMoveTarget +import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveConflictChecker +import org.jetbrains.kotlin.idea.refactoring.toPsiDirectory import org.jetbrains.kotlin.idea.util.application.executeCommand import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.idea.util.sourceRoot import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtFile @@ -55,7 +63,7 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() { @set:TestOnly var Project.newName: String? by UserDataProperty(Key.create("NEW_NAME")) - private fun PsiElement.getElementsToCopy(): List { + private fun PsiElement.getElementsToCopy(): List { val declarationOrFile = parentsWithSelf.firstOrNull { it is KtFile || (it is KtNamedDeclaration && it.parent is KtFile) } return when (declarationOrFile) { is KtFile -> declarationOrFile.declarations.filterIsInstance().ifEmpty { listOf(declarationOrFile) } @@ -147,15 +155,16 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() { val project = initialTargetDirectory.project - if (ProjectRootManager.getInstance(project).fileIndex.getSourceRootForFile(initialTargetDirectory.virtualFile) == null) return - val commandName = "Copy Declarations" var openInEditor = false var newName: String? = singleElementToCopy?.name ?: originalFile.name var targetDirWrapper: AutocreatingPsiDirectoryWrapper = initialTargetDirectory.toDirectoryWrapper() + var targetSourceRoot: VirtualFile? = initialTargetDirectory.sourceRoot ?: return - if (!ApplicationManager.getApplication().isUnitTestMode) { + val isUnitTestMode = ApplicationManager.getApplication().isUnitTestMode + + if (!isUnitTestMode) { if (singleElementToCopy != null && singleElementToCopy is KtNamedDeclaration) { val dialog = CopyKotlinDeclarationDialog(singleElementToCopy, initialTargetDirectory, project) dialog.title = commandName @@ -164,6 +173,7 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() { openInEditor = dialog.openInEditor newName = dialog.newName ?: singleElementToCopy.name targetDirWrapper = dialog.targetDirectory?.toDirectoryWrapper() ?: return + targetSourceRoot = dialog.targetSourceRoot } else { val dialog = CopyFilesOrDirectoriesDialog(arrayOf(originalFile), initialTargetDirectory, project, false) @@ -171,6 +181,7 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() { openInEditor = dialog.openInEditor() newName = dialog.newName targetDirWrapper = dialog.targetDirectory?.toDirectoryWrapper() ?: return + targetSourceRoot = dialog.targetDirectory?.sourceRoot } } else { @@ -185,8 +196,8 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() { ContainerInfo.Package(originalFile.packageFqName), ContainerInfo.Package(FqName(targetPackageName)) ) - elementsToCopy.flatMap { elementToCopy -> - (elementToCopy as KtElement).getInternalReferencesToUpdateOnPackageNameChange(changeInfo).filter { + elementsToCopy.flatMapTo(LinkedHashSet()) { elementToCopy -> + elementToCopy.getInternalReferencesToUpdateOnPackageNameChange(changeInfo).filter { val referencedElement = (it as? MoveRenameUsageInfo)?.referencedElement referencedElement == null || !elementToCopy.isAncestor(referencedElement) } @@ -194,51 +205,71 @@ class CopyKotlinDeclarationsHandler : CopyHandlerDelegateBase() { } markInternalUsages(internalUsages) - val restoredInternalUsages = ArrayList() + fun doRefactor() { + val restoredInternalUsages = ArrayList() - project.executeCommand(commandName) { - try { - val targetDirectory = runWriteAction { targetDirWrapper.getOrCreateDirectory(initialTargetDirectory) } - val targetFileName = if (newName?.contains(".") ?: false) newName!! else newName + "." + originalFile.virtualFile.extension + project.executeCommand(commandName) { + try { + val targetDirectory = runWriteAction { targetDirWrapper.getOrCreateDirectory(initialTargetDirectory) } + val targetFileName = if (newName?.contains(".") ?: false) newName!! else newName + "." + originalFile.virtualFile.extension - val oldToNewElementsMapping = HashMap() + val oldToNewElementsMapping = HashMap() + + val targetFile: KtFile + if (singleElementToCopy is KtFile) { + targetFile = runWriteAction { targetDirectory.copyFileFrom(targetFileName, singleElementToCopy) as KtFile } + } + else { + targetFile = getOrCreateTargetFile(originalFile, targetDirectory, targetFileName, commandName) ?: return@executeCommand + runWriteAction { + val newElements = elementsToCopy.map { targetFile.add(it.copy()) as KtNamedDeclaration } + elementsToCopy.zip(newElements).toMap(oldToNewElementsMapping) + } + } - val targetFile: KtFile - if (singleElementToCopy is KtFile) { - targetFile = runWriteAction { targetDirectory.copyFileFrom(targetFileName, singleElementToCopy) as KtFile } - } - else { - targetFile = getOrCreateTargetFile(originalFile, targetDirectory, targetFileName, commandName) ?: return@executeCommand runWriteAction { - val newElements = elementsToCopy.map { targetFile.add(it.copy()) as KtNamedDeclaration } - elementsToCopy.zip(newElements).toMap(oldToNewElementsMapping) - } - } + for (newElement in oldToNewElementsMapping.values) { + restoredInternalUsages += restoreInternalUsages(newElement as KtElement, oldToNewElementsMapping, true) + postProcessMoveUsages(restoredInternalUsages, oldToNewElementsMapping) + } - runWriteAction { - for (newElement in oldToNewElementsMapping.values) { - restoredInternalUsages += restoreInternalUsages(newElement as KtElement, oldToNewElementsMapping, true) - postProcessMoveUsages(restoredInternalUsages, oldToNewElementsMapping) + performDelayedRefactoringRequests(project) } - performDelayedRefactoringRequests(project) - } + oldToNewElementsMapping.values.singleOrNull()?.let { + RenameProcessor(project, it, newName!!.quoteIfNeeded(), false, false).run() + } - oldToNewElementsMapping.values.singleOrNull()?.let { - RenameProcessor(project, it, newName!!.quoteIfNeeded(), false, false).run() + if (openInEditor) { + EditorHelper.openFilesInEditor(arrayOf(targetFile)) + } } - - if (openInEditor) { - EditorHelper.openFilesInEditor(arrayOf(targetFile)) + catch (e: IncorrectOperationException) { + Messages.showMessageDialog(project, e.message, RefactoringBundle.message("error.title"), Messages.getErrorIcon()) + } + finally { + cleanUpInternalUsages(internalUsages + restoredInternalUsages) } - } - catch (e: IncorrectOperationException) { - Messages.showMessageDialog(project, e.message, RefactoringBundle.message("error.title"), Messages.getErrorIcon()) - } - finally { - cleanUpInternalUsages(internalUsages + restoredInternalUsages) } } + + val conflicts = MultiMap() + + if (!(isUnitTestMode && BaseRefactoringProcessor.ConflictsInTestsException.isTestIgnore())) { + val targetSourceRootPsi = targetSourceRoot?.toPsiDirectory(project) + if (targetSourceRootPsi != null) { + val conflictChecker = MoveConflictChecker( + project, + elementsToCopy, + KotlinDirectoryMoveTarget(FqName.ROOT, targetSourceRootPsi), + originalFile + ) + conflictChecker.checkModuleConflictsInDeclarations(internalUsages, conflicts) + conflictChecker.checkVisibilityInDeclarations(conflicts) + } + } + + project.checkConflictsInteractively(conflicts, onAccept = ::doRefactor) } override fun doClone(element: PsiElement) { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/moveConflictUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/moveConflictUtils.kt index a159b48b6d0..a4e3cd58105 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/moveConflictUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveDeclarations/moveConflictUtils.kt @@ -219,7 +219,7 @@ class MoveConflictChecker( } } - private fun checkModuleConflictsInDeclarations( + fun checkModuleConflictsInDeclarations( internalUsages: MutableSet, conflicts: MultiMap ) { @@ -327,7 +327,7 @@ class MoveConflictChecker( } } - private fun checkVisibilityInDeclarations(conflicts: MultiMap) { + fun checkVisibilityInDeclarations(conflicts: MultiMap) { val targetContainer = moveTarget.getContainerDescriptor() ?: return fun DeclarationDescriptor.targetAwareContainingDescriptor(): DeclarationDescriptor? { diff --git a/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/after/A/A.iml b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/after/A/A.iml new file mode 100644 index 00000000000..c90834f2d60 --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/after/A/A.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/after/A/src/foo/test.kt b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/after/A/src/foo/test.kt new file mode 100644 index 00000000000..4c4e005ea0c --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/after/A/src/foo/test.kt @@ -0,0 +1,11 @@ +package foo + +internal class A { + val a: A = A() + val b: B = B() +} + +internal class B { + val a: A = A() + val b: B = B() +} \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/after/B/B.iml b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/after/B/B.iml new file mode 100644 index 00000000000..0c1fa46fe12 --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/after/B/B.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/after/B/src/bar/A.kt b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/after/B/src/bar/A.kt new file mode 100644 index 00000000000..9201989e1e3 --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/after/B/src/bar/A.kt @@ -0,0 +1,8 @@ +package bar + +import foo.B + +internal class A { + val a: A = A() + val b: B = B() +} \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/after/B/src/bar/dummy.kt b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/after/B/src/bar/dummy.kt new file mode 100644 index 00000000000..47c6c429b83 --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/after/B/src/bar/dummy.kt @@ -0,0 +1 @@ +package bar \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/before/A/A.iml b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/before/A/A.iml new file mode 100644 index 00000000000..c90834f2d60 --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/before/A/A.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/before/A/src/foo/test.kt b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/before/A/src/foo/test.kt new file mode 100644 index 00000000000..5473d56cd80 --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/before/A/src/foo/test.kt @@ -0,0 +1,11 @@ +package foo + +internal class A { + val a: A = A() + val b: B = B() +} + +internal class B { + val a: A = A() + val b: B = B() +} \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/before/B/B.iml b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/before/B/B.iml new file mode 100644 index 00000000000..0c1fa46fe12 --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/before/B/B.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/before/B/src/bar/dummy.kt b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/before/B/src/bar/dummy.kt new file mode 100644 index 00000000000..47c6c429b83 --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/before/B/src/bar/dummy.kt @@ -0,0 +1 @@ +package bar \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/conflicts.txt b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/conflicts.txt new file mode 100644 index 00000000000..39962eff0ba --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/conflicts.txt @@ -0,0 +1 @@ +Class A uses class B which will be inaccessible after move \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/internalReferencesToAnotherModule.test b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/internalReferencesToAnotherModule.test new file mode 100644 index 00000000000..d8a68a9d4f0 --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/internalReferencesToAnotherModule.test @@ -0,0 +1,4 @@ +{ + "mainFile": "A/src/foo/test.kt", + "targetDirectory": "B/src/bar" +} diff --git a/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/after/A/A.iml b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/after/A/A.iml new file mode 100644 index 00000000000..c90834f2d60 --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/after/A/A.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/after/A/src/foo/test.kt b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/after/A/src/foo/test.kt new file mode 100644 index 00000000000..f1245dc0a8b --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/after/A/src/foo/test.kt @@ -0,0 +1,11 @@ +package foo + +class A { + val a: A = A() + val b: B = B() +} + +class B { + val a: A = A() + val b: B = B() +} \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/after/B/B.iml b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/after/B/B.iml new file mode 100644 index 00000000000..c90834f2d60 --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/after/B/B.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/after/B/src/bar/A.kt b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/after/B/src/bar/A.kt new file mode 100644 index 00000000000..f7d35322d22 --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/after/B/src/bar/A.kt @@ -0,0 +1,6 @@ +package bar + +class A { + val a: A = A() + val b: foo.B = foo.B() +} \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/after/B/src/bar/dummy.kt b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/after/B/src/bar/dummy.kt new file mode 100644 index 00000000000..47c6c429b83 --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/after/B/src/bar/dummy.kt @@ -0,0 +1 @@ +package bar \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/before/A/A.iml b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/before/A/A.iml new file mode 100644 index 00000000000..c90834f2d60 --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/before/A/A.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/before/A/src/foo/test.kt b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/before/A/src/foo/test.kt new file mode 100644 index 00000000000..b2f7f251fae --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/before/A/src/foo/test.kt @@ -0,0 +1,11 @@ +package foo + +class A { + val a: A = A() + val b: B = B() +} + +class B { + val a: A = A() + val b: B = B() +} \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/before/B/B.iml b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/before/B/B.iml new file mode 100644 index 00000000000..c90834f2d60 --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/before/B/B.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/before/B/src/bar/dummy.kt b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/before/B/src/bar/dummy.kt new file mode 100644 index 00000000000..47c6c429b83 --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/before/B/src/bar/dummy.kt @@ -0,0 +1 @@ +package bar \ No newline at end of file diff --git a/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/conflicts.txt b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/conflicts.txt new file mode 100644 index 00000000000..0a4ac675bfa --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/conflicts.txt @@ -0,0 +1 @@ +Class foo.B, referenced in property A.b, will not be accessible in module B diff --git a/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/referencesToUnrelatedModule.test b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/referencesToUnrelatedModule.test new file mode 100644 index 00000000000..d8a68a9d4f0 --- /dev/null +++ b/idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/referencesToUnrelatedModule.test @@ -0,0 +1,4 @@ +{ + "mainFile": "A/src/foo/test.kt", + "targetDirectory": "B/src/bar" +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/copy/AbstractCopyTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/copy/AbstractCopyTest.kt index aedd59ed752..5341068ef62 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/copy/AbstractCopyTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/copy/AbstractCopyTest.kt @@ -29,21 +29,37 @@ import org.jetbrains.kotlin.idea.jsonUtils.getString import org.jetbrains.kotlin.idea.refactoring.AbstractMultifileRefactoringTest import org.jetbrains.kotlin.idea.refactoring.copy.CopyKotlinDeclarationsHandler.Companion.newName import org.jetbrains.kotlin.idea.refactoring.runRefactoringTest +import org.jetbrains.kotlin.idea.refactoring.toPsiDirectory import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.utils.ifEmpty -abstract class AbstractCopyTest : AbstractMultifileRefactoringTest(), AbstractMultifileRefactoringTest.RefactoringAction { - override fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementsAtCaret: List, config: JsonObject) { - val elementsToCopy = elementsAtCaret.ifEmpty { listOf(mainFile) }.toTypedArray() - assert(CopyHandler.canCopy(elementsToCopy)) +abstract class AbstractCopyTest : AbstractMultifileRefactoringTest() { + companion object : RefactoringAction { + fun runCopyRefactoring(path: String, config: JsonObject, rootDir: VirtualFile, project: Project) { + runRefactoringTest(path, config, rootDir, project, this) + } - val packageWrapper = PackageWrapper(mainFile.manager, config.getString("targetPackage")) - project.newName = config.getNullableString("newName") - val targetDirectory = runWriteAction { MultipleRootsMoveDestination(packageWrapper).getTargetDirectory(mainFile) } - CopyHandler.doCopy(elementsToCopy, targetDirectory) + override fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementsAtCaret: List, config: JsonObject) { + val project = mainFile.project + + val elementsToCopy = elementsAtCaret.ifEmpty { listOf(mainFile) }.toTypedArray() + assert(CopyHandler.canCopy(elementsToCopy)) + + val targetDirectory = config.getNullableString("targetDirectory")?.let { + rootDir.findFileByRelativePath(it)?.toPsiDirectory(project) + } + ?: run { + val packageWrapper = PackageWrapper(mainFile.manager, config.getString("targetPackage")) + runWriteAction { MultipleRootsMoveDestination(packageWrapper).getTargetDirectory(mainFile) } + } + + project.newName = config.getNullableString("newName") + + CopyHandler.doCopy(elementsToCopy, targetDirectory) + } } override fun runRefactoring(path: String, config: JsonObject, rootDir: VirtualFile, project: Project) { - runRefactoringTest(path, config, rootDir, project, this) + runCopyRefactoring(path, config, rootDir, project) } } \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/copy/AbstractMultiModuleCopyTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/copy/AbstractMultiModuleCopyTest.kt new file mode 100644 index 00000000000..e86fdc6c073 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/copy/AbstractMultiModuleCopyTest.kt @@ -0,0 +1,38 @@ +/* + * 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.copy + +import org.jetbrains.kotlin.idea.refactoring.rename.loadTestConfiguration +import org.jetbrains.kotlin.idea.test.KotlinMultiFileTestCase +import org.jetbrains.kotlin.idea.test.PluginTestCaseBase +import java.io.File + +abstract class AbstractMultiModuleCopyTest : KotlinMultiFileTestCase() { + override fun getTestRoot(): String = "/refactoring/copyMultiModule/" + + override fun getTestDataPath(): String = PluginTestCaseBase.getTestDataPathBase() + + fun doTest(path: String) { + val config = loadTestConfiguration(File(path)) + + isMultiModule = true + + doTestCommittingDocuments { rootDir, _ -> + AbstractCopyTest.runCopyRefactoring(path, config, rootDir, project) + } + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/copy/MultiModuleCopyTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/copy/MultiModuleCopyTestGenerated.java new file mode 100644 index 00000000000..5d9e2ef1dcf --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/copy/MultiModuleCopyTestGenerated.java @@ -0,0 +1,50 @@ +/* + * 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.copy; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TargetBackend; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("idea/testData/refactoring/copyMultiModule") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class MultiModuleCopyTestGenerated extends AbstractMultiModuleCopyTest { + public void testAllFilesPresentInCopyMultiModule() throws Exception { + KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/refactoring/copyMultiModule"), Pattern.compile("^(.+)\\.test$"), TargetBackend.ANY); + } + + @TestMetadata("internalReferencesToAnotherModule2/internalReferencesToAnotherModule.test") + public void testInternalReferencesToAnotherModule2_InternalReferencesToAnotherModule() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/copyMultiModule/internalReferencesToAnotherModule2/internalReferencesToAnotherModule.test"); + doTest(fileName); + } + + @TestMetadata("referencesToUnrelatedModule/referencesToUnrelatedModule.test") + public void testReferencesToUnrelatedModule_ReferencesToUnrelatedModule() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/copyMultiModule/referencesToUnrelatedModule/referencesToUnrelatedModule.test"); + doTest(fileName); + } +}